mirror of https://github.com/grafana/grafana.git
v8: remove Singlestat panel and add automatic migration to stat/gauge(#31904)
* Remove singlestat panel from the codebase * Automatically migrate deprecated panels * Migrate singlestat to stat when initializing panel * Singlestat -> gauge detection * Missing await * Throw error when panel plugin not found and allow new panels to take names of the deprecated ones * Make it pretty Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
a6fcd37a20
commit
b47fba4b68
|
|
@ -1,757 +0,0 @@
|
|||
{
|
||||
"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,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorValue": true,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 100,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "postfix",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "prefix",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": false,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": true
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,2,3,4,5"
|
||||
}
|
||||
],
|
||||
"thresholds": "5,10",
|
||||
"title": "prefix 3 ms (green) postfix + sparkline",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "avg"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": true,
|
||||
"colors": ["#d44a3a", "rgba(237, 129, 40, 0.89)", "#299c46"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 100,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 0
|
||||
},
|
||||
"id": 3,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": true
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,2,3,4,5"
|
||||
}
|
||||
],
|
||||
"thresholds": "5,10",
|
||||
"title": "3 ms (red) + full height sparkline",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "200%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "avg"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": true,
|
||||
"colorPrefix": false,
|
||||
"colorValue": false,
|
||||
"colors": ["#d44a3a", "rgba(237, 129, 40, 0.89)", "#299c46"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 100,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 0
|
||||
},
|
||||
"id": 4,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,2,3,4,5"
|
||||
}
|
||||
],
|
||||
"thresholds": "5,10",
|
||||
"title": "3 ms + red background",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "200%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "avg"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": true,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": true,
|
||||
"thresholdLabels": true,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 7
|
||||
},
|
||||
"id": 5,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "10,20,80"
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "80 ms green gauge, thresholds 81, 90",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": true,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": true,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 7
|
||||
},
|
||||
"id": 6,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "10,20,80"
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "80 ms green gauge, thresholds 81, 90, no labels",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": true,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "ms",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": true,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": false
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 7
|
||||
},
|
||||
"id": 7,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "",
|
||||
"targets": [
|
||||
{
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "10,20,80"
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "80 ms green gauge, thresholds 81, 90, no markers or labels",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [
|
||||
{
|
||||
"op": "=",
|
||||
"text": "N/A",
|
||||
"value": "null"
|
||||
}
|
||||
],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": false,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "none",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 14
|
||||
},
|
||||
"id": 8,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"options": {},
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "Info",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk_table",
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "TableData 'Info' string Column",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": false,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": 2,
|
||||
"description": "",
|
||||
"format": "celsius",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 14
|
||||
},
|
||||
"id": 9,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"options": {},
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "Min",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk_table",
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "TableData 'Value' as temp Column",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [],
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"cacheTimeout": null,
|
||||
"colorBackground": false,
|
||||
"colorPrefix": false,
|
||||
"colorValue": false,
|
||||
"colors": ["#299c46", "rgba(237, 129, 40, 0.89)", "#d44a3a"],
|
||||
"datasource": "gdev-testdata",
|
||||
"decimals": null,
|
||||
"description": "",
|
||||
"format": "dateTimeFromNow",
|
||||
"gauge": {
|
||||
"maxValue": 150,
|
||||
"minValue": 0,
|
||||
"show": false,
|
||||
"thresholdLabels": false,
|
||||
"thresholdMarkers": true
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 4,
|
||||
"w": 8,
|
||||
"x": 16,
|
||||
"y": 14
|
||||
},
|
||||
"id": 10,
|
||||
"interval": null,
|
||||
"links": [],
|
||||
"mappingType": 1,
|
||||
"mappingTypes": [
|
||||
{
|
||||
"name": "value to text",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "range to text",
|
||||
"value": 2
|
||||
}
|
||||
],
|
||||
"maxDataPoints": 100,
|
||||
"nullPointMode": "connected",
|
||||
"nullText": null,
|
||||
"options": {},
|
||||
"postfix": "",
|
||||
"postfixFontSize": "50%",
|
||||
"prefix": "",
|
||||
"prefixFontSize": "50%",
|
||||
"rangeMaps": [
|
||||
{
|
||||
"from": "null",
|
||||
"text": "N/A",
|
||||
"to": "null"
|
||||
}
|
||||
],
|
||||
"sparkline": {
|
||||
"fillColor": "rgba(31, 118, 189, 0.18)",
|
||||
"full": true,
|
||||
"lineColor": "rgb(31, 120, 193)",
|
||||
"show": false
|
||||
},
|
||||
"tableColumn": "time",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"expr": "",
|
||||
"format": "time_series",
|
||||
"intervalFactor": 1,
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"stringInput": ""
|
||||
}
|
||||
],
|
||||
"thresholds": "81,90",
|
||||
"title": "last_time display (a few seconds ago)",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "80%",
|
||||
"valueMaps": [],
|
||||
"valueName": "last_time"
|
||||
}
|
||||
],
|
||||
"refresh": false,
|
||||
"revision": 8,
|
||||
"schemaVersion": 16,
|
||||
"style": "dark",
|
||||
"tags": ["gdev", "panel-tests"],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"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": "browser",
|
||||
"title": "Panel Tests - Singlestat",
|
||||
"uid": "singlestat",
|
||||
"version": 14
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
import { PanelModel } from '../features/dashboard/state';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
export const GRID_CELL_HEIGHT = 30;
|
||||
export const GRID_CELL_VMARGIN = 8;
|
||||
export const GRID_COLUMN_COUNT = 24;
|
||||
|
|
@ -14,3 +17,18 @@ export const PANEL_BORDER = 2;
|
|||
export const EDIT_PANEL_ID = 23763571993;
|
||||
|
||||
export const DEFAULT_PER_PAGE_PAGINATION = 8;
|
||||
|
||||
export const DEPRECATED_PANELS: Record<string, (panel: PanelModel) => string> = {
|
||||
singlestat: (panel: PanelModel) => {
|
||||
// If 'grafana-singlestat-panel' exists, move to that
|
||||
if (config.panels['grafana-singlestat-panel']) {
|
||||
return 'grafana-singlestat-panel';
|
||||
}
|
||||
|
||||
// Otheriwse use gauge or stat panel
|
||||
if ((panel as any).gauge && (panel as any).gauge.show) {
|
||||
return 'gauge';
|
||||
}
|
||||
return 'stat';
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import {
|
|||
} from './getPanelOptionsWithDefaults';
|
||||
import { QueryGroupOptions } from 'app/types';
|
||||
import { PanelModelLibraryPanel } from '../../library-panels/types';
|
||||
import { isDeprecatedPanel } from '../utils/panel';
|
||||
|
||||
export interface GridPos {
|
||||
x: number;
|
||||
|
|
@ -339,10 +340,9 @@ export class PanelModel implements DataConfigSource {
|
|||
|
||||
pluginLoaded(plugin: PanelPlugin) {
|
||||
this.plugin = plugin;
|
||||
const version = getPluginVersion(plugin);
|
||||
|
||||
if (plugin.onPanelMigration) {
|
||||
const version = getPluginVersion(plugin);
|
||||
|
||||
if (version !== this.pluginVersion) {
|
||||
this.options = plugin.onPanelMigration(this);
|
||||
this.pluginVersion = version;
|
||||
|
|
@ -380,8 +380,7 @@ export class PanelModel implements DataConfigSource {
|
|||
const oldOptions: any = this.getOptionsToRemember();
|
||||
const prevFieldConfig = this.fieldConfig;
|
||||
const oldPluginId = this.type;
|
||||
const wasAngular = this.isAngularPlugin();
|
||||
|
||||
const wasAngular = this.isAngularPlugin() || isDeprecatedPanel(this.type);
|
||||
this.cachedPluginOptions[oldPluginId] = {
|
||||
properties: oldOptions,
|
||||
fieldConfig: prevFieldConfig,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ import { loadPanelPlugin } from 'app/features/plugins/state/actions';
|
|||
import { DashboardAcl, DashboardAclUpdateDTO, NewDashboardAclItem, PermissionLevel, ThunkResult } from 'app/types';
|
||||
import { PanelModel } from './PanelModel';
|
||||
import { cancelVariables } from '../../variables/state/actions';
|
||||
import { isDeprecatedPanel } from '../utils/panel';
|
||||
import { DEPRECATED_PANELS } from '../../../core/constants';
|
||||
import { getPanelPluginNotFound } from '../dashgrid/PanelPluginError';
|
||||
import { getTimeSrv } from '../services/TimeSrv';
|
||||
|
||||
export function getDashboardPermissions(id: number): ThunkResult<void> {
|
||||
|
|
@ -120,10 +123,27 @@ export function removeDashboard(uri: string): ThunkResult<void> {
|
|||
|
||||
export function initDashboardPanel(panel: PanelModel): ThunkResult<void> {
|
||||
return async (dispatch, getStore) => {
|
||||
let plugin = getStore().plugins.panels[panel.type];
|
||||
let pluginToLoad = panel.type;
|
||||
|
||||
const isDeprecated = isDeprecatedPanel(panel.type);
|
||||
let notFound = false;
|
||||
let plugin = getStore().plugins.panels[pluginToLoad];
|
||||
|
||||
if (!plugin) {
|
||||
plugin = await dispatch(loadPanelPlugin(panel.type));
|
||||
try {
|
||||
plugin = await dispatch(loadPanelPlugin(pluginToLoad));
|
||||
} catch (e) {
|
||||
// When plugin not found
|
||||
plugin = getPanelPluginNotFound(pluginToLoad);
|
||||
notFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if there isn't an "external" plugin with the same name as deprecated one, load the deprecated panel replacement
|
||||
if (notFound && isDeprecated) {
|
||||
pluginToLoad = DEPRECATED_PANELS[panel.type](panel);
|
||||
plugin = await dispatch(loadPanelPlugin(pluginToLoad));
|
||||
await dispatch(changePanelPlugin(panel, pluginToLoad));
|
||||
}
|
||||
|
||||
if (!panel.plugin) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import config from 'app/core/config';
|
|||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
// Constants
|
||||
import { LS_PANEL_COPY_KEY, PANEL_BORDER } from 'app/core/constants';
|
||||
import { DEPRECATED_PANELS, LS_PANEL_COPY_KEY, PANEL_BORDER } from 'app/core/constants';
|
||||
|
||||
import { ShareModal } from 'app/features/dashboard/components/ShareModal';
|
||||
import { ShowConfirmModalEvent, ShowModalReactEvent } from '../../../types/events';
|
||||
|
|
@ -157,3 +157,7 @@ export function calculateInnerPanelHeight(panel: PanelModel, containerHeight: nu
|
|||
const headerHeight = panel.hasTitle() ? config.theme.panelHeaderHeight : 0;
|
||||
return containerHeight - headerHeight - chromePadding - PANEL_BORDER;
|
||||
}
|
||||
|
||||
export function isDeprecatedPanel(panelType: string) {
|
||||
return !!DEPRECATED_PANELS[panelType];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,8 +52,7 @@ import * as annoListPanel from 'app/plugins/panel/annolist/module';
|
|||
import * as heatmapPanel from 'app/plugins/panel/heatmap/module';
|
||||
import * as tablePanel from 'app/plugins/panel/table/module';
|
||||
import * as oldTablePanel from 'app/plugins/panel/table-old/module';
|
||||
import * as singlestatPanel from 'app/plugins/panel/singlestat/module';
|
||||
import * as singlestatPanel2 from 'app/plugins/panel/stat/module';
|
||||
import * as statPanel from 'app/plugins/panel/stat/module';
|
||||
import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module';
|
||||
import * as gaugePanel from 'app/plugins/panel/gauge/module';
|
||||
import * as pieChartPanel from 'app/plugins/panel/piechart/module';
|
||||
|
|
@ -102,9 +101,8 @@ const builtInPlugins: any = {
|
|||
'app/plugins/panel/table-old/module': oldTablePanel,
|
||||
'app/plugins/panel/news/module': newsPanel,
|
||||
'app/plugins/panel/live/module': livePanel,
|
||||
'app/plugins/panel/stat/module': statPanel,
|
||||
'app/plugins/panel/debug/module': debugPanel,
|
||||
'app/plugins/panel/singlestat/module': singlestatPanel,
|
||||
'app/plugins/panel/stat/module': singlestatPanel2,
|
||||
'app/plugins/panel/gettingstarted/module': gettingStartedPanel,
|
||||
'app/plugins/panel/gauge/module': gaugePanel,
|
||||
'app/plugins/panel/piechart/module': pieChartPanel,
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ const bust = `?_cache=${Date.now()}`;
|
|||
function locate(load: { address: string }) {
|
||||
return load.address + bust;
|
||||
}
|
||||
|
||||
grafanaRuntime.SystemJS.registry.set('plugin-loader', grafanaRuntime.SystemJS.newModule({ locate: locate }));
|
||||
|
||||
grafanaRuntime.SystemJS.config({
|
||||
|
|
@ -214,7 +215,7 @@ export function importAppPlugin(meta: grafanaData.PluginMeta): Promise<grafanaDa
|
|||
});
|
||||
}
|
||||
|
||||
import { getPanelPluginNotFound, getPanelPluginLoadError } from '../dashboard/dashgrid/PanelPluginError';
|
||||
import { getPanelPluginLoadError } from '../dashboard/dashgrid/PanelPluginError';
|
||||
import { GenericDataSourcePlugin } from '../datasources/settings/PluginSettings';
|
||||
|
||||
interface PanelCache {
|
||||
|
|
@ -224,7 +225,6 @@ const panelCache: PanelCache = {};
|
|||
|
||||
export function importPanelPlugin(id: string): Promise<grafanaData.PanelPlugin> {
|
||||
const loaded = panelCache[id];
|
||||
|
||||
if (loaded) {
|
||||
return loaded;
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ export function importPanelPlugin(id: string): Promise<grafanaData.PanelPlugin>
|
|||
const meta = config.panels[id];
|
||||
|
||||
if (!meta) {
|
||||
return Promise.resolve(getPanelPluginNotFound(id));
|
||||
throw new Error(`Plugin ${id} not found`);
|
||||
}
|
||||
|
||||
panelCache[id] = importPluginModule(meta.module)
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
# Singlestat Panel - Native Plugin
|
||||
|
||||
The Singlestat Panel is **included** with Grafana.
|
||||
|
||||
The Singlestat Panel allows you to show the one main summary stat of a SINGLE series. It reduces the series into a single number (by looking at the max, min, average, or sum of values in the series). Singlestat also provides thresholds to color the stat or the Panel background. It can also translate the single number into a text value, and show a sparkline summary of the series.
|
||||
|
||||
Read more about it here:
|
||||
|
||||
[https://grafana.com/docs/grafana/latest/features/panels/singlestat/](https://grafana.com/docs/grafana/latest/features/panels/singlestat/)
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
<div class="editor-row">
|
||||
|
||||
<div class="grafana-info-box" ng-if="ctrl.panel.gauge.show">
|
||||
<h5>Gauge migration</h5>
|
||||
<p>
|
||||
This panel is deprecated. Please migrate to the new Gauge panel.
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-primary" ng-click="ctrl.migrateToPanel('gauge')">
|
||||
Migrate to Gauge panel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div ng-if="ctrl.panel.sparkline.show">
|
||||
<b>NOTE:</b> Sparklines are not supported in the gauge panel
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.panel.prefix">
|
||||
<b>NOTE:</b> Prefix is no longer supported but can be done via a custom unit
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.panel.postfix">
|
||||
<b>NOTE:</b> Postfix is no longer supported but can be done via a custom unit
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grafana-info-box" ng-if="!ctrl.panel.gauge.show">
|
||||
<h5>Migration</h5>
|
||||
<p>
|
||||
This panel is deprecated. Please migrate to the new Stat panel.
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-primary" ng-click="ctrl.migrateToPanel('stat')">
|
||||
Migrate to Stat panel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<div ng-if="ctrl.panel.prefix">
|
||||
<b>NOTE:</b> Prefix is no longer supported but can be done via a custom unit
|
||||
</div>
|
||||
|
||||
<div ng-if="ctrl.panel.postfix">
|
||||
<b>NOTE:</b> Postfix is no longer supported but can be done via a custom unit
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Value</h5>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form" ng-if="ctrl.fieldNames.length > 1">
|
||||
<label class="gf-form-label width-6">Field</label>
|
||||
<div class="gf-form-select-wrapper width-12">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.tableColumn" ng-options="f for f in ctrl.fieldNames" ng-change="ctrl.refresh()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Show</label>
|
||||
<div class="gf-form-select-wrapper width-12">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.valueName" ng-options="f.value as f.text for f in ctrl.valueNameOptions" ng-change="ctrl.refresh()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Font size</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.valueFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Prefix</label>
|
||||
<input type="text" class="gf-form-input width-12" ng-model="ctrl.panel.prefix" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Font size</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.prefixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()" ng-disabled="!ctrl.canModifyText()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Postfix</label>
|
||||
<input type="text" class="gf-form-input width-12" ng-model="ctrl.panel.postfix" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Font size</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.postfixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()" ng-disabled="!ctrl.canModifyText()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Unit</label>
|
||||
<unit-picker onChange="ctrl.setUnitFormat()" value="ctrl.panel.format" width="18" />
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Decimals</label>
|
||||
<input type="number" class="gf-form-input width-18" placeholder="auto" data-placement="right" bs-tooltip="'Override automatic decimal precision for legend and tooltips'" ng-model="ctrl.panel.decimals" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Coloring</h5>
|
||||
<div class="gf-form-inline">
|
||||
<gf-form-switch class="gf-form" label-class="width-8" label="Background" checked="ctrl.panel.colorBackground" on-change="ctrl.render()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-6" label="Value" checked="ctrl.panel.colorValue" on-change="ctrl.render()"></gf-form-switch>
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<gf-form-switch class="gf-form" label-class="width-8" label="Prefix" checked="ctrl.panel.colorPrefix" on-change="ctrl.render()" ng-disabled="!ctrl.canModifyText()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-6" label="Postfix" checked="ctrl.panel.colorPostfix" on-change="ctrl.render()" ng-disabled="!ctrl.canModifyText()"></gf-form-switch>
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-21">
|
||||
<label class="gf-form-label width-8">Thresholds
|
||||
<tip>Define two threshold values<br /> 50,80 will produce: value < 50 = Green, 50 <= value < 80 = Yellow, value >= 80 = Red</tip>
|
||||
</label>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.panel.thresholds" ng-blur="ctrl.render()" placeholder="50,80"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Colors</label>
|
||||
<span class="gf-form-label" ng-repeat="color in ctrl.panel.colors track by $index">
|
||||
<color-picker color="color" onChange="ctrl.onColorChange($index)"></color-picker>
|
||||
</span>
|
||||
<span class="gf-form-label">
|
||||
<a ng-click="ctrl.invertColorOrder()">
|
||||
Invert
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">Spark lines</h5>
|
||||
<gf-form-switch class="gf-form" label-class="width-9" label="Show" checked="ctrl.panel.sparkline.show" on-change="ctrl.refresh()"></gf-form-switch>
|
||||
<div ng-if="ctrl.panel.sparkline.show">
|
||||
<gf-form-switch class="gf-form" label-class="width-9" label="Full height" checked="ctrl.panel.sparkline.full" on-change="ctrl.render()"></gf-form-switch>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Y-Min</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right" ng-model="ctrl.panel.sparkline.ymin" ng-change="ctrl.render()" ng-model-onblur empty-to-null>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Y-Max</label>
|
||||
<input type="number" class="gf-form-input width-5" placeholder="auto" data-placement="right" ng-model="ctrl.panel.sparkline.ymax" ng-change="ctrl.render()" ng-model-onblur empty-to-null>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Line Color</label>
|
||||
<span class="gf-form-label">
|
||||
<color-picker color="ctrl.panel.sparkline.lineColor" onChange="ctrl.onSparklineColorChange"></color-picker>
|
||||
</span>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Fill Color</label>
|
||||
<span class="gf-form-label">
|
||||
<color-picker color="ctrl.panel.sparkline.fillColor" onChange="ctrl.onSparklineFillChange"></color-picker>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group" ng-if="ctrl.panel.gauge.show">
|
||||
<h5 class="section-heading">Gauge</h5>
|
||||
<gf-form-switch class="gf-form" label-class="width-10" switch-class="max-width-6" label="Show" checked="ctrl.panel.gauge.show" on-change="ctrl.render()"></gf-form-switch>
|
||||
<div ng-if="ctrl.panel.gauge.show">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Min</label>
|
||||
<input type="number" class="gf-form-input width-6" placeholder="0" data-placement="right" ng-model="ctrl.panel.gauge.minValue" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
<label class="gf-form-label alert-state-critical" ng-show="ctrl.invalidGaugeRange">
|
||||
<icon name="'exclamation-triangle'"></icon>
|
||||
Min value is bigger than max.
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Max</label>
|
||||
<input type="number" class="gf-form-input width-6" placeholder="0" data-placement="right" ng-model="ctrl.panel.gauge.maxValue" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form" label-class="width-10" switch-class="max-width-6" label="Threshold labels" checked="ctrl.panel.gauge.thresholdLabels" on-change="ctrl.render()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-10" switch-class="max-width-6" label="Threshold markers" checked="ctrl.panel.gauge.thresholdMarkers" on-change="ctrl.render()"></gf-form-switch>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="100px" height="100px" viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{opacity:0.26;fill:url(#SVGID_1_);}
|
||||
.st1{fill:url(#SVGID_2_);}
|
||||
.st2{fill:url(#SVGID_3_);}
|
||||
.st3{fill:url(#SVGID_4_);}
|
||||
.st4{fill:url(#SVGID_5_);}
|
||||
.st5{fill:none;stroke:url(#SVGID_6_);stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="50" y1="65.6698" x2="50" y2="93.5681">
|
||||
<stop offset="0" style="stop-color:#FFF23A"/>
|
||||
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
|
||||
<stop offset="0.1171" style="stop-color:#FED41A"/>
|
||||
<stop offset="0.1964" style="stop-color:#FDC90F"/>
|
||||
<stop offset="0.2809" style="stop-color:#FDC60B"/>
|
||||
<stop offset="0.6685" style="stop-color:#F28F3F"/>
|
||||
<stop offset="0.8876" style="stop-color:#ED693C"/>
|
||||
<stop offset="1" style="stop-color:#E83E39"/>
|
||||
</linearGradient>
|
||||
<path class="st0" d="M97.6,83.8H2.4c-1.3,0-2.4-1.1-2.4-2.4v-1.8l17-1l19.2-4.3l16.3-1.6l16.5,0l15.8-4.7l15.1-3v16.3
|
||||
C100,82.8,98.9,83.8,97.6,83.8z"/>
|
||||
<g>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="19.098" y1="76.0776" x2="19.098" y2="27.8027">
|
||||
<stop offset="0" style="stop-color:#FFF23A"/>
|
||||
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
|
||||
<stop offset="0.1171" style="stop-color:#FED41A"/>
|
||||
<stop offset="0.1964" style="stop-color:#FDC90F"/>
|
||||
<stop offset="0.2809" style="stop-color:#FDC60B"/>
|
||||
<stop offset="0.6685" style="stop-color:#F28F3F"/>
|
||||
<stop offset="0.8876" style="stop-color:#ED693C"/>
|
||||
<stop offset="1" style="stop-color:#E83E39"/>
|
||||
</linearGradient>
|
||||
<path class="st1" d="M19.6,64.3V38.9l-5.2,3.9l-3.5-6l9.4-6.9h6.8v34.4H19.6z"/>
|
||||
<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="42.412" y1="76.0776" x2="42.412" y2="27.8027">
|
||||
<stop offset="0" style="stop-color:#FFF23A"/>
|
||||
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
|
||||
<stop offset="0.1171" style="stop-color:#FED41A"/>
|
||||
<stop offset="0.1964" style="stop-color:#FDC90F"/>
|
||||
<stop offset="0.2809" style="stop-color:#FDC60B"/>
|
||||
<stop offset="0.6685" style="stop-color:#F28F3F"/>
|
||||
<stop offset="0.8876" style="stop-color:#ED693C"/>
|
||||
<stop offset="1" style="stop-color:#E83E39"/>
|
||||
</linearGradient>
|
||||
<path class="st2" d="M53.1,39.4c0,1.1-0.1,2.2-0.4,3.2c-0.3,1-0.7,1.9-1.2,2.8c-0.5,0.9-1,1.7-1.7,2.5c-0.6,0.8-1.2,1.6-1.9,2.3
|
||||
l-6.4,7.4h11.1v6.7H32.3v-6.9l10.5-12c0.8-1,1.5-2,2-3c0.5-1,0.7-2,0.7-2.9c0-1-0.2-1.9-0.7-2.6c-0.5-0.7-1.2-1.1-2.2-1.1
|
||||
c-0.9,0-1.7,0.4-2.3,1.1c-0.6,0.8-1,1.9-1.1,3.3l-7.3-0.7c0.4-3.5,1.6-6.1,3.6-7.9c2-1.7,4.5-2.6,7.4-2.6c1.6,0,3,0.2,4.3,0.7
|
||||
c1.3,0.5,2.3,1.2,3.2,2c0.9,0.9,1.6,1.9,2.1,3.2C52.8,36.4,53.1,37.8,53.1,39.4z"/>
|
||||
<linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="60.3739" y1="76.0776" x2="60.3739" y2="27.8027">
|
||||
<stop offset="0" style="stop-color:#FFF23A"/>
|
||||
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
|
||||
<stop offset="0.1171" style="stop-color:#FED41A"/>
|
||||
<stop offset="0.1964" style="stop-color:#FDC90F"/>
|
||||
<stop offset="0.2809" style="stop-color:#FDC60B"/>
|
||||
<stop offset="0.6685" style="stop-color:#F28F3F"/>
|
||||
<stop offset="0.8876" style="stop-color:#ED693C"/>
|
||||
<stop offset="1" style="stop-color:#E83E39"/>
|
||||
</linearGradient>
|
||||
<path class="st3" d="M64.5,60.4c0,1.2-0.4,2.3-1.2,3.1c-0.8,0.8-1.8,1.3-3,1.3c-1.2,0-2.2-0.4-3-1.3c-0.8-0.8-1.1-1.9-1.1-3.1
|
||||
c0-1.2,0.4-2.2,1.1-3.1c0.8-0.9,1.8-1.3,3-1.3c1.2,0,2.2,0.4,3,1.3C64.1,58.1,64.5,59.2,64.5,60.4z"/>
|
||||
<linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="77.5234" y1="76.0776" x2="77.5234" y2="27.8027">
|
||||
<stop offset="0" style="stop-color:#FFF23A"/>
|
||||
<stop offset="4.010540e-02" style="stop-color:#FEE62D"/>
|
||||
<stop offset="0.1171" style="stop-color:#FED41A"/>
|
||||
<stop offset="0.1964" style="stop-color:#FDC90F"/>
|
||||
<stop offset="0.2809" style="stop-color:#FDC60B"/>
|
||||
<stop offset="0.6685" style="stop-color:#F28F3F"/>
|
||||
<stop offset="0.8876" style="stop-color:#ED693C"/>
|
||||
<stop offset="1" style="stop-color:#E83E39"/>
|
||||
</linearGradient>
|
||||
<path class="st4" d="M85.5,57.4v6.9h-6.9v-6.9H66v-6.6l10.1-20.9h9.4V51H89v6.4H85.5z M78.8,37.5L78.8,37.5l-6,13.5h6V37.5z"/>
|
||||
</g>
|
||||
<linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="-2.852199e-02" y1="72.3985" x2="100.0976" y2="72.3985">
|
||||
<stop offset="0" style="stop-color:#F28F3F"/>
|
||||
<stop offset="1" style="stop-color:#F28F3F"/>
|
||||
</linearGradient>
|
||||
<polyline class="st5" points="0,79.7 17,78.7 36.2,74.4 52.5,72.8 69,72.9 84.9,68.1 100,65.1 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
|
|
@ -1,74 +0,0 @@
|
|||
<div class="editor-row">
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">
|
||||
Type
|
||||
</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select
|
||||
class="gf-form-input"
|
||||
ng-model="ctrl.panel.mappingType"
|
||||
ng-options="f.value as f.name for f in ctrl.panel.mappingTypes"
|
||||
ng-change="ctrl.refresh()"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" ng-if="ctrl.panel.mappingType==1">
|
||||
<h5 class="section-heading">Set value mappings</h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form" ng-repeat="map in ctrl.panel.valueMaps">
|
||||
<span class="gf-form-label">
|
||||
<icon name="'times'" ng-click="ctrl.removeValueMap(map)"></icon>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
ng-model="map.value"
|
||||
placeholder="value"
|
||||
class="gf-form-input max-width-6"
|
||||
ng-blur="ctrl.refresh()"
|
||||
/>
|
||||
<span class="gf-form-label">
|
||||
<icon name="'arrow-right'"></icon>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="text"
|
||||
ng-model="map.text"
|
||||
class="gf-form-input max-width-8"
|
||||
ng-blur="ctrl.refresh()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.addValueMap();">
|
||||
<icon name="'plus'"></icon>
|
||||
Add a value mapping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-row" ng-if="ctrl.panel.mappingType==2">
|
||||
<h5 class="section-heading">Set range mappings</h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form" ng-repeat="rangeMap in ctrl.panel.rangeMaps">
|
||||
<span class="gf-form-label">
|
||||
<icon name="'times'" ng-click="ctrl.removeRangeMap(rangeMap)"></icon>
|
||||
</span>
|
||||
<span class="gf-form-label">From</span>
|
||||
<input type="text" ng-model="rangeMap.from" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()" />
|
||||
<span class="gf-form-label">To</span>
|
||||
<input type="text" ng-model="rangeMap.to" class="gf-form-input max-width-6" ng-blur="ctrl.refresh()" />
|
||||
<span class="gf-form-label">Text</span>
|
||||
<input type="text" ng-model="rangeMap.text" class="gf-form-input max-width-8" ng-blur="ctrl.refresh()" />
|
||||
</div>
|
||||
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-inverse" ng-click="ctrl.addRangeMap()">
|
||||
<icon name="'plus'"></icon>
|
||||
Add a range mapping
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<div class="singlestat-panel">
|
||||
|
||||
</div>
|
||||
|
|
@ -1,756 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import { auto } from 'angular';
|
||||
import $ from 'jquery';
|
||||
import 'vendor/flot/jquery.flot';
|
||||
import 'vendor/flot/jquery.flot.gauge';
|
||||
|
||||
import {
|
||||
DataFrame,
|
||||
DisplayValue,
|
||||
Field,
|
||||
fieldReducers,
|
||||
FieldType,
|
||||
GraphSeriesValue,
|
||||
KeyValue,
|
||||
LinkModel,
|
||||
reduceField,
|
||||
ReducerID,
|
||||
LegacyResponseData,
|
||||
getFlotPairs,
|
||||
getDisplayProcessor,
|
||||
PanelEvents,
|
||||
formattedValueToString,
|
||||
locationUtil,
|
||||
getFieldDisplayName,
|
||||
getColorForTheme,
|
||||
InterpolateFunction,
|
||||
} from '@grafana/data';
|
||||
|
||||
import { convertOldAngularValueMapping } from '@grafana/ui';
|
||||
|
||||
import config from 'app/core/config';
|
||||
import { MetricsPanelCtrl } from 'app/plugins/sdk';
|
||||
import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
|
||||
import { getProcessedDataFrames } from 'app/features/query/state/runRequest';
|
||||
|
||||
const BASE_FONT_SIZE = 38;
|
||||
|
||||
export interface ShowData {
|
||||
field: Field;
|
||||
value: any;
|
||||
sparkline: GraphSeriesValue[][];
|
||||
display: DisplayValue;
|
||||
|
||||
scopedVars: any;
|
||||
|
||||
thresholds: any[];
|
||||
colorMap: any;
|
||||
}
|
||||
|
||||
class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
static templateUrl = 'module.html';
|
||||
|
||||
data: Partial<ShowData> = {};
|
||||
fontSizes: any[] = [];
|
||||
fieldNames: string[] = [];
|
||||
invalidGaugeRange = false;
|
||||
|
||||
valueNameOptions: any[] = [
|
||||
{ value: 'min', text: 'Min' },
|
||||
{ value: 'max', text: 'Max' },
|
||||
{ value: 'avg', text: 'Average' },
|
||||
{ value: 'current', text: 'Current' },
|
||||
{ value: 'total', text: 'Total' },
|
||||
{ value: 'name', text: 'Name' },
|
||||
{ value: 'first', text: 'First' },
|
||||
{ value: 'delta', text: 'Delta' },
|
||||
{ value: 'diff', text: 'Difference' },
|
||||
{ value: 'diffperc', text: 'Difference percent' },
|
||||
{ value: 'range', text: 'Range' },
|
||||
{ value: 'last_time', text: 'Time of last point' },
|
||||
];
|
||||
|
||||
// Set and populate defaults
|
||||
panelDefaults: any = {
|
||||
links: [],
|
||||
datasource: null,
|
||||
maxDataPoints: 100,
|
||||
interval: null,
|
||||
targets: [{}],
|
||||
cacheTimeout: null,
|
||||
format: 'none',
|
||||
prefix: '',
|
||||
postfix: '',
|
||||
nullText: null,
|
||||
valueMaps: [{ value: 'null', op: '=', text: 'N/A' }],
|
||||
mappingTypes: [
|
||||
{ name: 'value to text', value: 1 },
|
||||
{ name: 'range to text', value: 2 },
|
||||
],
|
||||
rangeMaps: [{ from: 'null', to: 'null', text: 'N/A' }],
|
||||
mappingType: 1,
|
||||
nullPointMode: 'connected',
|
||||
valueName: 'avg',
|
||||
prefixFontSize: '50%',
|
||||
valueFontSize: '80%',
|
||||
postfixFontSize: '50%',
|
||||
thresholds: '',
|
||||
colorBackground: false,
|
||||
colorValue: false,
|
||||
colors: ['#299c46', 'rgba(237, 129, 40, 0.89)', '#d44a3a'],
|
||||
sparkline: {
|
||||
show: false,
|
||||
full: false,
|
||||
ymin: null,
|
||||
ymax: null,
|
||||
lineColor: 'rgb(31, 120, 193)',
|
||||
fillColor: 'rgba(31, 118, 189, 0.18)',
|
||||
},
|
||||
gauge: {
|
||||
show: false,
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
thresholdMarkers: true,
|
||||
thresholdLabels: false,
|
||||
},
|
||||
tableColumn: '',
|
||||
};
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope: any, $injector: auto.IInjectorService, private linkSrv: LinkSrv, private $sanitize: any) {
|
||||
super($scope, $injector);
|
||||
_.defaults(this.panel, this.panelDefaults);
|
||||
|
||||
this.events.on(PanelEvents.dataFramesReceived, this.onFramesReceived.bind(this));
|
||||
this.events.on(PanelEvents.dataSnapshotLoad, this.onSnapshotLoad.bind(this));
|
||||
this.events.on(PanelEvents.editModeInitialized, this.onInitEditMode.bind(this));
|
||||
|
||||
this.useDataFrames = true;
|
||||
|
||||
this.onSparklineColorChange = this.onSparklineColorChange.bind(this);
|
||||
this.onSparklineFillChange = this.onSparklineFillChange.bind(this);
|
||||
}
|
||||
|
||||
onInitEditMode() {
|
||||
this.fontSizes = ['20%', '30%', '50%', '70%', '80%', '100%', '110%', '120%', '150%', '170%', '200%'];
|
||||
this.addEditorTab('Options', 'public/app/plugins/panel/singlestat/editor.html', 2);
|
||||
this.addEditorTab('Value Mappings', 'public/app/plugins/panel/singlestat/mappings.html', 3);
|
||||
}
|
||||
|
||||
migrateToPanel(type: string) {
|
||||
this.onPluginTypeChange(config.panels[type]);
|
||||
}
|
||||
|
||||
setUnitFormat() {
|
||||
return (unit: string) => {
|
||||
this.panel.format = unit;
|
||||
this.refresh();
|
||||
};
|
||||
}
|
||||
|
||||
onSnapshotLoad(dataList: LegacyResponseData[]) {
|
||||
this.onFramesReceived(getProcessedDataFrames(dataList));
|
||||
}
|
||||
|
||||
onFramesReceived(frames: DataFrame[]) {
|
||||
const { panel } = this;
|
||||
this.dataList = frames;
|
||||
|
||||
if (frames && frames.length > 1) {
|
||||
this.data = {
|
||||
value: 0,
|
||||
display: {
|
||||
text: 'Only queries that return single series/table is supported',
|
||||
numeric: NaN,
|
||||
},
|
||||
};
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
|
||||
const distinct = getDistinctNames(frames);
|
||||
let fieldInfo: FieldInfo | undefined = distinct.byName[panel.tableColumn];
|
||||
|
||||
this.fieldNames = distinct.names;
|
||||
|
||||
if (!fieldInfo) {
|
||||
fieldInfo = distinct.first;
|
||||
}
|
||||
|
||||
if (!fieldInfo) {
|
||||
const processor = getDisplayProcessor({
|
||||
field: {
|
||||
config: {
|
||||
mappings: convertOldAngularValueMapping(this.panel),
|
||||
noValue: 'No Data',
|
||||
},
|
||||
},
|
||||
theme: config.theme,
|
||||
timeZone: this.dashboard.getTimezone(),
|
||||
});
|
||||
// When we don't have any field
|
||||
this.data = {
|
||||
value: null,
|
||||
display: processor(null),
|
||||
};
|
||||
} else {
|
||||
this.data = this.processField(fieldInfo);
|
||||
}
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
processField(fieldInfo: FieldInfo) {
|
||||
const { panel, dashboard } = this;
|
||||
|
||||
const name = getFieldDisplayName(fieldInfo.field, fieldInfo.frame.frame, this.dataList as DataFrame[]);
|
||||
let calc = panel.valueName;
|
||||
let calcField = fieldInfo.field;
|
||||
let val: any = undefined;
|
||||
|
||||
if ('name' === calc) {
|
||||
val = name;
|
||||
} else {
|
||||
if ('last_time' === calc) {
|
||||
if (fieldInfo.frame.firstTimeField) {
|
||||
calcField = fieldInfo.frame.firstTimeField;
|
||||
calc = ReducerID.last;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize functions (avg -> mean, etc)
|
||||
const r = fieldReducers.getIfExists(calc);
|
||||
if (r) {
|
||||
calc = r.id;
|
||||
// With strings, don't accidentally use a math function
|
||||
if (calcField.type === FieldType.string) {
|
||||
const avoid = [ReducerID.mean, ReducerID.sum];
|
||||
if (avoid.includes(calc)) {
|
||||
calc = panel.valueName = ReducerID.first;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
calc = ReducerID.lastNotNull;
|
||||
}
|
||||
|
||||
// Calculate the value
|
||||
val = reduceField({
|
||||
field: calcField,
|
||||
reducers: [calc],
|
||||
})[calc];
|
||||
}
|
||||
|
||||
const processor = getDisplayProcessor({
|
||||
field: {
|
||||
...fieldInfo.field,
|
||||
config: {
|
||||
...fieldInfo.field.config,
|
||||
unit: panel.format,
|
||||
decimals: panel.decimals,
|
||||
mappings: convertOldAngularValueMapping(panel),
|
||||
},
|
||||
},
|
||||
theme: config.theme,
|
||||
timeZone: dashboard.getTimezone(),
|
||||
});
|
||||
|
||||
const sparkline: any[] = [];
|
||||
const data = {
|
||||
field: fieldInfo.field,
|
||||
value: val,
|
||||
display: processor(val),
|
||||
scopedVars: _.extend({}, panel.scopedVars),
|
||||
sparkline,
|
||||
};
|
||||
|
||||
data.scopedVars['__name'] = { value: name };
|
||||
panel.tableColumn = this.fieldNames.length > 1 ? name : '';
|
||||
|
||||
// Get the fields for a sparkline
|
||||
if (panel.sparkline && panel.sparkline.show && fieldInfo.frame.firstTimeField) {
|
||||
data.sparkline = getFlotPairs({
|
||||
xField: fieldInfo.frame.firstTimeField,
|
||||
yField: fieldInfo.field,
|
||||
nullValueMode: panel.nullPointMode,
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
canModifyText() {
|
||||
return !this.panel.gauge.show;
|
||||
}
|
||||
|
||||
setColoring(options: { background: any }) {
|
||||
if (options.background) {
|
||||
this.panel.colorValue = false;
|
||||
this.panel.colors = ['rgba(71, 212, 59, 0.4)', 'rgba(245, 150, 40, 0.73)', 'rgba(225, 40, 40, 0.59)'];
|
||||
} else {
|
||||
this.panel.colorBackground = false;
|
||||
this.panel.colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
invertColorOrder() {
|
||||
const tmp = this.panel.colors[0];
|
||||
this.panel.colors[0] = this.panel.colors[2];
|
||||
this.panel.colors[2] = tmp;
|
||||
this.render();
|
||||
}
|
||||
|
||||
onColorChange(panelColorIndex: number) {
|
||||
return (color: string) => {
|
||||
this.panel.colors[panelColorIndex] = color;
|
||||
this.render();
|
||||
};
|
||||
}
|
||||
|
||||
onSparklineColorChange(newColor: string) {
|
||||
this.panel.sparkline.lineColor = newColor;
|
||||
this.render();
|
||||
}
|
||||
|
||||
onSparklineFillChange(newColor: string) {
|
||||
this.panel.sparkline.fillColor = newColor;
|
||||
this.render();
|
||||
}
|
||||
|
||||
removeValueMap(map: any) {
|
||||
const index = _.indexOf(this.panel.valueMaps, map);
|
||||
this.panel.valueMaps.splice(index, 1);
|
||||
this.render();
|
||||
}
|
||||
|
||||
addValueMap() {
|
||||
this.panel.valueMaps.push({ value: '', op: '=', text: '' });
|
||||
}
|
||||
|
||||
removeRangeMap(rangeMap: any) {
|
||||
const index = _.indexOf(this.panel.rangeMaps, rangeMap);
|
||||
this.panel.rangeMaps.splice(index, 1);
|
||||
this.render();
|
||||
}
|
||||
|
||||
addRangeMap() {
|
||||
this.panel.rangeMaps.push({ from: '', to: '', text: '' });
|
||||
}
|
||||
|
||||
link(scope: any, elem: JQuery, attrs: any, ctrl: any) {
|
||||
const $location = this.$location;
|
||||
const linkSrv = this.linkSrv;
|
||||
const $timeout = this.$timeout;
|
||||
const $sanitize = this.$sanitize;
|
||||
const panel = ctrl.panel;
|
||||
const templateSrv = this.templateSrv;
|
||||
let linkInfo: LinkModel<any> | null = null;
|
||||
elem = elem.find('.singlestat-panel');
|
||||
|
||||
function getPanelContainer() {
|
||||
return elem.closest('.panel-container');
|
||||
}
|
||||
|
||||
function applyColoringThresholds(valueString: string) {
|
||||
const data = ctrl.data;
|
||||
const color = getColorForValue(data, data.value);
|
||||
if (color) {
|
||||
return '<span style="color:' + color + '">' + valueString + '</span>';
|
||||
}
|
||||
|
||||
return valueString;
|
||||
}
|
||||
|
||||
function getSpan(className: string, fontSizePercent: string, applyColoring: any, value: string) {
|
||||
value = $sanitize(templateSrv.replace(value, ctrl.data.scopedVars));
|
||||
value = applyColoring ? applyColoringThresholds(value) : value;
|
||||
const pixelSize = (parseInt(fontSizePercent, 10) / 100) * BASE_FONT_SIZE;
|
||||
return '<span class="' + className + '" style="font-size:' + pixelSize + 'px">' + value + '</span>';
|
||||
}
|
||||
|
||||
function getBigValueHtml() {
|
||||
const data: ShowData = ctrl.data;
|
||||
let body = '<div class="singlestat-panel-value-container">';
|
||||
|
||||
if (panel.prefix) {
|
||||
body += getSpan('singlestat-panel-prefix', panel.prefixFontSize, panel.colorPrefix, panel.prefix);
|
||||
}
|
||||
|
||||
body += getSpan(
|
||||
'singlestat-panel-value',
|
||||
panel.valueFontSize,
|
||||
panel.colorValue,
|
||||
formattedValueToString(data.display)
|
||||
);
|
||||
|
||||
if (panel.postfix) {
|
||||
body += getSpan('singlestat-panel-postfix', panel.postfixFontSize, panel.colorPostfix, panel.postfix);
|
||||
}
|
||||
|
||||
body += '</div>';
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
function getValueText() {
|
||||
const data: ShowData = ctrl.data;
|
||||
let result = panel.prefix ? templateSrv.replace(panel.prefix, data.scopedVars) : '';
|
||||
result += formattedValueToString(data.display);
|
||||
result += panel.postfix ? templateSrv.replace(panel.postfix, data.scopedVars) : '';
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function addGauge() {
|
||||
const data: ShowData = ctrl.data;
|
||||
const width = elem.width() || 10;
|
||||
const height = elem.height() || 10;
|
||||
|
||||
// Allow to use a bit more space for wide gauges
|
||||
const dimension = Math.min(width, height * 1.3);
|
||||
|
||||
ctrl.invalidGaugeRange = false;
|
||||
if (panel.gauge.minValue > panel.gauge.maxValue) {
|
||||
ctrl.invalidGaugeRange = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const plotCanvas = $('<div></div>');
|
||||
const plotCss = {
|
||||
top: '5px',
|
||||
margin: 'auto',
|
||||
position: 'relative',
|
||||
height: height * 0.9 + 'px',
|
||||
width: dimension + 'px',
|
||||
};
|
||||
|
||||
plotCanvas.css(plotCss);
|
||||
|
||||
const thresholds = [];
|
||||
|
||||
for (let i = 0; i < data.thresholds.length; i++) {
|
||||
thresholds.push({
|
||||
value: data.thresholds[i],
|
||||
color: data.colorMap[i],
|
||||
});
|
||||
}
|
||||
thresholds.push({
|
||||
value: panel.gauge.maxValue,
|
||||
color: data.colorMap[data.colorMap.length - 1],
|
||||
});
|
||||
|
||||
const bgColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
|
||||
|
||||
const fontScale = parseInt(panel.valueFontSize, 10) / 100;
|
||||
const fontSize = Math.min(dimension / 5, 100) * fontScale;
|
||||
// Reduce gauge width if threshold labels enabled
|
||||
const gaugeWidthReduceRatio = panel.gauge.thresholdLabels ? 1.5 : 1;
|
||||
const gaugeWidth = Math.min(dimension / 6, 60) / gaugeWidthReduceRatio;
|
||||
const thresholdMarkersWidth = gaugeWidth / 5;
|
||||
const thresholdLabelFontSize = fontSize / 2.5;
|
||||
|
||||
const options: any = {
|
||||
series: {
|
||||
gauges: {
|
||||
gauge: {
|
||||
min: panel.gauge.minValue,
|
||||
max: panel.gauge.maxValue,
|
||||
background: { color: bgColor },
|
||||
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: thresholds,
|
||||
label: {
|
||||
show: panel.gauge.thresholdLabels,
|
||||
margin: thresholdMarkersWidth + 1,
|
||||
font: { size: thresholdLabelFontSize },
|
||||
},
|
||||
show: panel.gauge.thresholdMarkers,
|
||||
width: thresholdMarkersWidth,
|
||||
},
|
||||
value: {
|
||||
color: panel.colorValue ? getColorForValue(data, data.display.numeric) : null,
|
||||
formatter: () => {
|
||||
return getValueText();
|
||||
},
|
||||
font: {
|
||||
size: fontSize,
|
||||
family: config.theme.typography.fontFamily.sansSerif,
|
||||
},
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
elem.append(plotCanvas);
|
||||
|
||||
const plotSeries = {
|
||||
data: [[0, data.value]],
|
||||
};
|
||||
|
||||
$.plot(plotCanvas, [plotSeries], options);
|
||||
}
|
||||
|
||||
function addSparkline() {
|
||||
const data: ShowData = ctrl.data;
|
||||
const width = elem.width() || 30;
|
||||
|
||||
if (width && width < 30) {
|
||||
// element has not gotten it's width yet
|
||||
// delay sparkline render
|
||||
setTimeout(addSparkline, 30);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data.sparkline || !data.sparkline.length) {
|
||||
// no sparkline data
|
||||
return;
|
||||
}
|
||||
|
||||
const height = ctrl.height;
|
||||
const plotCanvas = $('<div></div>');
|
||||
const plotCss: any = {};
|
||||
plotCss.position = 'absolute';
|
||||
plotCss.bottom = '0px';
|
||||
|
||||
if (panel.sparkline.full) {
|
||||
plotCss.left = '0px';
|
||||
plotCss.width = width + 'px';
|
||||
const dynamicHeightMargin = height <= 100 ? 5 : Math.round(height / 100) * 15 + 5;
|
||||
plotCss.height = height - dynamicHeightMargin + 'px';
|
||||
} else {
|
||||
plotCss.left = '0px';
|
||||
plotCss.width = width + 'px';
|
||||
plotCss.height = Math.floor(height * 0.25) + 'px';
|
||||
}
|
||||
|
||||
plotCanvas.css(plotCss);
|
||||
|
||||
const options = {
|
||||
legend: { show: false },
|
||||
series: {
|
||||
lines: {
|
||||
show: true,
|
||||
fill: 1,
|
||||
lineWidth: 1,
|
||||
fillColor: getColorForTheme(panel.sparkline.fillColor, config.theme),
|
||||
zero: false,
|
||||
},
|
||||
},
|
||||
yaxis: {
|
||||
show: false,
|
||||
min: panel.sparkline.ymin,
|
||||
max: panel.sparkline.ymax,
|
||||
},
|
||||
xaxis: {
|
||||
show: false,
|
||||
mode: 'time',
|
||||
min: ctrl.range.from.valueOf(),
|
||||
max: ctrl.range.to.valueOf(),
|
||||
},
|
||||
grid: { hoverable: false, show: false },
|
||||
};
|
||||
|
||||
elem.append(plotCanvas);
|
||||
|
||||
const plotSeries = {
|
||||
data: data.sparkline,
|
||||
color: getColorForTheme(panel.sparkline.lineColor, config.theme),
|
||||
};
|
||||
|
||||
$.plot(plotCanvas, [plotSeries], options);
|
||||
}
|
||||
|
||||
function render() {
|
||||
if (!ctrl.data) {
|
||||
return;
|
||||
}
|
||||
const { data, panel } = ctrl;
|
||||
|
||||
// get thresholds
|
||||
data.thresholds = panel.thresholds
|
||||
? panel.thresholds.split(',').map((strVale: string) => {
|
||||
return Number(strVale.trim());
|
||||
})
|
||||
: [];
|
||||
|
||||
// Map panel colors to hex or rgb/a values
|
||||
if (panel.colors) {
|
||||
data.colorMap = panel.colors.map((color: string) => getColorForTheme(color, config.theme));
|
||||
}
|
||||
|
||||
const body = panel.gauge.show ? '' : getBigValueHtml();
|
||||
|
||||
if (panel.colorBackground) {
|
||||
const color = getColorForValue(data, data.display.numeric);
|
||||
if (color) {
|
||||
getPanelContainer().css('background-color', color);
|
||||
if (scope.fullscreen) {
|
||||
elem.css('background-color', color);
|
||||
} else {
|
||||
elem.css('background-color', '');
|
||||
}
|
||||
} else {
|
||||
getPanelContainer().css('background-color', '');
|
||||
elem.css('background-color', '');
|
||||
}
|
||||
} else {
|
||||
getPanelContainer().css('background-color', '');
|
||||
elem.css('background-color', '');
|
||||
}
|
||||
|
||||
elem.html(body);
|
||||
|
||||
if (panel.sparkline.show) {
|
||||
addSparkline();
|
||||
}
|
||||
|
||||
if (panel.gauge.show) {
|
||||
addGauge();
|
||||
}
|
||||
|
||||
elem.toggleClass('pointer', panel.links.length > 0);
|
||||
|
||||
if (panel.links.length > 0) {
|
||||
const replace: InterpolateFunction = (value, vars) =>
|
||||
templateSrv.replace(value, { ...vars, ...data.scopedVars });
|
||||
linkInfo = linkSrv.getDataLinkUIModel(panel.links[0], replace, {});
|
||||
} else {
|
||||
linkInfo = null;
|
||||
}
|
||||
}
|
||||
|
||||
function hookupDrilldownLinkTooltip() {
|
||||
// drilldown link tooltip
|
||||
const drilldownTooltip = $('<div id="tooltip" class="">hello</div>"');
|
||||
|
||||
elem.mouseleave(() => {
|
||||
if (panel.links.length === 0) {
|
||||
return;
|
||||
}
|
||||
$timeout(() => {
|
||||
drilldownTooltip.detach();
|
||||
});
|
||||
});
|
||||
|
||||
elem.click((evt) => {
|
||||
if (!linkInfo) {
|
||||
return;
|
||||
}
|
||||
// ignore title clicks in title
|
||||
if ($(evt).parents('.panel-header').length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (linkInfo.target === '_blank') {
|
||||
window.open(linkInfo.href, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
if (linkInfo.href.indexOf('http') === 0) {
|
||||
window.location.href = linkInfo.href;
|
||||
} else {
|
||||
$timeout(() => {
|
||||
$location.url(locationUtil.stripBaseFromUrl(linkInfo!.href));
|
||||
});
|
||||
}
|
||||
|
||||
drilldownTooltip.detach();
|
||||
});
|
||||
|
||||
elem.mousemove((e) => {
|
||||
if (!linkInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
drilldownTooltip.text('click to go to: ' + linkInfo.title);
|
||||
drilldownTooltip.place_tt(e.pageX, e.pageY - 50);
|
||||
});
|
||||
}
|
||||
|
||||
hookupDrilldownLinkTooltip();
|
||||
|
||||
this.events.on(PanelEvents.render, () => {
|
||||
render();
|
||||
ctrl.renderingCompleted();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getColorForValue(data: any, value: number) {
|
||||
if (!_.isFinite(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = data.thresholds.length; i > 0; i--) {
|
||||
if (value >= data.thresholds[i - 1]) {
|
||||
return data.colorMap[i];
|
||||
}
|
||||
}
|
||||
|
||||
return _.first(data.colorMap);
|
||||
}
|
||||
|
||||
//------------------------------------------------
|
||||
// Private utility functions
|
||||
// Something like this should be available in a
|
||||
// DataFrame[] abstraction helper
|
||||
//------------------------------------------------
|
||||
|
||||
interface FrameInfo {
|
||||
firstTimeField?: Field;
|
||||
frame: DataFrame;
|
||||
}
|
||||
|
||||
interface FieldInfo {
|
||||
field: Field;
|
||||
frame: FrameInfo;
|
||||
}
|
||||
|
||||
interface DistinctFieldsInfo {
|
||||
first?: FieldInfo;
|
||||
byName: KeyValue<FieldInfo>;
|
||||
names: string[];
|
||||
}
|
||||
|
||||
function getDistinctNames(data: DataFrame[]): DistinctFieldsInfo {
|
||||
const distinct: DistinctFieldsInfo = {
|
||||
byName: {},
|
||||
names: [],
|
||||
};
|
||||
for (const frame of data) {
|
||||
const info: FrameInfo = { frame };
|
||||
for (const field of frame.fields) {
|
||||
if (field.type === FieldType.time) {
|
||||
if (!info.firstTimeField) {
|
||||
info.firstTimeField = field;
|
||||
}
|
||||
} else {
|
||||
const f = { field, frame: info };
|
||||
if (!distinct.first) {
|
||||
distinct.first = f;
|
||||
}
|
||||
let t = field.config.displayName;
|
||||
if (t && !distinct.byName[t]) {
|
||||
distinct.byName[t] = f;
|
||||
distinct.names.push(t);
|
||||
}
|
||||
t = field.name;
|
||||
if (t && !distinct.byName[t]) {
|
||||
distinct.byName[t] = f;
|
||||
distinct.names.push(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return distinct;
|
||||
}
|
||||
|
||||
export { SingleStatCtrl, SingleStatCtrl as PanelCtrl, getColorForValue };
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"type": "panel",
|
||||
"name": "Singlestat",
|
||||
"id": "singlestat",
|
||||
"state": "deprecated",
|
||||
|
||||
"info": {
|
||||
"description": "Singlestat Panel for Grafana",
|
||||
"author": {
|
||||
"name": "Grafana Labs",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-singlestat-panel.svg",
|
||||
"large": "img/icn-singlestat-panel.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,457 +0,0 @@
|
|||
import { SingleStatCtrl, ShowData } from '../module';
|
||||
import { dateTime, ReducerID, getFieldDisplayName, LegacyResponseData } from '@grafana/data';
|
||||
import { LinkSrv } from 'app/features/panel/panellinks/link_srv';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
|
||||
interface TestContext {
|
||||
ctrl: SingleStatCtrl;
|
||||
input: LegacyResponseData[];
|
||||
data: Partial<ShowData>;
|
||||
setup: (setupFunc: any) => void;
|
||||
}
|
||||
|
||||
describe('SingleStatCtrl', () => {
|
||||
const ctx: TestContext = {} as TestContext;
|
||||
const epoch = 1505826363746;
|
||||
Date.now = () => epoch;
|
||||
|
||||
const $scope = {
|
||||
$on: () => {},
|
||||
};
|
||||
|
||||
const $injector = {
|
||||
get: () => {},
|
||||
};
|
||||
|
||||
const $sanitize = {};
|
||||
|
||||
SingleStatCtrl.prototype.dashboard = ({
|
||||
getTimezone: jest.fn(() => 'utc'),
|
||||
} as any) as DashboardModel;
|
||||
|
||||
function singleStatScenario(desc: string, func: any) {
|
||||
describe(desc, () => {
|
||||
ctx.setup = (setupFunc: any) => {
|
||||
beforeEach(() => {
|
||||
SingleStatCtrl.prototype.panel = {
|
||||
events: {
|
||||
on: () => {},
|
||||
emit: () => {},
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
ctx.ctrl = new SingleStatCtrl($scope, $injector, {} as LinkSrv, $sanitize);
|
||||
setupFunc();
|
||||
ctx.ctrl.onSnapshotLoad(ctx.input);
|
||||
ctx.data = ctx.ctrl.data;
|
||||
});
|
||||
};
|
||||
|
||||
func(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
singleStatScenario('with defaults', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [
|
||||
{
|
||||
target: 'test.cpu1',
|
||||
datapoints: [
|
||||
[10, 1],
|
||||
[20, 2],
|
||||
],
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
it('Should use series avg as default main value', () => {
|
||||
expect(ctx.data.value).toBe(15);
|
||||
});
|
||||
|
||||
it('should set formatted falue', () => {
|
||||
expect(ctx.data.display!.text).toBe('15');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing serie name instead of value', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [
|
||||
{
|
||||
target: 'test.cpu1',
|
||||
datapoints: [
|
||||
[10, 1],
|
||||
[20, 2],
|
||||
],
|
||||
},
|
||||
];
|
||||
ctx.ctrl.panel.valueName = 'name';
|
||||
});
|
||||
|
||||
it('Should use series avg as default main value', () => {
|
||||
const name = getFieldDisplayName(ctx.data.field!);
|
||||
expect(name).toBe('test.cpu1');
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.display!.text).toBe('test.cpu1');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last iso time instead of value', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [
|
||||
{
|
||||
target: 'test.cpu1',
|
||||
datapoints: [
|
||||
[10, 12],
|
||||
[20, 1505634997920],
|
||||
],
|
||||
},
|
||||
];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeAsIso';
|
||||
ctx.ctrl.dashboard.getTimezone = () => 'browser';
|
||||
});
|
||||
|
||||
it('Should use time instead of value', () => {
|
||||
expect(ctx.data.value).toBe(1505634997920);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(dateTime(ctx.data.display!.text).valueOf()).toBe(1505634997000);
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last iso time instead of value (in UTC)', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [
|
||||
{
|
||||
target: 'test.cpu1',
|
||||
datapoints: [
|
||||
[10, 12],
|
||||
[20, 5000],
|
||||
],
|
||||
},
|
||||
];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeAsIso';
|
||||
ctx.ctrl.dashboard.getTimezone = () => 'utc';
|
||||
});
|
||||
|
||||
it('should set value', () => {
|
||||
expect(ctx.data.display!.text).toBe('1970-01-01 00:00:05');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last us time instead of value', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [
|
||||
{
|
||||
target: 'test.cpu1',
|
||||
datapoints: [
|
||||
[10, 12],
|
||||
[20, 1505634997920],
|
||||
],
|
||||
},
|
||||
];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeAsUS';
|
||||
ctx.ctrl.dashboard.getTimezone = () => 'browser';
|
||||
});
|
||||
|
||||
it('Should use time instead of value', () => {
|
||||
expect(ctx.data.value).toBe(1505634997920);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.display!.text).toBe(dateTime(1505634997920).format('MM/DD/YYYY h:mm:ss a'));
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last us time instead of value (in UTC)', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [
|
||||
{
|
||||
target: 'test.cpu1',
|
||||
datapoints: [
|
||||
[10, 12],
|
||||
[20, 5000],
|
||||
],
|
||||
},
|
||||
];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeAsUS';
|
||||
ctx.ctrl.dashboard.getTimezone = () => 'utc';
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.display!.text).toBe('01/01/1970 12:00:05 am');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last time from now instead of value', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [
|
||||
{
|
||||
target: 'test.cpu1',
|
||||
datapoints: [
|
||||
[10, 12],
|
||||
[20, 1505634997920],
|
||||
],
|
||||
},
|
||||
];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeFromNow';
|
||||
});
|
||||
|
||||
it('Should use time instead of value', () => {
|
||||
expect(ctx.data.value).toBe(1505634997920);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.display!.text).toBe('2 days ago');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('showing last time from now instead of value (in UTC)', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [
|
||||
{
|
||||
target: 'test.cpu1',
|
||||
datapoints: [
|
||||
[10, 12],
|
||||
[20, 1505634997920],
|
||||
],
|
||||
},
|
||||
];
|
||||
ctx.ctrl.panel.valueName = 'last_time';
|
||||
ctx.ctrl.panel.format = 'dateTimeFromNow';
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.display!.text).toBe('2 days ago');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario(
|
||||
'MainValue should use same number for decimals as displayed when checking thresholds',
|
||||
(ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [
|
||||
{
|
||||
target: 'test.cpu1',
|
||||
datapoints: [
|
||||
[99.999, 1],
|
||||
[99.99999, 2],
|
||||
],
|
||||
},
|
||||
];
|
||||
ctx.ctrl.panel.valueName = 'avg';
|
||||
ctx.ctrl.panel.format = 'none';
|
||||
});
|
||||
|
||||
it('Should be rounded', () => {
|
||||
expect(ctx.data.value).toBe(99.999495);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.display!.text).toBe('100.0');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
singleStatScenario('When value to text mapping is specified', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[9.9, 1]] }];
|
||||
ctx.ctrl.panel.valueMaps = [{ value: '9.9', text: 'OK' }];
|
||||
});
|
||||
|
||||
it('value should remain', () => {
|
||||
expect(ctx.data.value).toBe(9.9);
|
||||
});
|
||||
|
||||
it('Should replace value with text', () => {
|
||||
expect(ctx.data.display!.text).toBe('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When mapping null values and no data', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = []; // No data
|
||||
ctx.ctrl.panel.valueMaps = [{ value: 'null', text: 'XYZ' }];
|
||||
});
|
||||
|
||||
it('value should be null', () => {
|
||||
expect(ctx.data.value).toBe(null);
|
||||
});
|
||||
|
||||
it('Should replace value with text', () => {
|
||||
expect(ctx.data.display!.text).toBe('XYZ');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for first range', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[41, 50]] }];
|
||||
ctx.ctrl.panel.mappingType = 2;
|
||||
ctx.ctrl.panel.rangeMaps = [
|
||||
{ from: '10', to: '50', text: 'OK' },
|
||||
{ from: '51', to: '100', text: 'NOT OK' },
|
||||
];
|
||||
});
|
||||
|
||||
it('Should replace value with text OK', () => {
|
||||
expect(ctx.data.display!.text).toBe('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for other ranges', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = [{ target: 'test.cpu1', datapoints: [[65, 75]] }];
|
||||
ctx.ctrl.panel.mappingType = 2;
|
||||
ctx.ctrl.panel.rangeMaps = [
|
||||
{ from: '10', to: '50', text: 'OK' },
|
||||
{ from: '51', to: '100', text: 'NOT OK' },
|
||||
];
|
||||
});
|
||||
|
||||
it('Should replace value with text NOT OK', () => {
|
||||
expect(ctx.data.display!.text).toBe('NOT OK');
|
||||
});
|
||||
});
|
||||
|
||||
describe('When table data', () => {
|
||||
const tableData = [
|
||||
{
|
||||
columns: [{ text: 'Time', type: 'time' }, { text: 'test1' }, { text: 'mean' }, { text: 'test2' }],
|
||||
rows: [[1492759673649, 'ignore1', 15, 'ignore2']],
|
||||
type: 'table',
|
||||
},
|
||||
];
|
||||
|
||||
singleStatScenario('with default values', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = tableData;
|
||||
ctx.ctrl.panel.tableColumn = 'mean';
|
||||
ctx.ctrl.panel.format = 'none';
|
||||
});
|
||||
|
||||
it('Should use first rows value as default main value', () => {
|
||||
expect(ctx.data.value).toBe(15);
|
||||
});
|
||||
|
||||
it('should set formatted value', () => {
|
||||
expect(ctx.data.display!.text).toBe('15');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When table data has multiple columns', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = tableData;
|
||||
ctx.ctrl.panel.tableColumn = '';
|
||||
});
|
||||
|
||||
it('Should set column to first column that is not time', () => {
|
||||
expect(ctx.ctrl.panel.tableColumn).toBe('test1');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario(
|
||||
'MainValue should use same number for decimals as displayed when checking thresholds',
|
||||
(ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 99.99999, 'ignore2'];
|
||||
ctx.ctrl.panel.mappingType = 0;
|
||||
ctx.ctrl.panel.tableColumn = 'mean';
|
||||
});
|
||||
|
||||
it('Should be rounded', () => {
|
||||
expect(ctx.data.value).toBe(99.99999);
|
||||
});
|
||||
|
||||
it('should set formatted falue', () => {
|
||||
expect(ctx.data.display!.text).toBe('100.0');
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
singleStatScenario('When value to text mapping is specified', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 10, 'ignore2'];
|
||||
ctx.ctrl.panel.mappingType = 1;
|
||||
ctx.ctrl.panel.tableColumn = 'mean';
|
||||
ctx.ctrl.panel.valueMaps = [{ value: '10', text: 'OK' }];
|
||||
});
|
||||
|
||||
it('value should remain', () => {
|
||||
expect(ctx.data.value).toBe(10);
|
||||
});
|
||||
|
||||
it('Should replace value with text', () => {
|
||||
expect(ctx.data.display!.text).toBe('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for first range', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 41, 'ignore2'];
|
||||
ctx.ctrl.panel.tableColumn = 'mean';
|
||||
ctx.ctrl.panel.mappingType = 2;
|
||||
ctx.ctrl.panel.rangeMaps = [
|
||||
{ from: '10', to: '50', text: 'OK' },
|
||||
{ from: '51', to: '100', text: 'NOT OK' },
|
||||
];
|
||||
});
|
||||
|
||||
it('Should replace value with text OK', () => {
|
||||
expect(ctx.data.display!.text).toBe('OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When range to text mapping is specified for other ranges', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 65, 'ignore2'];
|
||||
ctx.ctrl.panel.tableColumn = 'mean';
|
||||
ctx.ctrl.panel.mappingType = 2;
|
||||
ctx.ctrl.panel.rangeMaps = [
|
||||
{ from: '10', to: '50', text: 'OK' },
|
||||
{ from: '51', to: '100', text: 'NOT OK' },
|
||||
];
|
||||
});
|
||||
|
||||
it('Should replace value with text NOT OK', () => {
|
||||
expect(ctx.data.display!.text).toBe('NOT OK');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When value is string', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 65, 'ignore2'];
|
||||
ctx.ctrl.panel.tableColumn = 'test1';
|
||||
ctx.ctrl.panel.valueName = ReducerID.first;
|
||||
});
|
||||
|
||||
it('Should replace value with text NOT OK', () => {
|
||||
expect(ctx.data.display!.text).toBe('ignore1');
|
||||
});
|
||||
});
|
||||
|
||||
singleStatScenario('When value is zero', (ctx: TestContext) => {
|
||||
ctx.setup(() => {
|
||||
ctx.input = tableData;
|
||||
ctx.input[0].rows[0] = [1492759673649, 'ignore1', 0, 'ignore2'];
|
||||
ctx.ctrl.panel.tableColumn = 'mean';
|
||||
});
|
||||
|
||||
it('Should return zero', () => {
|
||||
expect(ctx.data.value).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
import { getColorForValue } from '../module';
|
||||
|
||||
describe('grafanaSingleStat', () => {
|
||||
describe('legacy thresholds', () => {
|
||||
describe('positive thresholds', () => {
|
||||
const data: any = {
|
||||
colorMap: ['green', 'yellow', 'red'],
|
||||
thresholds: [20, 50],
|
||||
};
|
||||
|
||||
it('5 should return green', () => {
|
||||
expect(getColorForValue(data, 5)).toBe('green');
|
||||
});
|
||||
|
||||
it('19.9 should return green', () => {
|
||||
expect(getColorForValue(data, 19.9)).toBe('green');
|
||||
});
|
||||
|
||||
it('20 should return yellow', () => {
|
||||
expect(getColorForValue(data, 20)).toBe('yellow');
|
||||
});
|
||||
|
||||
it('20.1 should return yellow', () => {
|
||||
expect(getColorForValue(data, 20.1)).toBe('yellow');
|
||||
});
|
||||
|
||||
it('25 should return yellow', () => {
|
||||
expect(getColorForValue(data, 25)).toBe('yellow');
|
||||
});
|
||||
|
||||
it('50 should return red', () => {
|
||||
expect(getColorForValue(data, 50)).toBe('red');
|
||||
});
|
||||
|
||||
it('55 should return red', () => {
|
||||
expect(getColorForValue(data, 55)).toBe('red');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('negative thresholds', () => {
|
||||
const data: any = {
|
||||
colorMap: ['green', 'yellow', 'red'],
|
||||
thresholds: [0, 20],
|
||||
};
|
||||
|
||||
it('-30 should return green', () => {
|
||||
expect(getColorForValue(data, -30)).toBe('green');
|
||||
});
|
||||
|
||||
it('1 should return green', () => {
|
||||
expect(getColorForValue(data, 1)).toBe('yellow');
|
||||
});
|
||||
|
||||
it('22 should return green', () => {
|
||||
expect(getColorForValue(data, 22)).toBe('red');
|
||||
});
|
||||
});
|
||||
|
||||
describe('negative thresholds', () => {
|
||||
const data: any = {
|
||||
colorMap: ['green', 'yellow', 'red'],
|
||||
thresholds: [-27, 20],
|
||||
};
|
||||
|
||||
it('-30 should return green', () => {
|
||||
expect(getColorForValue(data, -26)).toBe('yellow');
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue