Просмотр исходного кода

Merge branch 'master' of github.com:grafana/grafana

Torkel Ödegaard 7 лет назад
Родитель
Сommit
453ca25847

+ 1250 - 0
devenv/dev-dashboards/panel_tests_gauge.json

@@ -0,0 +1,1250 @@
+{
+  "annotations": {
+    "list": [
+      {
+        "builtIn": 1,
+        "datasource": "-- Grafana --",
+        "enable": true,
+        "hide": true,
+        "iconColor": "rgba(0, 211, 255, 1)",
+        "name": "Annotations & Alerts",
+        "type": "dashboard"
+      }
+    ]
+  },
+  "editable": true,
+  "gnetId": null,
+  "graphTooltip": 0,
+  "iteration": 1547810606599,
+  "links": [],
+  "panels": [
+    {
+      "collapsed": false,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 0
+      },
+      "id": 11,
+      "panels": [],
+      "title": "Value options tests",
+      "type": "row"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 0,
+        "y": 1
+      },
+      "id": 2,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Average, 2 decimals, ms unit",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 5,
+        "y": 1
+      },
+      "id": 5,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "max",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Max (90 ms), no decimals",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 5,
+        "x": 11,
+        "y": 1
+      },
+      "id": 6,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "p",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "s",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "Current (10 ms), no unit, prefix (p), suffix (s)",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 4,
+        "w": 3,
+        "x": 16,
+        "y": 1
+      },
+      "id": 16,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 4,
+        "w": 5,
+        "x": 19,
+        "y": 1
+      },
+      "id": 18,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,91"
+        }
+      ],
+      "timeFrom": "1h",
+      "timeShift": null,
+      "title": "",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 4,
+        "w": 3,
+        "x": 16,
+        "y": 5
+      },
+      "id": 17,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 4,
+        "w": 5,
+        "x": 19,
+        "y": 5
+      },
+      "id": 19,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": []
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,81"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "",
+      "type": "gauge"
+    },
+    {
+      "collapsed": false,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 9
+      },
+      "id": 15,
+      "panels": [],
+      "title": "Value Mappings",
+      "type": "row"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 4,
+        "x": 0,
+        "y": 10
+      },
+      "id": 12,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": [
+          {
+            "from": "",
+            "id": 1,
+            "operator": "",
+            "text": "TEN",
+            "to": "",
+            "type": 1,
+            "value": "10"
+          }
+        ]
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "value mapping 10 -> TEN",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "description": "should read N/A",
+      "gridPos": {
+        "h": 8,
+        "w": 4,
+        "x": 4,
+        "y": 10
+      },
+      "id": 13,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": [
+          {
+            "from": "",
+            "id": 1,
+            "operator": "",
+            "text": "N/A",
+            "to": "",
+            "type": 1,
+            "value": "null"
+          }
+        ]
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,null,null,null,null"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "value mapping null -> N/A",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "description": "should read N/A",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 8,
+        "y": 10
+      },
+      "id": 20,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": [
+          {
+            "from": "0",
+            "id": 1,
+            "operator": "",
+            "text": "OK",
+            "to": "10",
+            "type": 2,
+            "value": "null"
+          }
+        ]
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,null,null,null,null,10"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "value mapping range, 0-10 -> OK, value 10",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "description": "should read N/A",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 14,
+        "y": 10
+      },
+      "id": 21,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "current",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "none",
+        "valueMappings": [
+          {
+            "from": "0",
+            "id": 1,
+            "operator": "",
+            "text": "OK",
+            "to": "90",
+            "type": 2,
+            "value": "null"
+          },
+          {
+            "from": "90",
+            "id": 2,
+            "operator": "",
+            "text": "BAD",
+            "to": "100",
+            "type": 2,
+            "value": ""
+          }
+        ]
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,10,null,null,null,null,10,95"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "value mapping range, 90-100 -> BAD, value 90",
+      "type": "gauge"
+    },
+    {
+      "collapsed": false,
+      "gridPos": {
+        "h": 1,
+        "w": 24,
+        "x": 0,
+        "y": 18
+      },
+      "id": 9,
+      "panels": [],
+      "title": "Templating & Repeat",
+      "type": "row"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 0,
+        "y": 19
+      },
+      "id": 7,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "$Servers",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "repeat": "Servers",
+      "repeatDirection": "h",
+      "scopedVars": {
+        "Servers": {
+          "selected": false,
+          "text": "server1",
+          "value": "server1"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "repeat $Servers",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 6,
+        "y": 19
+      },
+      "id": 22,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "$Servers",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "repeat": null,
+      "repeatDirection": "h",
+      "repeatIteration": 1547810606599,
+      "repeatPanelId": 7,
+      "scopedVars": {
+        "Servers": {
+          "selected": false,
+          "text": "server2",
+          "value": "server2"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "repeat $Servers",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 12,
+        "y": 19
+      },
+      "id": 23,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "$Servers",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "repeat": null,
+      "repeatDirection": "h",
+      "repeatIteration": 1547810606599,
+      "repeatPanelId": 7,
+      "scopedVars": {
+        "Servers": {
+          "selected": false,
+          "text": "server3",
+          "value": "server3"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "repeat $Servers",
+      "type": "gauge"
+    },
+    {
+      "datasource": "gdev-testdata",
+      "gridPos": {
+        "h": 8,
+        "w": 6,
+        "x": 18,
+        "y": 19
+      },
+      "id": 24,
+      "links": [],
+      "nullPointMode": "null",
+      "options-gauge": {
+        "baseColor": "#299c46",
+        "decimals": "2",
+        "maxValue": 100,
+        "minValue": 0,
+        "options": {
+          "baseColor": "#299c46",
+          "decimals": 0,
+          "maxValue": 100,
+          "minValue": 0,
+          "prefix": "",
+          "showThresholdLabels": false,
+          "showThresholdMarkers": true,
+          "stat": "avg",
+          "suffix": "",
+          "thresholds": [],
+          "unit": "none",
+          "valueMappings": []
+        },
+        "prefix": "$Servers",
+        "showThresholdLabels": false,
+        "showThresholdMarkers": true,
+        "stat": "avg",
+        "suffix": "",
+        "thresholds": [
+          {
+            "color": "#e24d42",
+            "index": 2,
+            "value": 90
+          },
+          {
+            "color": "#ef843c",
+            "index": 1,
+            "value": 75
+          },
+          {
+            "color": "#7EB26D",
+            "index": 0,
+            "value": null
+          }
+        ],
+        "unit": "ms",
+        "valueMappings": []
+      },
+      "repeat": null,
+      "repeatDirection": "h",
+      "repeatIteration": 1547810606599,
+      "repeatPanelId": 7,
+      "scopedVars": {
+        "Servers": {
+          "selected": false,
+          "text": "server4",
+          "value": "server4"
+        }
+      },
+      "targets": [
+        {
+          "refId": "A",
+          "scenarioId": "csv_metric_values",
+          "stringInput": "1,20,90,30,5,0"
+        }
+      ],
+      "timeFrom": null,
+      "timeShift": null,
+      "title": "repeat $Servers",
+      "type": "gauge"
+    }
+  ],
+  "refresh": false,
+  "schemaVersion": 17,
+  "style": "dark",
+  "tags": [
+    "gdev",
+    "panel-tests"
+  ],
+  "templating": {
+    "list": [
+      {
+        "allValue": null,
+        "current": {
+          "selected": true,
+          "tags": [],
+          "text": "All",
+          "value": [
+            "$__all"
+          ]
+        },
+        "hide": 0,
+        "includeAll": true,
+        "label": null,
+        "multi": true,
+        "name": "Servers",
+        "options": [
+          {
+            "selected": true,
+            "text": "All",
+            "value": "$__all"
+          },
+          {
+            "selected": false,
+            "text": "server1",
+            "value": "server1"
+          },
+          {
+            "selected": false,
+            "text": "server2",
+            "value": "server2"
+          },
+          {
+            "selected": false,
+            "text": "server3",
+            "value": "server3"
+          },
+          {
+            "selected": false,
+            "text": "server4",
+            "value": "server4"
+          }
+        ],
+        "query": "server1,server2,server3,server4",
+        "skipUrlSync": false,
+        "type": "custom"
+      }
+    ]
+  },
+  "time": {
+    "from": "now-1h",
+    "to": "now"
+  },
+  "timepicker": {
+    "refresh_intervals": [
+      "5s",
+      "10s",
+      "30s",
+      "1m",
+      "5m",
+      "15m",
+      "30m",
+      "1h",
+      "2h",
+      "1d"
+    ],
+    "time_options": [
+      "5m",
+      "15m",
+      "1h",
+      "6h",
+      "12h",
+      "24h",
+      "2d",
+      "7d",
+      "30d"
+    ]
+  },
+  "timezone": "",
+  "title": "Panel Tests - Gauge",
+  "uid": "_5rDmaQiz",
+  "version": 5
+}

+ 224 - 0
packages/grafana-ui/src/components/Gauge/Gauge.test.tsx

@@ -0,0 +1,224 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { Gauge, Props } from './Gauge';
+import { TimeSeriesVMs } from '../../types/series';
+import { ValueMapping, MappingType } from '../../types';
+
+jest.mock('jquery', () => ({
+  plot: jest.fn(),
+}));
+
+const setup = (propOverrides?: object) => {
+  const props: Props = {
+    maxValue: 100,
+    valueMappings: [],
+    minValue: 0,
+    prefix: '',
+    showThresholdMarkers: true,
+    showThresholdLabels: false,
+    suffix: '',
+    thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }],
+    unit: 'none',
+    stat: 'avg',
+    height: 300,
+    width: 300,
+    timeSeries: {} as TimeSeriesVMs,
+    decimals: 0,
+  };
+
+  Object.assign(props, propOverrides);
+
+  const wrapper = shallow(<Gauge {...props} />);
+  const instance = wrapper.instance() as Gauge;
+
+  return {
+    instance,
+    wrapper,
+  };
+};
+
+describe('Get font color', () => {
+  it('should get first threshold color when only one threshold', () => {
+    const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] });
+
+    expect(instance.getFontColor(49)).toEqual('#7EB26D');
+  });
+
+  it('should get the threshold color if value is same as a threshold', () => {
+    const { instance } = setup({
+      thresholds: [
+        { index: 2, value: 75, color: '#6ED0E0' },
+        { index: 1, value: 50, color: '#EAB839' },
+        { index: 0, value: -Infinity, color: '#7EB26D' },
+      ],
+    });
+
+    expect(instance.getFontColor(50)).toEqual('#EAB839');
+  });
+
+  it('should get the nearest threshold color between thresholds', () => {
+    const { instance } = setup({
+      thresholds: [
+        { index: 2, value: 75, color: '#6ED0E0' },
+        { index: 1, value: 50, color: '#EAB839' },
+        { index: 0, value: -Infinity, color: '#7EB26D' },
+      ],
+    });
+
+    expect(instance.getFontColor(55)).toEqual('#EAB839');
+  });
+});
+
+describe('Get thresholds formatted', () => {
+  it('should return first thresholds color for min and max', () => {
+    const { instance } = setup({ thresholds: [{ index: 0, value: -Infinity, color: '#7EB26D' }] });
+
+    expect(instance.getFormattedThresholds()).toEqual([
+      { value: 0, color: '#7EB26D' },
+      { value: 100, color: '#7EB26D' },
+    ]);
+  });
+
+  it('should get the correct formatted values when thresholds are added', () => {
+    const { instance } = setup({
+      thresholds: [
+        { index: 2, value: 75, color: '#6ED0E0' },
+        { index: 1, value: 50, color: '#EAB839' },
+        { index: 0, value: -Infinity, color: '#7EB26D' },
+      ],
+    });
+
+    expect(instance.getFormattedThresholds()).toEqual([
+      { value: 0, color: '#7EB26D' },
+      { value: 50, color: '#7EB26D' },
+      { value: 75, color: '#EAB839' },
+      { value: 100, color: '#6ED0E0' },
+    ]);
+  });
+});
+
+describe('Format value with value mappings', () => {
+  it('should return undefined with no valuemappings', () => {
+    const valueMappings: ValueMapping[] = [];
+    const value = '10';
+    const { instance } = setup({ valueMappings });
+
+    const result = instance.getFirstFormattedValueMapping(valueMappings, value);
+
+    expect(result).toBeUndefined();
+  });
+
+  it('should return undefined with no matching valuemappings', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+      { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
+    ];
+    const value = '10';
+    const { instance } = setup({ valueMappings });
+
+    const result = instance.getFirstFormattedValueMapping(valueMappings, value);
+
+    expect(result).toBeUndefined();
+  });
+
+  it('should return first matching mapping with lowest id', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
+      { id: 1, operator: '', text: 'tio', type: MappingType.ValueToText, value: '10' },
+    ];
+    const value = '10';
+    const { instance } = setup({ valueMappings });
+
+    const result = instance.getFirstFormattedValueMapping(valueMappings, value);
+
+    expect(result.text).toEqual('1-20');
+  });
+
+  it('should return rangeToText mapping where value equals to', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: '1-10', type: MappingType.RangeToText, from: '1', to: '10' },
+      { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+    ];
+    const value = '10';
+    const { instance } = setup({ valueMappings });
+
+    const result = instance.getFirstFormattedValueMapping(valueMappings, value);
+
+    expect(result.text).toEqual('1-10');
+  });
+
+  it('should return rangeToText mapping where value equals from', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: '10-20', type: MappingType.RangeToText, from: '10', to: '20' },
+      { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+    ];
+    const value = '10';
+    const { instance } = setup({ valueMappings });
+
+    const result = instance.getFirstFormattedValueMapping(valueMappings, value);
+
+    expect(result.text).toEqual('10-20');
+  });
+
+  it('should return rangeToText mapping where value is between from and to', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
+      { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+    ];
+    const value = '10';
+    const { instance } = setup({ valueMappings });
+
+    const result = instance.getFirstFormattedValueMapping(valueMappings, value);
+
+    expect(result.text).toEqual('1-20');
+  });
+});
+
+describe('Format value', () => {
+  it('should return if value isNaN', () => {
+    const valueMappings: ValueMapping[] = [];
+    const value = 'N/A';
+    const { instance } = setup({ valueMappings });
+
+    const result = instance.formatValue(value);
+
+    expect(result).toEqual('N/A');
+  });
+
+  it('should return formatted value if there are no value mappings', () => {
+    const valueMappings: ValueMapping[] = [];
+    const value = '6';
+    const { instance } = setup({ valueMappings, decimals: 1 });
+
+    const result = instance.formatValue(value);
+
+    expect(result).toEqual(' 6.0 ');
+  });
+
+  it('should return formatted value if there are no matching value mappings', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+      { id: 1, operator: '', text: '1-9', type: MappingType.RangeToText, from: '1', to: '9' },
+    ];
+    const value = '10';
+    const { instance } = setup({ valueMappings, decimals: 1 });
+
+    const result = instance.formatValue(value);
+
+    expect(result).toEqual(' 10.0 ');
+  });
+
+  it('should return mapped value if there are matching value mappings', () => {
+    const valueMappings: ValueMapping[] = [
+      { id: 0, operator: '', text: '1-20', type: MappingType.RangeToText, from: '1', to: '20' },
+      { id: 1, operator: '', text: 'elva', type: MappingType.ValueToText, value: '11' },
+    ];
+    const value = '11';
+    const { instance } = setup({ valueMappings, decimals: 1 });
+
+    const result = instance.formatValue(value);
+
+    expect(result).toEqual(' 1-20 ');
+  });
+});

+ 284 - 0
packages/grafana-ui/src/components/Gauge/Gauge.tsx

@@ -0,0 +1,284 @@
+import React, { PureComponent } from 'react';
+import $ from 'jquery';
+
+import {
+  ValueMapping,
+  Threshold,
+  ThemeName,
+  MappingType,
+  BasicGaugeColor,
+  ThemeNames,
+  ValueMap,
+  RangeMap,
+} from '../../types/panel';
+import { TimeSeriesVMs } from '../../types/series';
+import { getValueFormat } from '../../utils/valueFormats/valueFormats';
+
+type TimeSeriesValue = string | number | null;
+
+export interface Props {
+  decimals: number;
+  height: number;
+  valueMappings: ValueMapping[];
+  maxValue: number;
+  minValue: number;
+  prefix: string;
+  timeSeries: TimeSeriesVMs;
+  thresholds: Threshold[];
+  showThresholdMarkers: boolean;
+  showThresholdLabels: boolean;
+  stat: string;
+  suffix: string;
+  unit: string;
+  width: number;
+  theme?: ThemeName;
+}
+
+export class Gauge extends PureComponent<Props> {
+  canvasElement: any;
+
+  static defaultProps = {
+    maxValue: 100,
+    valueMappings: [],
+    minValue: 0,
+    prefix: '',
+    showThresholdMarkers: true,
+    showThresholdLabels: false,
+    suffix: '',
+    thresholds: [],
+    unit: 'none',
+    stat: 'avg',
+    theme: ThemeNames.Dark,
+  };
+
+  componentDidMount() {
+    this.draw();
+  }
+
+  componentDidUpdate() {
+    this.draw();
+  }
+
+  addValueToTextMappingText(allValueMappings: ValueMapping[], valueToTextMapping: ValueMap, value: TimeSeriesValue) {
+    if (!valueToTextMapping.value) {
+      return allValueMappings;
+    }
+
+    const valueAsNumber = parseFloat(value as string);
+    const valueToTextMappingAsNumber = parseFloat(valueToTextMapping.value as string);
+
+    if (isNaN(valueAsNumber) || isNaN(valueToTextMappingAsNumber)) {
+      return allValueMappings;
+    }
+
+    if (valueAsNumber !== valueToTextMappingAsNumber) {
+      return allValueMappings;
+    }
+
+    return allValueMappings.concat(valueToTextMapping);
+  }
+
+  addRangeToTextMappingText(allValueMappings: ValueMapping[], rangeToTextMapping: RangeMap, value: TimeSeriesValue) {
+    if (!rangeToTextMapping.from || !rangeToTextMapping.to || !value) {
+      return allValueMappings;
+    }
+
+    const valueAsNumber = parseFloat(value as string);
+    const fromAsNumber = parseFloat(rangeToTextMapping.from as string);
+    const toAsNumber = parseFloat(rangeToTextMapping.to as string);
+
+    if (isNaN(valueAsNumber) || isNaN(fromAsNumber) || isNaN(toAsNumber)) {
+      return allValueMappings;
+    }
+
+    if (valueAsNumber >= fromAsNumber && valueAsNumber <= toAsNumber) {
+      return allValueMappings.concat(rangeToTextMapping);
+    }
+
+    return allValueMappings;
+  }
+
+  getAllFormattedValueMappings(valueMappings: ValueMapping[], value: TimeSeriesValue) {
+    const allFormattedValueMappings = valueMappings.reduce(
+      (allValueMappings, valueMapping) => {
+        if (valueMapping.type === MappingType.ValueToText) {
+          allValueMappings = this.addValueToTextMappingText(allValueMappings, valueMapping as ValueMap, value);
+        } else if (valueMapping.type === MappingType.RangeToText) {
+          allValueMappings = this.addRangeToTextMappingText(allValueMappings, valueMapping as RangeMap, value);
+        }
+
+        return allValueMappings;
+      },
+      [] as ValueMapping[]
+    );
+
+    allFormattedValueMappings.sort((t1, t2) => {
+      return t1.id - t2.id;
+    });
+
+    return allFormattedValueMappings;
+  }
+
+  getFirstFormattedValueMapping(valueMappings: ValueMapping[], value: TimeSeriesValue) {
+    return this.getAllFormattedValueMappings(valueMappings, value)[0];
+  }
+
+  formatValue(value: TimeSeriesValue) {
+    const { decimals, valueMappings, prefix, suffix, unit } = this.props;
+
+    if (isNaN(value as number)) {
+      return value;
+    }
+
+    if (valueMappings.length > 0) {
+      const valueMappedValue = this.getFirstFormattedValueMapping(valueMappings, value);
+      if (valueMappedValue) {
+        return `${prefix} ${valueMappedValue.text} ${suffix}`;
+      }
+    }
+
+    const formatFunc = getValueFormat(unit);
+    const formattedValue = formatFunc(value as number, decimals);
+
+    return `${prefix} ${formattedValue} ${suffix}`;
+  }
+
+  getFontColor(value: TimeSeriesValue) {
+    const { thresholds } = this.props;
+
+    if (thresholds.length === 1) {
+      return thresholds[0].color;
+    }
+
+    const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0];
+    if (atThreshold) {
+      return atThreshold.color;
+    }
+
+    const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value);
+
+    if (belowThreshold.length > 0) {
+      const nearestThreshold = belowThreshold.sort((t1, t2) => t2.value - t1.value)[0];
+      return nearestThreshold.color;
+    }
+
+    return BasicGaugeColor.Red;
+  }
+
+  getFormattedThresholds() {
+    const { maxValue, minValue, thresholds } = this.props;
+
+    const thresholdsSortedByIndex = [...thresholds].sort((t1, t2) => t1.index - t2.index);
+    const lastThreshold = thresholdsSortedByIndex[thresholdsSortedByIndex.length - 1];
+
+    const formattedThresholds = [
+      ...thresholdsSortedByIndex.map(threshold => {
+        if (threshold.index === 0) {
+          return { value: minValue, color: threshold.color };
+        }
+
+        const previousThreshold = thresholdsSortedByIndex[threshold.index - 1];
+        return { value: threshold.value, color: previousThreshold.color };
+      }),
+      { value: maxValue, color: lastThreshold.color },
+    ];
+
+    return formattedThresholds;
+  }
+
+  draw() {
+    const {
+      maxValue,
+      minValue,
+      timeSeries,
+      showThresholdLabels,
+      showThresholdMarkers,
+      width,
+      height,
+      stat,
+      theme,
+    } = this.props;
+
+    let value: TimeSeriesValue = '';
+
+    if (timeSeries[0]) {
+      value = timeSeries[0].stats[stat];
+    } else {
+      value = 'N/A';
+    }
+
+    const dimension = Math.min(width, height * 1.3);
+    const backgroundColor = theme === ThemeNames.Light ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
+    const fontScale = parseInt('80', 10) / 100;
+    const fontSize = Math.min(dimension / 5, 100) * fontScale;
+    const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
+    const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio;
+    const thresholdMarkersWidth = gaugeWidth / 5;
+    const thresholdLabelFontSize = fontSize / 2.5;
+
+    const options = {
+      series: {
+        gauges: {
+          gauge: {
+            min: minValue,
+            max: maxValue,
+            background: { color: backgroundColor },
+            border: { color: null },
+            shadow: { show: false },
+            width: gaugeWidth,
+          },
+          frame: { show: false },
+          label: { show: false },
+          layout: { margin: 0, thresholdWidth: 0 },
+          cell: { border: { width: 0 } },
+          threshold: {
+            values: this.getFormattedThresholds(),
+            label: {
+              show: showThresholdLabels,
+              margin: thresholdMarkersWidth + 1,
+              font: { size: thresholdLabelFontSize },
+            },
+            show: showThresholdMarkers,
+            width: thresholdMarkersWidth,
+          },
+          value: {
+            color: this.getFontColor(value),
+            formatter: () => {
+              return this.formatValue(value);
+            },
+            font: { size: fontSize, family: '"Helvetica Neue", Helvetica, Arial, sans-serif' },
+          },
+          show: true,
+        },
+      },
+    };
+
+    const plotSeries = { data: [[0, value]] };
+
+    try {
+      $.plot(this.canvasElement, [plotSeries], options);
+    } catch (err) {
+      console.log('Gauge rendering error', err, options, timeSeries);
+    }
+  }
+
+  render() {
+    const { height, width } = this.props;
+
+    return (
+      <div className="singlestat-panel">
+        <div
+          style={{
+            height: `${height * 0.9}px`,
+            width: `${Math.min(width, height * 1.3)}px`,
+            top: '10px',
+            margin: 'auto',
+          }}
+          ref={element => (this.canvasElement = element)}
+        />
+      </div>
+    );
+  }
+}
+
+export default Gauge;

+ 14 - 9
packages/grafana-ui/src/components/ThresholdsEditor/ThresholdsEditor.tsx

@@ -19,9 +19,15 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
   constructor(props: Props) {
     super(props);
 
-    const thresholds: Threshold[] =
-      props.thresholds.length > 0 ? props.thresholds : [{ index: 0, value: -Infinity, color: colors[0] }];
+    const addDefaultThreshold = this.props.thresholds.length === 0;
+    const thresholds: Threshold[] = addDefaultThreshold
+      ? [{ index: 0, value: -Infinity, color: colors[0] }]
+      : props.thresholds;
     this.state = { thresholds };
+
+    if (addDefaultThreshold) {
+      this.onChange();
+    }
   }
 
   onAddThreshold = (index: number) => {
@@ -62,7 +68,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
           },
         ]),
       },
-      () => this.updateGauge()
+      () => this.onChange()
     );
   };
 
@@ -85,7 +91,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
           thresholds: newThresholds.filter(t => t !== threshold),
         };
       },
-      () => this.updateGauge()
+      () => this.onChange()
     );
   };
 
@@ -99,7 +105,7 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
     const value = isNaN(parsedValue) ? null : parsedValue;
 
     const newThresholds = thresholds.map(t => {
-      if (t === threshold) {
+      if (t === threshold && t.index !== 0) {
         t = { ...t, value: value as number };
       }
 
@@ -124,11 +130,10 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
       {
         thresholds: newThresholds,
       },
-      () => this.updateGauge()
+      () => this.onChange()
     );
   };
 
-  onChangeBaseColor = (color: string) => this.props.onChange(this.state.thresholds);
   onBlur = () => {
     this.setState(prevState => {
       const sortThresholds = this.sortThresholds([...prevState.thresholds]);
@@ -139,10 +144,10 @@ export class ThresholdsEditor extends PureComponent<Props, State> {
       return { thresholds: sortThresholds };
     });
 
-    this.updateGauge();
+    this.onChange();
   };
 
-  updateGauge = () => {
+  onChange = () => {
     this.props.onChange(this.state.thresholds);
   };
 

+ 1 - 0
packages/grafana-ui/src/components/index.ts

@@ -22,3 +22,4 @@ export { Graph } from './Graph/Graph';
 export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
 export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
 export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor';
+export { Gauge } from './Gauge/Gauge';

+ 7 - 0
packages/grafana-ui/src/types/panel.ts

@@ -66,3 +66,10 @@ export interface RangeMap extends BaseMap {
   from: string;
   to: string;
 }
+
+export type ThemeName = 'dark' | 'light';
+
+export enum ThemeNames {
+  Dark = 'dark',
+  Light = 'light',
+}

+ 3 - 2
packages/grafana-ui/src/types/series.ts

@@ -21,9 +21,12 @@ export interface TimeSeriesVM {
   color: string;
   data: TimeSeriesValue[][];
   stats: TimeSeriesStats;
+  allIsNull: boolean;
+  allIsZero: boolean;
 }
 
 export interface TimeSeriesStats {
+  [key: string]: number | null;
   total: number | null;
   max: number | null;
   min: number | null;
@@ -36,8 +39,6 @@ export interface TimeSeriesStats {
   range: number | null;
   timeStep: number;
   count: number;
-  allIsNull: boolean;
-  allIsZero: boolean;
 }
 
 export enum NullValueMode {

+ 9 - 8
packages/grafana-ui/src/utils/processTimeSeries.ts

@@ -1,18 +1,19 @@
 // Libraries
 import _ from 'lodash';
 
+import { colors } from './colors';
+
 // Types
 import { TimeSeries, TimeSeriesVMs, NullValueMode, TimeSeriesValue } from '../types';
 
 interface Options {
   timeSeries: TimeSeries[];
   nullValueMode: NullValueMode;
-  colorPalette: string[];
 }
 
-export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: Options): TimeSeriesVMs {
+export function processTimeSeries({ timeSeries, nullValueMode }: Options): TimeSeriesVMs {
   const vmSeries = timeSeries.map((item, index) => {
-    const colorIndex = index % colorPalette.length;
+    const colorIndex = index % colors.length;
     const label = item.target;
     const result = [];
 
@@ -49,8 +50,8 @@ export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: O
         continue;
       }
 
-      if (typeof currentValue !== 'number') {
-        continue;
+      if (currentValue !== null && typeof currentValue !== 'number') {
+        throw {message: 'Time series contains non number values'};
       }
 
       // Due to missing values we could have different timeStep all along the series
@@ -150,7 +151,9 @@ export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: O
     return {
       data: result,
       label: label,
-      color: colorPalette[colorIndex],
+      color: colors[colorIndex],
+      allIsZero,
+      allIsNull,
       stats: {
         total,
         min,
@@ -164,8 +167,6 @@ export function processTimeSeries({ timeSeries, nullValueMode, colorPalette }: O
         range,
         count,
         first,
-        allIsZero,
-        allIsNull,
       },
     };
   });

+ 5 - 0
public/app/core/services/context_srv.ts

@@ -2,6 +2,7 @@ import config from 'app/core/config';
 import _ from 'lodash';
 import coreModule from 'app/core/core_module';
 import store from 'app/core/store';
+import { ThemeNames, ThemeName } from '@grafana/ui';
 
 export class User {
   isGrafanaAdmin: any;
@@ -59,6 +60,10 @@ export class ContextSrv {
     this.sidemenu = !this.sidemenu;
     store.set('grafana.sidemenu', this.sidemenu);
   }
+
+  getTheme(): ThemeName {
+    return this.user.lightTheme ? ThemeNames.Light : ThemeNames.Dark;
+  }
 }
 
 const contextSrv = new ContextSrv();

+ 0 - 1
public/app/features/dashboard/panel_model.ts

@@ -55,7 +55,6 @@ const mustKeepProps: { [str: string]: boolean } = {
   hasRefreshed: true,
   events: true,
   cacheTimeout: true,
-  nullPointMode: true,
   cachedPluginOptions: true,
   transparent: true,
 };

+ 14 - 5
public/app/plugins/panel/gauge/GaugePanel.tsx

@@ -1,22 +1,30 @@
+// Libraries
 import React, { PureComponent } from 'react';
-import { PanelProps, NullValueMode } from '@grafana/ui';
 
-import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
-import Gauge from 'app/viz/Gauge';
+// Services & Utils
+import { contextSrv } from 'app/core/core';
+import { processTimeSeries } from '@grafana/ui';
+
+// Components
+import { Gauge } from '@grafana/ui';
+
+// Types
 import { GaugeOptions } from './types';
+import { PanelProps, NullValueMode } from '@grafana/ui/src/types';
 
 interface Props extends PanelProps<GaugeOptions> {}
 
 export class GaugePanel extends PureComponent<Props> {
+
   render() {
     const { timeSeries, width, height, onInterpolate, options } = this.props;
 
     const prefix = onInterpolate(options.prefix);
     const suffix = onInterpolate(options.suffix);
 
-    const vmSeries = getTimeSeriesVMs({
+    const vmSeries = processTimeSeries({
       timeSeries: timeSeries,
-      nullValueMode: NullValueMode.Ignore,
+      nullValueMode: NullValueMode.Null,
     });
 
     return (
@@ -27,6 +35,7 @@ export class GaugePanel extends PureComponent<Props> {
         height={height}
         prefix={prefix}
         suffix={suffix}
+        theme={contextSrv.getTheme()}
       />
     );
   }

+ 0 - 2
public/app/plugins/panel/gauge/GaugePanelOptions.tsx

@@ -1,6 +1,5 @@
 import React, { PureComponent } from 'react';
 import {
-  BasicGaugeColor,
   PanelOptionsProps,
   ThresholdsEditor,
   Threshold,
@@ -15,7 +14,6 @@ import { GaugeOptions } from './types';
 
 export const defaultProps = {
   options: {
-    baseColor: BasicGaugeColor.Green,
     minValue: 0,
     maxValue: 100,
     prefix: '',

+ 0 - 1
public/app/plugins/panel/gauge/types.ts

@@ -1,7 +1,6 @@
 import { Threshold, ValueMapping } from '@grafana/ui';
 
 export interface GaugeOptions {
-  baseColor: string;
   decimals: number;
   valueMappings: ValueMapping[];
   maxValue: number;

+ 0 - 2
public/app/plugins/panel/graph2/GraphPanel.tsx

@@ -1,7 +1,6 @@
 // Libraries
 import _ from 'lodash';
 import React, { PureComponent } from 'react';
-import { colors } from '@grafana/ui';
 
 // Utils
 import { processTimeSeries } from '@grafana/ui/src/utils';
@@ -23,7 +22,6 @@ export class GraphPanel extends PureComponent<Props> {
     const vmSeries = processTimeSeries({
       timeSeries: timeSeries,
       nullValueMode: NullValueMode.Ignore,
-      colorPalette: colors,
     });
 
     return (

+ 0 - 55
public/app/viz/Gauge.test.tsx

@@ -1,55 +0,0 @@
-import React from 'react';
-import { shallow } from 'enzyme';
-import { BasicGaugeColor, TimeSeriesVMs } from '@grafana/ui';
-
-import { Gauge, Props } from './Gauge';
-
-jest.mock('jquery', () => ({
-  plot: jest.fn(),
-}));
-
-const setup = (propOverrides?: object) => {
-  const props: Props = {
-    baseColor: BasicGaugeColor.Green,
-    maxValue: 100,
-    valueMappings: [],
-    minValue: 0,
-    prefix: '',
-    showThresholdMarkers: true,
-    showThresholdLabels: false,
-    suffix: '',
-    thresholds: [],
-    unit: 'none',
-    stat: 'avg',
-    height: 300,
-    width: 300,
-    timeSeries: {} as TimeSeriesVMs,
-    decimals: 0,
-  };
-
-  Object.assign(props, propOverrides);
-
-  const wrapper = shallow(<Gauge {...props} />);
-  const instance = wrapper.instance() as Gauge;
-
-  return {
-    instance,
-    wrapper,
-  };
-};
-
-describe('Get font color', () => {
-  it('should get base color if no threshold', () => {
-    const { instance } = setup();
-
-    expect(instance.getFontColor(40)).toEqual(BasicGaugeColor.Green);
-  });
-
-  it('should be f2f2f2', () => {
-    const { instance } = setup({
-      thresholds: [{ value: 59, color: '#f2f2f2' }],
-    });
-
-    expect(instance.getFontColor(58)).toEqual('#f2f2f2');
-  });
-});

+ 0 - 216
public/app/viz/Gauge.tsx

@@ -1,216 +0,0 @@
-import React, { PureComponent } from 'react';
-import $ from 'jquery';
-import { BasicGaugeColor, Threshold, TimeSeriesVMs, MappingType, ValueMapping } from '@grafana/ui';
-
-import config from '../core/config';
-import kbn from '../core/utils/kbn';
-
-export interface Props {
-  baseColor: string;
-  decimals: number;
-  height: number;
-  valueMappings: ValueMapping[];
-  maxValue: number;
-  minValue: number;
-  prefix: string;
-  timeSeries: TimeSeriesVMs;
-  thresholds: Threshold[];
-  showThresholdMarkers: boolean;
-  showThresholdLabels: boolean;
-  stat: string;
-  suffix: string;
-  unit: string;
-  width: number;
-}
-
-export class Gauge extends PureComponent<Props> {
-  canvasElement: any;
-
-  static defaultProps = {
-    baseColor: BasicGaugeColor.Green,
-    maxValue: 100,
-    valueMappings: [],
-    minValue: 0,
-    prefix: '',
-    showThresholdMarkers: true,
-    showThresholdLabels: false,
-    suffix: '',
-    thresholds: [],
-    unit: 'none',
-    stat: 'avg',
-  };
-
-  componentDidMount() {
-    this.draw();
-  }
-
-  componentDidUpdate() {
-    this.draw();
-  }
-
-  formatWithMappings(mappings, value) {
-    const valueMaps = mappings.filter(m => m.type === MappingType.ValueToText);
-    const rangeMaps = mappings.filter(m => m.type === MappingType.RangeToText);
-
-    const valueMap = valueMaps.map(mapping => {
-      if (mapping.value && value === mapping.value) {
-        return mapping.text;
-      }
-    })[0];
-
-    const rangeMap = rangeMaps.map(mapping => {
-      if (mapping.from && mapping.to && value > mapping.from && value < mapping.to) {
-        return mapping.text;
-      }
-    })[0];
-
-    return { rangeMap, valueMap };
-  }
-
-  formatValue(value) {
-    const { decimals, valueMappings, prefix, suffix, unit } = this.props;
-
-    const formatFunc = kbn.valueFormats[unit];
-    const formattedValue = formatFunc(value, decimals);
-
-    if (valueMappings.length > 0) {
-      const { rangeMap, valueMap } = this.formatWithMappings(valueMappings, formattedValue);
-
-      if (valueMap) {
-        return `${prefix} ${valueMap} ${suffix}`;
-      } else if (rangeMap) {
-        return `${prefix} ${rangeMap} ${suffix}`;
-      }
-    }
-
-    if (isNaN(value)) {
-      return '-';
-    }
-
-    return `${prefix} ${formattedValue} ${suffix}`;
-  }
-
-  getFontColor(value) {
-    const { baseColor, maxValue, thresholds } = this.props;
-
-    if (thresholds.length > 0) {
-      const atThreshold = thresholds.filter(threshold => value <= threshold.value);
-
-      if (atThreshold.length > 0) {
-        return atThreshold[0].color;
-      } else if (value <= maxValue) {
-        return BasicGaugeColor.Red;
-      }
-    }
-
-    return baseColor;
-  }
-
-  draw() {
-    const {
-      baseColor,
-      maxValue,
-      minValue,
-      timeSeries,
-      showThresholdLabels,
-      showThresholdMarkers,
-      thresholds,
-      width,
-      height,
-      stat,
-    } = this.props;
-
-    let value: string | number = '';
-
-    if (timeSeries[0]) {
-      value = timeSeries[0].stats[stat];
-    } else {
-      value = 'N/A';
-    }
-
-    const dimension = Math.min(width, height * 1.3);
-    const backgroundColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
-    const fontScale = parseInt('80', 10) / 100;
-    const fontSize = Math.min(dimension / 5, 100) * fontScale;
-    const gaugeWidthReduceRatio = showThresholdLabels ? 1.5 : 1;
-    const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio;
-    const thresholdMarkersWidth = gaugeWidth / 5;
-    const thresholdLabelFontSize = fontSize / 2.5;
-
-    const formattedThresholds = [
-      { value: minValue, color: BasicGaugeColor.Green },
-      ...thresholds.map((threshold, index) => {
-        return {
-          value: threshold.value,
-          color: index === 0 ? threshold.color : thresholds[index].color,
-        };
-      }),
-      { value: maxValue, color: thresholds.length > 0 ? BasicGaugeColor.Red : baseColor },
-    ];
-
-    const options = {
-      series: {
-        gauges: {
-          gauge: {
-            min: minValue,
-            max: maxValue,
-            background: { color: backgroundColor },
-            border: { color: null },
-            shadow: { show: false },
-            width: gaugeWidth,
-          },
-          frame: { show: false },
-          label: { show: false },
-          layout: { margin: 0, thresholdWidth: 0 },
-          cell: { border: { width: 0 } },
-          threshold: {
-            values: formattedThresholds,
-            label: {
-              show: showThresholdLabels,
-              margin: thresholdMarkersWidth + 1,
-              font: { size: thresholdLabelFontSize },
-            },
-            show: showThresholdMarkers,
-            width: thresholdMarkersWidth,
-          },
-          value: {
-            color: this.getFontColor(value),
-            formatter: () => {
-              return this.formatValue(value);
-            },
-            font: { size: fontSize, family: '"Helvetica Neue", Helvetica, Arial, sans-serif' },
-          },
-          show: true,
-        },
-      },
-    };
-
-    const plotSeries = { data: [[0, value]] };
-
-    try {
-      $.plot(this.canvasElement, [plotSeries], options);
-    } catch (err) {
-      console.log('Gauge rendering error', err, options, timeSeries);
-    }
-  }
-
-  render() {
-    const { height, width } = this.props;
-
-    return (
-      <div className="singlestat-panel">
-        <div
-          style={{
-            height: `${height * 0.9}px`,
-            width: `${Math.min(width, height * 1.3)}px`,
-            top: '10px',
-            margin: 'auto',
-          }}
-          ref={element => (this.canvasElement = element)}
-        />
-      </div>
-    );
-  }
-}
-
-export default Gauge;

+ 0 - 168
public/app/viz/state/timeSeries.ts

@@ -1,168 +0,0 @@
-// Libraries
-import _ from 'lodash';
-
-// Utils
-import { colors } from '@grafana/ui';
-
-// Types
-import { TimeSeries, TimeSeriesVMs, NullValueMode } from '@grafana/ui';
-
-interface Options {
-  timeSeries: TimeSeries[];
-  nullValueMode: NullValueMode;
-}
-
-export function getTimeSeriesVMs({ timeSeries, nullValueMode }: Options): TimeSeriesVMs {
-  const vmSeries = timeSeries.map((item, index) => {
-    const colorIndex = index % colors.length;
-    const label = item.target;
-    const result = [];
-
-    // stat defaults
-    let total = 0;
-    let max = -Number.MAX_VALUE;
-    let min = Number.MAX_VALUE;
-    let logmin = Number.MAX_VALUE;
-    let avg = null;
-    let current = null;
-    let first = null;
-    let delta = 0;
-    let diff = null;
-    let range = null;
-    let timeStep = Number.MAX_VALUE;
-    let allIsNull = true;
-    let allIsZero = true;
-
-    const ignoreNulls = nullValueMode === NullValueMode.Ignore;
-    const nullAsZero = nullValueMode === NullValueMode.AsZero;
-
-    let currentTime;
-    let currentValue;
-    let nonNulls = 0;
-    let previousTime;
-    let previousValue = 0;
-    let previousDeltaUp = true;
-
-    for (let i = 0; i < item.datapoints.length; i++) {
-      currentValue = item.datapoints[i][0];
-      currentTime = item.datapoints[i][1];
-
-      // Due to missing values we could have different timeStep all along the series
-      // so we have to find the minimum one (could occur with aggregators such as ZimSum)
-      if (previousTime !== undefined) {
-        const currentStep = currentTime - previousTime;
-        if (currentStep < timeStep) {
-          timeStep = currentStep;
-        }
-      }
-
-      previousTime = currentTime;
-
-      if (currentValue === null) {
-        if (ignoreNulls) {
-          continue;
-        }
-        if (nullAsZero) {
-          currentValue = 0;
-        }
-      }
-
-      if (currentValue !== null) {
-        if (_.isNumber(currentValue)) {
-          total += currentValue;
-          allIsNull = false;
-          nonNulls++;
-        }
-
-        if (currentValue > max) {
-          max = currentValue;
-        }
-
-        if (currentValue < min) {
-          min = currentValue;
-        }
-
-        if (first === null) {
-          first = currentValue;
-        } else {
-          if (previousValue > currentValue) {
-            // counter reset
-            previousDeltaUp = false;
-            if (i === item.datapoints.length - 1) {
-              // reset on last
-              delta += currentValue;
-            }
-          } else {
-            if (previousDeltaUp) {
-              delta += currentValue - previousValue; // normal increment
-            } else {
-              delta += currentValue; // account for counter reset
-            }
-            previousDeltaUp = true;
-          }
-        }
-        previousValue = currentValue;
-
-        if (currentValue < logmin && currentValue > 0) {
-          logmin = currentValue;
-        }
-
-        if (currentValue !== 0) {
-          allIsZero = false;
-        }
-      }
-
-      result.push([currentTime, currentValue]);
-    }
-
-    if (max === -Number.MAX_VALUE) {
-      max = null;
-    }
-
-    if (min === Number.MAX_VALUE) {
-      min = null;
-    }
-
-    if (result.length && !allIsNull) {
-      avg = total / nonNulls;
-      current = result[result.length - 1][1];
-      if (current === null && result.length > 1) {
-        current = result[result.length - 2][1];
-      }
-    }
-
-    if (max !== null && min !== null) {
-      range = max - min;
-    }
-
-    if (current !== null && first !== null) {
-      diff = current - first;
-    }
-
-    const count = result.length;
-
-    return {
-      data: result,
-      label: label,
-      color: colors[colorIndex],
-      stats: {
-        total,
-        min,
-        max,
-        current,
-        logmin,
-        avg,
-        diff,
-        delta,
-        timeStep,
-        range,
-        count,
-        first,
-        allIsZero,
-        allIsNull,
-      },
-    };
-  });
-
-  return vmSeries;
-}