Merge branch 'master' into metric-segment-remake
@@ -1,18 +1,17 @@
|
||||
{
|
||||
"revision": 5,
|
||||
"title": "TestData - Graph Panel Last 1h",
|
||||
"tags": [
|
||||
"grafana-test"
|
||||
],
|
||||
"style": "dark",
|
||||
"timezone": "browser",
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"editable": true,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"hideControls": false,
|
||||
"sharedCrosshair": false,
|
||||
"links": [],
|
||||
"refresh": false,
|
||||
"revision": 8,
|
||||
"rows": [
|
||||
{
|
||||
"collapse": false,
|
||||
"editable": true,
|
||||
"height": "250px",
|
||||
"panels": [
|
||||
{
|
||||
@@ -23,7 +22,6 @@
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 1,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
@@ -97,7 +95,6 @@
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 2,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
@@ -171,7 +168,6 @@
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 3,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
@@ -238,11 +234,15 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
"repeat": null,
|
||||
"repeatIteration": null,
|
||||
"repeatRowId": null,
|
||||
"showTitle": false,
|
||||
"title": "New row",
|
||||
"titleSize": "h6"
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
"editable": true,
|
||||
"height": "250px",
|
||||
"panels": [
|
||||
{
|
||||
@@ -253,7 +253,6 @@
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 4,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
@@ -324,7 +323,6 @@
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"id": 6,
|
||||
"isNew": true,
|
||||
"links": [],
|
||||
"mode": "markdown",
|
||||
"span": 4,
|
||||
@@ -332,11 +330,15 @@
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
"repeat": null,
|
||||
"repeatIteration": null,
|
||||
"repeatRowId": null,
|
||||
"showTitle": false,
|
||||
"title": "New row",
|
||||
"titleSize": "h6"
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
"editable": true,
|
||||
"height": 336,
|
||||
"panels": [
|
||||
{
|
||||
@@ -347,7 +349,6 @@
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 5,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
@@ -371,7 +372,7 @@
|
||||
"yaxis": 2
|
||||
}
|
||||
],
|
||||
"span": 7.99561403508772,
|
||||
"span": 8,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
@@ -429,19 +430,22 @@
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"id": 7,
|
||||
"isNew": true,
|
||||
"links": [],
|
||||
"mode": "markdown",
|
||||
"span": 4.00438596491228,
|
||||
"span": 4,
|
||||
"title": "",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
"repeat": null,
|
||||
"repeatIteration": null,
|
||||
"repeatRowId": null,
|
||||
"showTitle": false,
|
||||
"title": "New row",
|
||||
"titleSize": "h6"
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
"editable": true,
|
||||
"height": "250px",
|
||||
"panels": [
|
||||
{
|
||||
@@ -452,7 +456,6 @@
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 8,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
@@ -526,7 +529,6 @@
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 10,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
@@ -545,7 +547,7 @@
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 3,
|
||||
"span": 4,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
@@ -592,6 +594,29 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": "Should be a long line connecting the null region in the `connected` mode, and in zero it should just be a line with zero value at the null points. ",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"id": 13,
|
||||
"links": [],
|
||||
"mode": "markdown",
|
||||
"span": 4,
|
||||
"title": "",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"repeat": null,
|
||||
"repeatIteration": null,
|
||||
"repeatRowId": null,
|
||||
"showTitle": false,
|
||||
"title": "New row",
|
||||
"titleSize": "h6"
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
"height": 250,
|
||||
"panels": [
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
@@ -600,7 +625,6 @@
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 9,
|
||||
"isNew": true,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
@@ -624,7 +648,7 @@
|
||||
"zindex": -3
|
||||
}
|
||||
],
|
||||
"span": 5,
|
||||
"span": 8,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
@@ -687,11 +711,708 @@
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": "Stacking values on top of nulls, should treat the null values as zero. ",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"id": 14,
|
||||
"links": [],
|
||||
"mode": "markdown",
|
||||
"span": 4,
|
||||
"title": "",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
"repeat": null,
|
||||
"repeatIteration": null,
|
||||
"repeatRowId": null,
|
||||
"showTitle": false,
|
||||
"title": "Dashboard Row",
|
||||
"titleSize": "h6"
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
"height": 250,
|
||||
"panels": [
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"fill": 1,
|
||||
"id": 12,
|
||||
"legend": {
|
||||
"avg": false,
|
||||
"current": false,
|
||||
"max": false,
|
||||
"min": false,
|
||||
"show": true,
|
||||
"total": false,
|
||||
"values": false
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 2,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [
|
||||
{
|
||||
"alias": "B-series",
|
||||
"zindex": -3
|
||||
}
|
||||
],
|
||||
"span": 8,
|
||||
"stack": true,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"hide": false,
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"hide": false,
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"hide": false,
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,40,null,null,null,null,null,null,100,10,10,20,30,40,10",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Stacking all series null segment",
|
||||
"tooltip": {
|
||||
"msResolution": false,
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "cumulative"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"content": "Stacking when all values are null should leave a gap in the graph",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"id": 15,
|
||||
"links": [],
|
||||
"mode": "markdown",
|
||||
"span": 4,
|
||||
"title": "",
|
||||
"type": "text"
|
||||
}
|
||||
],
|
||||
"repeat": null,
|
||||
"repeatIteration": null,
|
||||
"repeatRowId": null,
|
||||
"showTitle": false,
|
||||
"title": "Dashboard Row",
|
||||
"titleSize": "h6"
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
"height": 250,
|
||||
"panels": [
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"decimals": 3,
|
||||
"fill": 1,
|
||||
"id": 20,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": true,
|
||||
"max": true,
|
||||
"min": true,
|
||||
"show": true,
|
||||
"total": true,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 12,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Legend Table Single Series Should Take Minium Height",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"repeat": null,
|
||||
"repeatIteration": null,
|
||||
"repeatRowId": null,
|
||||
"showTitle": false,
|
||||
"title": "Dashboard Row",
|
||||
"titleSize": "h6"
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
"height": 250,
|
||||
"panels": [
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"decimals": 3,
|
||||
"fill": 1,
|
||||
"id": 16,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": true,
|
||||
"max": true,
|
||||
"min": true,
|
||||
"show": true,
|
||||
"total": true,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 6,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "D",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Legend Table No Scroll Visible",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"decimals": 3,
|
||||
"fill": 1,
|
||||
"id": 17,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": true,
|
||||
"max": true,
|
||||
"min": true,
|
||||
"show": true,
|
||||
"total": true,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 6,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "D",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "E",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "F",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "G",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "H",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "I",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "J",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Legend Table Should Scroll",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"repeat": null,
|
||||
"repeatIteration": null,
|
||||
"repeatRowId": null,
|
||||
"showTitle": false,
|
||||
"title": "Dashboard Row",
|
||||
"titleSize": "h6"
|
||||
},
|
||||
{
|
||||
"collapse": false,
|
||||
"height": 250,
|
||||
"panels": [
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"decimals": 3,
|
||||
"fill": 1,
|
||||
"id": 18,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": true,
|
||||
"max": true,
|
||||
"min": true,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": true,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 6,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "D",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Legend Table No Scroll Visible",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aliasColors": {},
|
||||
"bars": false,
|
||||
"datasource": "Grafana TestData",
|
||||
"decimals": 3,
|
||||
"fill": 1,
|
||||
"id": 19,
|
||||
"legend": {
|
||||
"alignAsTable": true,
|
||||
"avg": true,
|
||||
"current": true,
|
||||
"max": true,
|
||||
"min": true,
|
||||
"rightSide": true,
|
||||
"show": true,
|
||||
"total": true,
|
||||
"values": true
|
||||
},
|
||||
"lines": true,
|
||||
"linewidth": 1,
|
||||
"links": [],
|
||||
"nullPointMode": "null",
|
||||
"percentage": false,
|
||||
"pointradius": 5,
|
||||
"points": false,
|
||||
"renderer": "flot",
|
||||
"seriesOverrides": [],
|
||||
"span": 6,
|
||||
"stack": false,
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "C",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "D",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "E",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "F",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "G",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "H",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "I",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "J",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "K",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
},
|
||||
{
|
||||
"refId": "L",
|
||||
"scenarioId": "csv_metric_values",
|
||||
"stringInput": "1,20,90,30,5,0",
|
||||
"target": ""
|
||||
}
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Legend Table No Scroll Visible",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
"xaxis": {
|
||||
"mode": "time",
|
||||
"name": null,
|
||||
"show": true,
|
||||
"values": []
|
||||
},
|
||||
"yaxes": [
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"format": "short",
|
||||
"label": null,
|
||||
"logBase": 1,
|
||||
"max": null,
|
||||
"min": null,
|
||||
"show": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"repeat": null,
|
||||
"repeatIteration": null,
|
||||
"repeatRowId": null,
|
||||
"showTitle": false,
|
||||
"title": "Dashboard Row",
|
||||
"titleSize": "h6"
|
||||
}
|
||||
],
|
||||
"schemaVersion": 14,
|
||||
"style": "dark",
|
||||
"tags": [
|
||||
"grafana-test"
|
||||
],
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
@@ -721,15 +1442,7 @@
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"annotations": {
|
||||
"list": []
|
||||
},
|
||||
"refresh": false,
|
||||
"schemaVersion": 13,
|
||||
"version": 13,
|
||||
"links": [],
|
||||
"gnetId": null
|
||||
"timezone": "browser",
|
||||
"title": "TestData - Graph Panel Last 1h",
|
||||
"version": 2
|
||||
}
|
||||
|
||||
@@ -4,9 +4,12 @@ import _ from 'lodash';
|
||||
import angular from 'angular';
|
||||
|
||||
class TestDataDatasource {
|
||||
id: any;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private $q) {}
|
||||
constructor(instanceSettings, private backendSrv, private $q) {
|
||||
this.id = instanceSettings.id;
|
||||
}
|
||||
|
||||
query(options) {
|
||||
var queries = _.filter(options.targets, item => {
|
||||
@@ -19,6 +22,7 @@ class TestDataDatasource {
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
stringInput: item.stringInput,
|
||||
jsonInput: angular.fromJson(item.jsonInput),
|
||||
datasourceId: this.id,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "",
|
||||
|
||||
1
public/app/plugins/app/testdata/module.ts
vendored
@@ -5,6 +5,7 @@ export class ConfigCtrl {
|
||||
|
||||
appEditCtrl: any;
|
||||
|
||||
/** @ngInject **/
|
||||
constructor(private backendSrv) {
|
||||
this.appEditCtrl.setPreUpdateHook(this.initDatasource.bind(this));
|
||||
}
|
||||
|
||||
4
public/app/plugins/app/testdata/plugin.json
vendored
@@ -7,9 +7,9 @@
|
||||
"description": "Grafana test data app",
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"version": "1.0.14",
|
||||
"version": "1.0.17",
|
||||
"updated": "2016-09-26"
|
||||
},
|
||||
|
||||
|
||||
45
public/app/plugins/datasource/cloudwatch/config_ctrl.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
export class CloudWatchConfigCtrl {
|
||||
static templateUrl = 'partials/config.html';
|
||||
current: any;
|
||||
|
||||
accessKeyExist = false;
|
||||
secretKeyExist = false;
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope) {
|
||||
this.current.jsonData.timeField = this.current.jsonData.timeField || '@timestamp';
|
||||
this.current.jsonData.authType = this.current.jsonData.authType || 'credentials';
|
||||
|
||||
this.accessKeyExist = this.current.secureJsonFields.accessKey;
|
||||
this.secretKeyExist = this.current.secureJsonFields.secretKey;
|
||||
}
|
||||
|
||||
resetAccessKey() {
|
||||
this.accessKeyExist = false;
|
||||
}
|
||||
|
||||
resetSecretKey() {
|
||||
this.secretKeyExist = false;
|
||||
}
|
||||
|
||||
authTypes = [
|
||||
{name: 'Access & secret key', value: 'keys'},
|
||||
{name: 'Credentials file', value: 'credentials'},
|
||||
{name: 'ARN', value: 'arn'},
|
||||
];
|
||||
|
||||
indexPatternTypes = [
|
||||
{name: 'No pattern', value: undefined},
|
||||
{name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH'},
|
||||
{name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD'},
|
||||
{name: 'Weekly', value: 'Weekly', example: '[logstash-]GGGG.WW'},
|
||||
{name: 'Monthly', value: 'Monthly', example: '[logstash-]YYYY.MM'},
|
||||
{name: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY'},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ define([
|
||||
'moment',
|
||||
'app/core/utils/datemath',
|
||||
'app/core/utils/kbn',
|
||||
'app/features/templating/variable',
|
||||
'./annotation_query',
|
||||
],
|
||||
function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnotationQuery) {
|
||||
'use strict';
|
||||
|
||||
/** @ngInject */
|
||||
@@ -16,6 +17,13 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
this.supportMetrics = true;
|
||||
this.proxyUrl = instanceSettings.url;
|
||||
this.defaultRegion = instanceSettings.jsonData.defaultRegion;
|
||||
this.standardStatistics = [
|
||||
'Average',
|
||||
'Maximum',
|
||||
'Minimum',
|
||||
'Sum',
|
||||
'SampleCount'
|
||||
];
|
||||
|
||||
var self = this;
|
||||
this.query = function(options) {
|
||||
@@ -24,7 +32,7 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
|
||||
var queries = [];
|
||||
options = angular.copy(options);
|
||||
options.targets = this.expandTemplateVariable(options.targets, templateSrv);
|
||||
options.targets = this.expandTemplateVariable(options.targets, options.scopedVars, templateSrv);
|
||||
_.each(options.targets, function(target) {
|
||||
if (target.hide || !target.namespace || !target.metricName || _.isEmpty(target.statistics)) {
|
||||
return;
|
||||
@@ -37,7 +45,8 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
query.dimensions = self.convertDimensionFormat(target.dimensions, options.scopedVars);
|
||||
query.statistics = target.statistics;
|
||||
|
||||
var period = this._getPeriod(target, query, options, start, end);
|
||||
var now = Math.round(Date.now() / 1000);
|
||||
var period = this._getPeriod(target, query, options, start, end, now);
|
||||
target.period = period;
|
||||
query.period = period;
|
||||
|
||||
@@ -67,28 +76,38 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
});
|
||||
};
|
||||
|
||||
this._getPeriod = function(target, query, options, start, end) {
|
||||
this._getPeriod = function(target, query, options, start, end, now) {
|
||||
var period;
|
||||
var range = end - start;
|
||||
|
||||
if (!target.period) {
|
||||
var daySec = 60 * 60 * 24;
|
||||
var periodUnit = 60;
|
||||
if (now - start > (daySec * 15)) { // until 63 days ago
|
||||
periodUnit = period = 60 * 5;
|
||||
} else if (now - start > (daySec * 63)) { // until 455 days ago
|
||||
periodUnit = period = 60 * 60;
|
||||
} else if (now - start > (daySec * 455)) { // over 455 days, should return error, but try to long period
|
||||
periodUnit = period = 60 * 60;
|
||||
} else if (!target.period) {
|
||||
period = (query.namespace === 'AWS/EC2') ? 300 : 60;
|
||||
} else if (/^\d+$/.test(target.period)) {
|
||||
period = parseInt(target.period, 10);
|
||||
} else {
|
||||
period = kbn.interval_to_seconds(templateSrv.replace(target.period, options.scopedVars));
|
||||
}
|
||||
if (query.period < 60) {
|
||||
if (period < 60) {
|
||||
period = 60;
|
||||
}
|
||||
if (range / query.period >= 1440) {
|
||||
period = Math.ceil(range / 1440 / 60) * 60;
|
||||
if (range / period >= 1440) {
|
||||
period = Math.ceil(range / 1440 / periodUnit) * periodUnit;
|
||||
}
|
||||
|
||||
return period;
|
||||
};
|
||||
|
||||
this.performTimeSeriesQuery = function(query, start, end) {
|
||||
var statistics = _.filter(query.statistics, function(s) { return _.includes(self.standardStatistics, s); });
|
||||
var extendedStatistics = _.reject(query.statistics, function(s) { return _.includes(self.standardStatistics, s); });
|
||||
return this.awsRequest({
|
||||
region: query.region,
|
||||
action: 'GetMetricStatistics',
|
||||
@@ -96,7 +115,8 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
namespace: query.namespace,
|
||||
metricName: query.metricName,
|
||||
dimensions: query.dimensions,
|
||||
statistics: query.statistics,
|
||||
statistics: statistics,
|
||||
extendedStatistics: extendedStatistics,
|
||||
startTime: start,
|
||||
endTime: end,
|
||||
period: query.period
|
||||
@@ -259,10 +279,19 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
};
|
||||
|
||||
this.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) {
|
||||
var s = _.includes(self.standardStatistics, statistic) ? statistic : '';
|
||||
var es = _.includes(self.standardStatistics, statistic) ? '' : statistic;
|
||||
return this.awsRequest({
|
||||
region: region,
|
||||
action: 'DescribeAlarmsForMetric',
|
||||
parameters: { namespace: namespace, metricName: metricName, dimensions: dimensions, statistic: statistic, period: period }
|
||||
parameters: {
|
||||
namespace: namespace,
|
||||
metricName: metricName,
|
||||
dimensions: dimensions,
|
||||
statistic: s,
|
||||
extendedStatistic: es,
|
||||
period: period
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -329,6 +358,7 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
var periodMs = options.period * 1000;
|
||||
|
||||
return _.map(options.statistics, function(stat) {
|
||||
var extended = !_.includes(self.standardStatistics, stat);
|
||||
var dps = [];
|
||||
var lastTimestamp = null;
|
||||
_.chain(md.Datapoints)
|
||||
@@ -341,7 +371,11 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
dps.push([null, lastTimestamp + periodMs]);
|
||||
}
|
||||
lastTimestamp = timestamp;
|
||||
dps.push([dp[stat], timestamp]);
|
||||
if (!extended) {
|
||||
dps.push([dp[stat], timestamp]);
|
||||
} else {
|
||||
dps.push([dp.ExtendedStatistics[stat], timestamp]);
|
||||
}
|
||||
})
|
||||
.value();
|
||||
|
||||
@@ -357,31 +391,42 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) {
|
||||
});
|
||||
}
|
||||
|
||||
this.getExpandedVariables = function(target, dimensionKey, variable) {
|
||||
this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) {
|
||||
/* if the all checkbox is marked we should add all values to the targets */
|
||||
var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
|
||||
return _.chain(variable.options)
|
||||
.filter(function(v) {
|
||||
return v.selected;
|
||||
if (allSelected) {
|
||||
return v.text !== 'All';
|
||||
} else {
|
||||
return v.selected;
|
||||
}
|
||||
})
|
||||
.map(function(v) {
|
||||
var t = angular.copy(target);
|
||||
t.dimensions[dimensionKey] = v.value;
|
||||
var scopedVar = {};
|
||||
scopedVar[variable.name] = v;
|
||||
t.dimensions[dimensionKey] = templateSrv.replace(t.dimensions[dimensionKey], scopedVar);
|
||||
return t;
|
||||
}).value();
|
||||
};
|
||||
|
||||
this.expandTemplateVariable = function(targets, templateSrv) {
|
||||
this.expandTemplateVariable = function(targets, scopedVars, templateSrv) {
|
||||
var self = this;
|
||||
return _.chain(targets)
|
||||
.map(function(target) {
|
||||
var dimensionKey = _.findKey(target.dimensions, function(v) {
|
||||
return templateSrv.variableExists(v);
|
||||
return templateSrv.variableExists(v) && !_.has(scopedVars, templateSrv.getVariableName(v));
|
||||
});
|
||||
|
||||
if (dimensionKey) {
|
||||
var variable = _.find(templateSrv.variables, function(variable) {
|
||||
return templateSrv.containsVariable(target.dimensions[dimensionKey], variable.name);
|
||||
var multiVariable = _.find(templateSrv.variables, function(variable) {
|
||||
return templatingVariable.containsVariable(target.dimensions[dimensionKey], variable.name) && variable.multi;
|
||||
});
|
||||
return self.getExpandedVariables(target, dimensionKey, variable);
|
||||
var variable = _.find(templateSrv.variables, function(variable) {
|
||||
return templatingVariable.containsVariable(target.dimensions[dimensionKey], variable.name);
|
||||
});
|
||||
return self.getExpandedVariables(target, dimensionKey, multiVariable || variable, templateSrv);
|
||||
} else {
|
||||
return [target];
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@ import './query_parameter_ctrl';
|
||||
|
||||
import {CloudWatchDatasource} from './datasource';
|
||||
import {CloudWatchQueryCtrl} from './query_ctrl';
|
||||
|
||||
class CloudWatchConfigCtrl {
|
||||
static templateUrl = 'partials/config.html';
|
||||
}
|
||||
import {CloudWatchConfigCtrl} from './config_ctrl';
|
||||
|
||||
class CloudWatchAnnotationsQueryCtrl {
|
||||
static templateUrl = 'partials/annotations.editor.html';
|
||||
|
||||
@@ -1,34 +1,55 @@
|
||||
<h3 class="page-heading">CloudWatch details</h3>
|
||||
|
||||
<div class="gf-form-group max-width-30">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-13">Credentials profile name</label>
|
||||
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.database' placeholder="default"></input>
|
||||
<info-popover mode="right-absolute">
|
||||
Credentials profile name, as specified in ~/.aws/credentials, leave blank for default
|
||||
</info-popover>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-13">Default Region</label>
|
||||
<div class="gf-form-select-wrapper max-width-18 gf-form-select-wrapper--has-help-icon">
|
||||
<select class="gf-form-input" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'sa-east-1', 'us-east-1', 'us-west-1', 'us-west-2']"></select>
|
||||
<info-popover mode="right-absolute">
|
||||
Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.
|
||||
</info-popover>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-13">Custom Metrics namespace</label>
|
||||
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.customMetricsNamespaces' placeholder="Namespace1,Namespace2"></input>
|
||||
<info-popover mode="right-absolute">
|
||||
Namespaces of Custom Metrics
|
||||
</info-popover>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-13">Assume Role ARN</label>
|
||||
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.assumeRoleArn' placeholder="arn:aws:iam:*"></input>
|
||||
<info-popover mode="right-absolute">
|
||||
ARN of Assume Role
|
||||
</info-popover>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-13">Auth Provider</label>
|
||||
<select class="gf-form-input gf-max-width-13" ng-model="ctrl.current.jsonData.authType" ng-options="f.value as f.name for f in ctrl.authTypes"></select>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-show='ctrl.current.jsonData.authType == "credentials"'>
|
||||
<label class="gf-form-label width-13">Credentials profile name</label>
|
||||
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.database' placeholder="default"></input>
|
||||
<info-popover mode="right-absolute">
|
||||
Credentials profile name, as specified in ~/.aws/credentials, leave blank for default
|
||||
</info-popover>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-show='ctrl.current.jsonData.authType == "keys"'>
|
||||
<label class="gf-form-label width-13">Access key ID </label>
|
||||
<label class="gf-form-label width-13" ng-show="ctrl.accessKeyExist">Configured</label>
|
||||
<a class="btn btn-secondary gf-form-btn" type="submit" ng-click="ctrl.resetAccessKey()" ng-show="ctrl.accessKeyExist">Reset</a>
|
||||
<input type="text" class="gf-form-input max-width-18" ng-hide="ctrl.accessKeyExist" ng-model='ctrl.current.secureJsonData.accessKey'></input>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-show='ctrl.current.jsonData.authType == "keys"'>
|
||||
<label class="gf-form-label width-13">Secret access key</label>
|
||||
<label class="gf-form-label width-13" ng-show="ctrl.secretKeyExist">Configured</label>
|
||||
<a class="btn btn-secondary gf-form-btn" type="submit" ng-click="ctrl.resetSecretKey()" ng-show="ctrl.secretKeyExist">Reset</a>
|
||||
<input type="text" class="gf-form-input max-width-18" ng-hide="ctrl.secretKeyExist" ng-model='ctrl.current.secureJsonData.secretKey'></input>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-show='ctrl.current.jsonData.authType == "arn"'>
|
||||
<label class="gf-form-label width-13">Assume Role ARN</label>
|
||||
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.assumeRoleArn' placeholder="arn:aws:iam:*"></input>
|
||||
<info-popover mode="right-absolute">
|
||||
ARN of Assume Role
|
||||
</info-popover>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-13">Default Region</label>
|
||||
<div class="gf-form-select-wrapper max-width-18 gf-form-select-wrapper--has-help-icon">
|
||||
<select class="gf-form-input" ng-model="ctrl.current.jsonData.defaultRegion" ng-options="region for region in ['ap-northeast-1', 'ap-northeast-2', 'ap-southeast-1', 'ap-southeast-2', 'ap-south-1', 'ca-central-1', 'cn-north-1', 'eu-central-1', 'eu-west-1', 'eu-west-2', 'sa-east-1', 'us-east-1', 'us-east-2', 'us-gov-west-1', 'us-west-1', 'us-west-2']"></select>
|
||||
<info-popover mode="right-absolute">
|
||||
Specify the region, such as for US West (Oregon) use ` us-west-2 ` as the region.
|
||||
</info-popover>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-13">Custom Metrics</label>
|
||||
<input type="text" class="gf-form-input max-width-18" ng-model='ctrl.current.jsonData.customMetricsNamespaces' placeholder="Namespace1,Namespace2"></input>
|
||||
<info-popover mode="right-absolute">
|
||||
Namespaces of Custom Metrics
|
||||
</info-popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/amazon-web-services.png",
|
||||
|
||||
@@ -61,14 +61,13 @@ function (angular, _) {
|
||||
};
|
||||
|
||||
$scope.getStatSegments = function() {
|
||||
return $q.when([
|
||||
return $q.when(_.flatten([
|
||||
angular.copy($scope.removeStatSegment),
|
||||
uiSegmentSrv.getSegmentForValue('Average'),
|
||||
uiSegmentSrv.getSegmentForValue('Maximum'),
|
||||
uiSegmentSrv.getSegmentForValue('Minimum'),
|
||||
uiSegmentSrv.getSegmentForValue('Sum'),
|
||||
uiSegmentSrv.getSegmentForValue('SampleCount'),
|
||||
]);
|
||||
_.map($scope.datasource.standardStatistics, function(s) {
|
||||
return uiSegmentSrv.getSegmentForValue(s);
|
||||
}),
|
||||
uiSegmentSrv.getSegmentForValue('pNN.NN'),
|
||||
]));
|
||||
};
|
||||
|
||||
$scope.statSegmentChanged = function(segment, index) {
|
||||
@@ -105,7 +104,7 @@ function (angular, _) {
|
||||
query = $scope.datasource.getDimensionKeys($scope.target.namespace, $scope.target.region);
|
||||
} else if (segment.type === 'value') {
|
||||
var dimensionKey = $scope.dimSegments[$index-2].value;
|
||||
query = $scope.datasource.getDimensionValues(target.region, target.namespace, target.metricName, dimensionKey, {});
|
||||
query = $scope.datasource.getDimensionValues(target.region, target.namespace, target.metricName, dimensionKey, target.dimensions);
|
||||
}
|
||||
|
||||
return query.then($scope.transformToSegments(true)).then(function(results) {
|
||||
|
||||
@@ -134,11 +134,19 @@ describe('CloudWatchDatasource', function() {
|
||||
{
|
||||
name: 'instance_id',
|
||||
options: [
|
||||
{ value: 'i-23456789', selected: false },
|
||||
{ value: 'i-34567890', selected: true }
|
||||
{ text: 'i-23456789', value: 'i-23456789', selected: false },
|
||||
{ text: 'i-34567890', value: 'i-34567890', selected: true }
|
||||
]
|
||||
}
|
||||
],
|
||||
replace: function (target, scopedVars) {
|
||||
if (target === '$instance_id' && scopedVars['instance_id']['text'] === 'i-34567890') {
|
||||
return 'i-34567890';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
getVariableName: function (e) { return 'instance_id'; },
|
||||
variableExists: function (e) { return true; },
|
||||
containsVariable: function (str, variableName) { return str.indexOf('$' + variableName) !== -1; }
|
||||
};
|
||||
@@ -156,11 +164,72 @@ describe('CloudWatchDatasource', function() {
|
||||
}
|
||||
];
|
||||
|
||||
var result = ctx.ds.expandTemplateVariable(targets, templateSrv);
|
||||
var result = ctx.ds.expandTemplateVariable(targets, {}, templateSrv);
|
||||
expect(result[0].dimensions.InstanceId).to.be('i-34567890');
|
||||
});
|
||||
});
|
||||
|
||||
describe('When performing CloudWatch query for extended statistics', function() {
|
||||
var requestParams;
|
||||
|
||||
var query = {
|
||||
range: { from: 'now-1h', to: 'now' },
|
||||
targets: [
|
||||
{
|
||||
region: 'us-east-1',
|
||||
namespace: 'AWS/ApplicationELB',
|
||||
metricName: 'TargetResponseTime',
|
||||
dimensions: {
|
||||
LoadBalancer: 'lb',
|
||||
TargetGroup: 'tg'
|
||||
},
|
||||
statistics: ['p90.00'],
|
||||
period: 300
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var response = {
|
||||
Datapoints: [
|
||||
{
|
||||
ExtendedStatistics: {
|
||||
'p90.00': 1
|
||||
},
|
||||
Timestamp: 'Wed Dec 31 1969 16:00:00 GMT-0800 (PST)'
|
||||
},
|
||||
{
|
||||
ExtendedStatistics: {
|
||||
'p90.00': 2
|
||||
},
|
||||
Timestamp: 'Wed Dec 31 1969 16:05:00 GMT-0800 (PST)'
|
||||
},
|
||||
{
|
||||
ExtendedStatistics: {
|
||||
'p90.00': 5
|
||||
},
|
||||
Timestamp: 'Wed Dec 31 1969 16:15:00 GMT-0800 (PST)'
|
||||
}
|
||||
],
|
||||
Label: 'TargetResponseTime'
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(params) {
|
||||
requestParams = params;
|
||||
return ctx.$q.when({data: response});
|
||||
};
|
||||
});
|
||||
|
||||
it('should return series list', function(done) {
|
||||
ctx.ds.query(query).then(function(result) {
|
||||
expect(result.data[0].target).to.be('TargetResponseTime_p90.00');
|
||||
expect(result.data[0].datapoints[0][0]).to.be(response.Datapoints[0].ExtendedStatistics['p90.00']);
|
||||
done();
|
||||
});
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
});
|
||||
|
||||
function describeMetricFindQuery(query, func) {
|
||||
describe('metricFindQuery ' + query, () => {
|
||||
let scenario: any = {};
|
||||
|
||||
@@ -50,6 +50,7 @@ function (angular, _, queryDef) {
|
||||
|
||||
switch($scope.agg.type) {
|
||||
case 'date_histogram':
|
||||
case 'histogram':
|
||||
case 'terms': {
|
||||
delete $scope.agg.query;
|
||||
$scope.agg.field = 'select field';
|
||||
@@ -73,21 +74,26 @@ function (angular, _, queryDef) {
|
||||
$scope.validateModel = function() {
|
||||
$scope.index = _.indexOf(bucketAggs, $scope.agg);
|
||||
$scope.isFirst = $scope.index === 0;
|
||||
$scope.isLast = $scope.index === bucketAggs.length - 1;
|
||||
$scope.bucketAggCount = bucketAggs.length;
|
||||
|
||||
var settingsLinkText = "";
|
||||
var settings = $scope.agg.settings || {};
|
||||
|
||||
switch($scope.agg.type) {
|
||||
case 'terms': {
|
||||
settings.order = settings.order || "asc";
|
||||
settings.order = settings.order || "desc";
|
||||
settings.size = settings.size || "10";
|
||||
settings.min_doc_count = settings.min_doc_count || 1;
|
||||
settings.orderBy = settings.orderBy || "_term";
|
||||
|
||||
if (settings.size !== '0') {
|
||||
settingsLinkText = queryDef.describeOrder(settings.order) + ' ' + settings.size + ', ';
|
||||
}
|
||||
|
||||
if (settings.min_doc_count > 0) {
|
||||
settingsLinkText += 'Min Doc Count: ' + settings.min_doc_count + ', ';
|
||||
}
|
||||
|
||||
settingsLinkText += 'Order by: ' + queryDef.describeOrderBy(settings.orderBy, $scope.target);
|
||||
|
||||
if (settings.size === '0') {
|
||||
@@ -127,6 +133,16 @@ function (angular, _, queryDef) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'histogram': {
|
||||
settings.interval = settings.interval || 1000;
|
||||
settings.min_doc_count = _.defaultTo(settings.min_doc_count, 1);
|
||||
settingsLinkText = 'Interval: ' + settings.interval;
|
||||
|
||||
if (settings.min_doc_count > 0) {
|
||||
settingsLinkText += ', Min Doc Count: ' + settings.min_doc_count;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'geohash_grid': {
|
||||
// limit precision to 7
|
||||
settings.precision = Math.max(Math.min(settings.precision, 7), 1);
|
||||
|
||||
@@ -22,8 +22,8 @@ export class ElasticConfigCtrl {
|
||||
];
|
||||
|
||||
esVersions = [
|
||||
{name: '1.x', value: 1},
|
||||
{name: '2.x', value: 2},
|
||||
{name: '5.x', value: 5},
|
||||
];
|
||||
|
||||
indexPatternTypeChanged() {
|
||||
|
||||
@@ -81,21 +81,33 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
range[timeField]= {
|
||||
from: options.range.from.valueOf(),
|
||||
to: options.range.to.valueOf(),
|
||||
format: "epoch_millis",
|
||||
};
|
||||
|
||||
if (this.esVersion >= 2) {
|
||||
range[timeField]["format"] = "epoch_millis";
|
||||
}
|
||||
|
||||
var queryInterpolated = templateSrv.replace(queryString, {}, 'lucene');
|
||||
var filter = { "bool": { "must": [{ "range": range }] } };
|
||||
var query = { "bool": { "should": [{ "query_string": { "query": queryInterpolated } }] } };
|
||||
var query = {
|
||||
"bool": {
|
||||
"filter": [
|
||||
{ "range": range },
|
||||
{
|
||||
"query_string": {
|
||||
"query": queryInterpolated
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var data = {
|
||||
"fields": [timeField, "_source"],
|
||||
"query" : { "filtered": { "query" : query, "filter": filter } },
|
||||
"query" : query,
|
||||
"size": 10000
|
||||
};
|
||||
|
||||
// fields field not supported on ES 5.x
|
||||
if (this.esVersion < 5) {
|
||||
data["fields"] = [timeField, "_source"];
|
||||
}
|
||||
|
||||
var header = {search_type: "query_then_fetch", "ignore_unavailable": true};
|
||||
|
||||
// old elastic annotations had index specified on them
|
||||
@@ -133,11 +145,12 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
|
||||
for (var i = 0; i < hits.length; i++) {
|
||||
var source = hits[i]._source;
|
||||
var fields = hits[i].fields;
|
||||
var time = source[timeField];
|
||||
|
||||
if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {
|
||||
time = fields[timeField];
|
||||
if (typeof hits[i].fields !== 'undefined') {
|
||||
var fields = hits[i].fields;
|
||||
if (_.isString(fields[timeField]) || _.isNumber(fields[timeField])) {
|
||||
time = fields[timeField];
|
||||
}
|
||||
}
|
||||
|
||||
var event = {
|
||||
@@ -155,11 +168,22 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
};
|
||||
|
||||
this.testDatasource = function() {
|
||||
return this._get('/_stats').then(function() {
|
||||
return { status: "success", message: "Data source is working", title: "Success" };
|
||||
}, function(err) {
|
||||
timeSrv.setTime({ from: 'now-1m', to: 'now' }, true);
|
||||
// validate that the index exist and has date field
|
||||
return this.getFields({type: 'date'}).then(function(dateFields) {
|
||||
var timeField = _.find(dateFields, {text: this.timeField});
|
||||
if (!timeField) {
|
||||
return { status: "error", message: "No date field named " + this.timeField + ' found', title: "Error" };
|
||||
}
|
||||
return { status: "success", message: "Index OK. Time field name OK.", title: "Success" };
|
||||
}.bind(this), function(err) {
|
||||
console.log(err);
|
||||
if (err.data && err.data.error) {
|
||||
return { status: "error", message: angular.toJson(err.data.error), title: "Error" };
|
||||
var message = angular.toJson(err.data.error);
|
||||
if (err.data.error.reason) {
|
||||
message = err.data.error.reason;
|
||||
}
|
||||
return { status: "error", message: message, title: "Error" };
|
||||
} else {
|
||||
return { status: "error", message: err.status, title: "Error" };
|
||||
}
|
||||
@@ -184,17 +208,11 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
target = options.targets[i];
|
||||
if (target.hide) {continue;}
|
||||
|
||||
var queryObj = this.queryBuilder.build(target, adhocFilters);
|
||||
var queryString = templateSrv.replace(target.query || '*', options.scopedVars, 'lucene');
|
||||
var queryObj = this.queryBuilder.build(target, adhocFilters, queryString);
|
||||
var esQuery = angular.toJson(queryObj);
|
||||
var luceneQuery = target.query || '*';
|
||||
luceneQuery = templateSrv.replace(luceneQuery, options.scopedVars, 'lucene');
|
||||
luceneQuery = angular.toJson(luceneQuery);
|
||||
|
||||
// remove inner quotes
|
||||
luceneQuery = luceneQuery.substr(1, luceneQuery.length - 2);
|
||||
esQuery = esQuery.replace("$lucene_query", luceneQuery);
|
||||
|
||||
var searchType = queryObj.size === 0 ? 'count' : 'query_then_fetch';
|
||||
var searchType = (queryObj.size === 0 && this.esVersion < 5) ? 'count' : 'query_then_fetch';
|
||||
var header = this.getQueryHeader(searchType, options.range.from, options.range.to);
|
||||
payload += header + '\n';
|
||||
|
||||
@@ -206,7 +224,6 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
return $q.when([]);
|
||||
}
|
||||
|
||||
payload = payload.replace(/\$interval/g, options.interval);
|
||||
payload = payload.replace(/\$timeFrom/g, options.range.from.valueOf());
|
||||
payload = payload.replace(/\$timeTo/g, options.range.to.valueOf());
|
||||
payload = templateSrv.replace(payload, options.scopedVars);
|
||||
@@ -218,6 +235,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
|
||||
this.getFields = function(query) {
|
||||
return this._get('/_mapping').then(function(result) {
|
||||
|
||||
var typeMap = {
|
||||
'float': 'number',
|
||||
'double': 'number',
|
||||
@@ -225,12 +243,28 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
'long': 'number',
|
||||
'date': 'date',
|
||||
'string': 'string',
|
||||
'text': 'string',
|
||||
'scaled_float': 'number',
|
||||
'nested': 'nested'
|
||||
};
|
||||
|
||||
function shouldAddField(obj, key, query) {
|
||||
if (key[0] === '_') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!query.type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// equal query type filter, or via typemap translation
|
||||
return query.type === obj.type || query.type === typeMap[obj.type];
|
||||
}
|
||||
|
||||
// Store subfield names: [system, process, cpu, total] -> system.process.cpu.total
|
||||
var fieldNameParts = [];
|
||||
var fields = {};
|
||||
|
||||
function getFieldsRecursively(obj) {
|
||||
for (var key in obj) {
|
||||
var subObj = obj[key];
|
||||
@@ -243,10 +277,7 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
var fieldName = fieldNameParts.concat(key).join('.');
|
||||
|
||||
// Hide meta-fields and check field type
|
||||
if (key[0] !== '_' &&
|
||||
(!query.type ||
|
||||
query.type && typeMap[subObj.type] === query.type)) {
|
||||
|
||||
if (shouldAddField(subObj, key, query)) {
|
||||
fields[fieldName] = {
|
||||
text: fieldName,
|
||||
type: subObj.type
|
||||
@@ -277,14 +308,15 @@ function (angular, _, moment, kbn, ElasticQueryBuilder, IndexPattern, ElasticRes
|
||||
|
||||
this.getTerms = function(queryDef) {
|
||||
var range = timeSrv.timeRange();
|
||||
var header = this.getQueryHeader('count', range.from, range.to);
|
||||
var searchType = this.esVersion >= 5 ? 'query_then_fetch' : 'count' ;
|
||||
var header = this.getQueryHeader(searchType, range.from, range.to);
|
||||
var esQuery = angular.toJson(this.queryBuilder.getTermsQuery(queryDef));
|
||||
|
||||
esQuery = esQuery.replace(/\$timeFrom/g, range.from.valueOf());
|
||||
esQuery = esQuery.replace(/\$timeTo/g, range.to.valueOf());
|
||||
esQuery = header + '\n' + esQuery + '\n';
|
||||
|
||||
return this._post('_msearch?search_type=count', esQuery).then(function(res) {
|
||||
return this._post('_msearch?search_type=' + searchType, esQuery).then(function(res) {
|
||||
if (!res.responses[0].aggregations) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -132,7 +132,14 @@ function (_, queryDef) {
|
||||
}
|
||||
default: {
|
||||
metricName = this._getMetricName(metric.type);
|
||||
doc[metricName] =bucket[metric.id].value;
|
||||
var otherMetrics = _.filter(target.metrics, {type: metric.type});
|
||||
|
||||
// if more of the same metric type include field field name in property
|
||||
if (otherMetrics.length > 1) {
|
||||
metricName += ' ' + metric.field;
|
||||
}
|
||||
|
||||
doc[metricName] = bucket[metric.id].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -166,11 +173,14 @@ function (_, queryDef) {
|
||||
for (var nameIndex in esAgg.buckets) {
|
||||
bucket = esAgg.buckets[nameIndex];
|
||||
props = _.clone(props);
|
||||
if (bucket.key) {
|
||||
if (bucket.key !== void 0) {
|
||||
props[aggDef.field] = bucket.key;
|
||||
} else {
|
||||
props["filter"] = nameIndex;
|
||||
}
|
||||
if (bucket.key_as_string) {
|
||||
props[aggDef.field] = bucket.key_as_string;
|
||||
}
|
||||
this.processBuckets(bucket, target, seriesList, docs, props, depth+1);
|
||||
}
|
||||
}
|
||||
@@ -196,7 +206,7 @@ function (_, queryDef) {
|
||||
var group = g1 || g2;
|
||||
|
||||
if (group.indexOf('term ') === 0) { return series.props[group.substring(5)]; }
|
||||
if (series.props[group]) { return series.props[group]; }
|
||||
if (series.props[group] !== void 0) { return series.props[group]; }
|
||||
if (group === 'metric') { return metricName; }
|
||||
if (group === 'field') { return series.field; }
|
||||
|
||||
|
||||
67
public/app/plugins/datasource/elasticsearch/img/elasticsearch.svg
Executable file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<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"
|
||||
viewBox="0 0 80 80" style="enable-background:new 0 0 80 80;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{clip-path:url(#SVGID_2_);fill:#F0BF1A;}
|
||||
.st1{clip-path:url(#SVGID_4_);fill:#3EBEB0;}
|
||||
.st2{clip-path:url(#SVGID_6_);fill:#07A5DE;}
|
||||
.st3{clip-path:url(#SVGID_8_);fill:#231F20;}
|
||||
.st4{fill:#D7A229;}
|
||||
.st5{fill:#019B8F;}
|
||||
.st6{fill:none;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<defs>
|
||||
<circle id="SVGID_1_" cx="40" cy="40" r="32"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_2_">
|
||||
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<path class="st0" d="M53.7,26H10c-1.1,0-2-0.9-2-2V10c0-1.1,0.9-2,2-2h57c1.1,0,2,0.9,2,2v0.7C68.9,19.1,62.1,26,53.7,26z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<defs>
|
||||
<circle id="SVGID_3_" cx="40" cy="40" r="32"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_4_">
|
||||
<use xlink:href="#SVGID_3_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<path class="st1" d="M69.1,72H8.2l0,0V54l0,0h45.7c8.4,0,15.2,6.8,15.2,15.2V72L69.1,72z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<defs>
|
||||
<circle id="SVGID_5_" cx="40" cy="40" r="32"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_6_">
|
||||
<use xlink:href="#SVGID_5_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<path class="st2" d="M50.1,49H4.8V31h45.3c5,0,9,4,9,9l0,0C59.1,45,55,49,50.1,49z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<defs>
|
||||
<circle id="SVGID_7_" cx="40" cy="40" r="32"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_8_">
|
||||
<use xlink:href="#SVGID_7_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<path class="st3" d="M36,31H6.4v18H36c0.7-2.7,1.1-5.7,1.1-9S36.7,33.7,36,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st4" d="M23.9,12.3c-5.4,3.2-9.9,8-12.7,13.7h23.6C32.4,20.5,28.6,15.9,23.9,12.3z"/>
|
||||
<path class="st5" d="M24.9,68.2c4.6-3.7,8.3-8.6,10.6-14.2H11.2C14.2,60,19,65,24.9,68.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect class="st6" width="80" height="80"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 22 KiB |
@@ -29,6 +29,7 @@ function (angular, _, queryDef) {
|
||||
$scope.metricAggTypes = queryDef.getMetricAggTypes($scope.esVersion);
|
||||
$scope.extendedStats = queryDef.extendedStats;
|
||||
$scope.pipelineAggOptions = [];
|
||||
$scope.modelSettingsValues = {};
|
||||
|
||||
$scope.init = function() {
|
||||
$scope.agg = metricAggs[$scope.index];
|
||||
@@ -66,7 +67,6 @@ function (angular, _, queryDef) {
|
||||
} else if (!$scope.agg.field) {
|
||||
$scope.agg.field = 'select field';
|
||||
}
|
||||
|
||||
switch($scope.agg.type) {
|
||||
case 'cardinality': {
|
||||
var precision_threshold = $scope.agg.settings.precision_threshold || '';
|
||||
@@ -95,13 +95,21 @@ function (angular, _, queryDef) {
|
||||
$scope.settingsLinkText = 'Stats: ' + stats.join(', ');
|
||||
break;
|
||||
}
|
||||
case 'moving_avg': {
|
||||
$scope.movingAvgModelTypes = queryDef.movingAvgModelOptions;
|
||||
$scope.modelSettings = queryDef.getMovingAvgSettings($scope.agg.settings.model, true);
|
||||
$scope.updateMovingAvgModelSettings();
|
||||
break;
|
||||
}
|
||||
case 'raw_document': {
|
||||
$scope.target.metrics = [$scope.agg];
|
||||
$scope.agg.settings.size = $scope.agg.settings.size || 500;
|
||||
$scope.settingsLinkText = 'Size: ' + $scope.agg.settings.size ;
|
||||
$scope.target.metrics.splice(0,$scope.target.metrics.length, $scope.agg);
|
||||
|
||||
$scope.target.bucketAggs = [];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($scope.aggDef.supportsInlineScript) {
|
||||
// I know this stores the inline script twice
|
||||
// but having it like this simplifes the query_builder
|
||||
@@ -127,6 +135,25 @@ function (angular, _, queryDef) {
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.updateMovingAvgModelSettings = function () {
|
||||
var modelSettingsKeys = [];
|
||||
var modelSettings = queryDef.getMovingAvgSettings($scope.agg.settings.model, false);
|
||||
for (var i=0; i < modelSettings.length; i++) {
|
||||
modelSettingsKeys.push(modelSettings[i].value);
|
||||
}
|
||||
|
||||
for (var key in $scope.agg.settings.settings) {
|
||||
if (($scope.agg.settings.settings[key] === null) || (modelSettingsKeys.indexOf(key) === -1)) {
|
||||
delete $scope.agg.settings.settings[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.onChangeClearInternal = function() {
|
||||
delete $scope.agg.settings.minimize;
|
||||
$scope.onChange();
|
||||
};
|
||||
|
||||
$scope.onTypeChange = function() {
|
||||
$scope.agg.settings = {};
|
||||
$scope.agg.meta = {};
|
||||
@@ -136,6 +163,9 @@ function (angular, _, queryDef) {
|
||||
};
|
||||
|
||||
$scope.getFieldsInternal = function() {
|
||||
if ($scope.agg.type === 'cardinality') {
|
||||
return $scope.getFields();
|
||||
}
|
||||
return $scope.getFields({$fieldType: 'number'});
|
||||
};
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
<span class="gf-form-label width-14">Index name</span>
|
||||
<input type="text" class="gf-form-input max-width-20" ng-model='ctrl.annotation.index' placeholder="events-*"></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-14">Search query (lucene) <tip>Use [[filterName]] in query to replace part of the query with a filter value</tip></span>
|
||||
<input type="text" class="gf-form-input max-width-20" ng-model='ctrl.annotation.query' placeholder="tags:deploy"></input>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.query'
|
||||
placeholder="Elasticsearch lucene query"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -33,4 +35,4 @@
|
||||
<input type="text" class="gf-form-input max-width-16" ng-model='ctrl.annotation.textField' placeholder=""></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<label class="gf-form-label" ng-if="isFirst">
|
||||
<a class="pointer" ng-click="addBucketAgg()"><i class="fa fa-plus"></i></a>
|
||||
</label>
|
||||
<label class="gf-form-label">
|
||||
<label class="gf-form-label" ng-if="bucketAggCount > 1">
|
||||
<a class="pointer" ng-click="removeBucketAgg()"><i class="fa fa-minus"></i></a>
|
||||
</label>
|
||||
</div>
|
||||
@@ -53,21 +53,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="agg.type === 'histogram'">
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Interval</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.interval" ng-blur="onChangeInternal()">
|
||||
</div>
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Min Doc Count</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.min_doc_count" ng-blur="onChangeInternal()">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="agg.type === 'terms'">
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Order</label>
|
||||
<metric-segment-model property="agg.settings.order" options="orderOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Size</label>
|
||||
<metric-segment-model property="agg.settings.size" options="sizeOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Min Doc Count</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.min_doc_count" ng-blur="onChangeInternal()">
|
||||
</div>
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Order By</label>
|
||||
<metric-segment-model property="agg.settings.orderBy" options="orderByOptions" on-change="onChangeInternal()" css-class="width-12"></metric-segment-model>
|
||||
</div>
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">
|
||||
Missing
|
||||
<info-popover mode="right-normal">
|
||||
The missing parameter defines how documents that are missing a value should be treated. By default they will be ignored but it is also possible to treat them as if they had a value
|
||||
</info-popover>
|
||||
</label>
|
||||
<input type="text" class="gf-form-input max-width-12" empty-to-null ng-model="agg.settings.missing" ng-blur="onChangeInternal()" spellcheck='false'>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="agg.type === 'filters'">
|
||||
@@ -75,6 +97,8 @@
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-10">Query {{$index + 1}}</label>
|
||||
<input type="text" class="gf-form-input max-width-12" ng-model="filter.query" spellcheck='false' placeholder="Lucene query" ng-blur="onChangeInternal()">
|
||||
<label class="gf-form-label width-10">Label {{$index + 1}}</label>
|
||||
<input type="text" class="gf-form-input max-width-12" ng-model="filter.label" spellcheck='false' placeholder="Label" ng-blur="onChangeInternal()">
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label" ng-if="$first">
|
||||
@@ -95,5 +119,3 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@@ -37,47 +37,67 @@
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group" ng-if="showOptions">
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="agg.type === 'derivative'">
|
||||
<label class="gf-form-label width-10">Unit</label>
|
||||
<input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.unit" ng-blur="onChangeInternal()" spellcheck='false'>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
|
||||
<label class="gf-form-label width-10">Window</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
|
||||
</div>
|
||||
<div ng-if="agg.type === 'moving_avg'">
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Model</label>
|
||||
<metric-segment-model property="agg.settings.model" options="movingAvgModelTypes" on-change="onChangeClearInternal()" custom="false" css-class="width-12"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="agg.type === 'moving_avg'">
|
||||
<label class="gf-form-label width-10">Model</label>
|
||||
<input type="text" class="gf-form-input max-width-12" ng-change="onChangeInternal()" ng-model="agg.settings.model" blur="onChange()" spellcheck='false'>
|
||||
</div>
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Window</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.window" ng-blur="onChangeInternal()" spellcheck='false'>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="agg.type === 'percentiles'">
|
||||
<label class="gf-form-label width-10">Percentiles</label>
|
||||
<input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
|
||||
</div>
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Predict</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.predict" ng-blur="onChangeInternal()" spellcheck='false'>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="agg.type === 'cardinality'">
|
||||
<label class="gf-form-label width-10">Precision threshold</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.precision_threshold" ng-blur="onChange()"></input>
|
||||
</div>
|
||||
|
||||
<div ng-if="agg.type === 'extended_stats'">
|
||||
<gf-form-switch ng-repeat="stat in extendedStats" class="gf-form offset-width-7" label="{{stat.text}}" label-class="width-10" checked="agg.meta[stat.value]" on-change="onChangeInternal()"></gf-form-switch>
|
||||
<div class="gf-form offset-width-7" ng-repeat="setting in modelSettings">
|
||||
<label class="gf-form-label width-10">{{setting.text}}</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.settings[setting.value]" ng-blur="onChangeInternal()" spellcheck='false'>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Sigma</label>
|
||||
<input type="number" class="gf-form-input max-width-12" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
|
||||
</div>
|
||||
</div>
|
||||
<gf-form-switch ng-if="agg.settings.model == 'holt_winters'" class="gf-form offset-width-7" label="Pad" label-class="width-10" checked="agg.settings.settings.pad" on-change="onChangeInternal()"></gf-form-switch>
|
||||
<gf-form-switch ng-if="agg.settings.model.match('ewma|holt_winters|holt') !== null" class="gf-form offset-width-7" label="Minimize" label-class="width-10" checked="agg.settings.minimize" on-change="onChangeInternal()"></gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="aggDef.supportsInlineScript">
|
||||
<label class="gf-form-label width-10">Script</label>
|
||||
<input type="text" class="gf-form-input max-width-12" empty-to-null ng-model="agg.inlineScript" ng-blur="onChangeInternal()" spellcheck='false' placeholder="_value * 1">
|
||||
</div>
|
||||
<div class="gf-form offset-width-7" ng-if="agg.type === 'percentiles'">
|
||||
<label class="gf-form-label width-10">Percentiles</label>
|
||||
<input type="text" class="gf-form-input max-width-12" ng-model="agg.settings.percents" array-join ng-blur="onChange()"></input>
|
||||
</div>
|
||||
<div class="gf-form offset-width-7" ng-if="agg.type === 'raw_document'">
|
||||
<label class="gf-form-label width-10">Size</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.size" ng-blur="onChange()"></input>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="aggDef.supportsMissing">
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="agg.type === 'cardinality'">
|
||||
<label class="gf-form-label width-10">Precision threshold</label>
|
||||
<input type="number" class="gf-form-input max-width-12" ng-model="agg.settings.precision_threshold" ng-blur="onChange()"></input>
|
||||
</div>
|
||||
|
||||
<div ng-if="agg.type === 'extended_stats'">
|
||||
<gf-form-switch ng-repeat="stat in extendedStats" class="gf-form offset-width-7" label="{{stat.text}}" label-class="width-10" checked="agg.meta[stat.value]" on-change="onChangeInternal()"></gf-form-switch>
|
||||
|
||||
<div class="gf-form offset-width-7">
|
||||
<label class="gf-form-label width-10">Sigma</label>
|
||||
<input type="number" class="gf-form-input max-width-12" placeholder="3" ng-model="agg.settings.sigma" ng-blur="onChange()"></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="aggDef.supportsInlineScript">
|
||||
<label class="gf-form-label width-10">Script</label>
|
||||
<input type="text" class="gf-form-input max-width-12" empty-to-null ng-model="agg.inlineScript" ng-blur="onChangeInternal()" spellcheck='false' placeholder="_value * 1">
|
||||
</div>
|
||||
|
||||
<div class="gf-form offset-width-7" ng-if="aggDef.supportsMissing">
|
||||
<label class="gf-form-label width-10">
|
||||
Missing
|
||||
<tip>The missing parameter defines how documents that are missing a value should be treated. By default they will be ignored but it is also possible to treat them as if they had a value</tip>
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
"description": "Elasticsearch Data Source for Grafana",
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"keywords": ["elasticsearch"],
|
||||
"logos": {
|
||||
"small": "img/logo_large.png",
|
||||
"large": "img/logo_large.png"
|
||||
"small": "img/elasticsearch.svg",
|
||||
"large": "img/elasticsearch.svg"
|
||||
},
|
||||
"links": [
|
||||
{"name": "elastic.co", "url": "https://www.elastic.co/products/elasticsearch"}
|
||||
|
||||
@@ -11,11 +11,11 @@ function (queryDef) {
|
||||
|
||||
ElasticQueryBuilder.prototype.getRangeFilter = function() {
|
||||
var filter = {};
|
||||
filter[this.timeField] = {"gte": "$timeFrom", "lte": "$timeTo"};
|
||||
|
||||
if (this.esVersion >= 2) {
|
||||
filter[this.timeField]["format"] = "epoch_millis";
|
||||
}
|
||||
filter[this.timeField] = {
|
||||
gte: "$timeFrom",
|
||||
lte: "$timeTo",
|
||||
format: "epoch_millis",
|
||||
};
|
||||
|
||||
return filter;
|
||||
};
|
||||
@@ -28,7 +28,7 @@ function (queryDef) {
|
||||
return queryNode;
|
||||
}
|
||||
|
||||
queryNode.terms.size = parseInt(aggDef.settings.size, 10);
|
||||
queryNode.terms.size = parseInt(aggDef.settings.size, 10) === 0 ? 500 : parseInt(aggDef.settings.size, 10);
|
||||
if (aggDef.settings.orderBy !== void 0) {
|
||||
queryNode.terms.order = {};
|
||||
queryNode.terms.order[aggDef.settings.orderBy] = aggDef.settings.order;
|
||||
@@ -48,6 +48,14 @@ function (queryDef) {
|
||||
}
|
||||
}
|
||||
|
||||
if (aggDef.settings.min_doc_count !== void 0) {
|
||||
queryNode.terms.min_doc_count = parseInt(aggDef.settings.min_doc_count, 10);
|
||||
}
|
||||
|
||||
if (aggDef.settings.missing) {
|
||||
queryNode.terms.missing = aggDef.settings.missing;
|
||||
}
|
||||
|
||||
return queryNode;
|
||||
};
|
||||
|
||||
@@ -58,29 +66,42 @@ function (queryDef) {
|
||||
esAgg.field = this.timeField;
|
||||
esAgg.min_doc_count = settings.min_doc_count || 0;
|
||||
esAgg.extended_bounds = {min: "$timeFrom", max: "$timeTo"};
|
||||
esAgg.format = "epoch_millis";
|
||||
|
||||
if (esAgg.interval === 'auto') {
|
||||
esAgg.interval = "$interval";
|
||||
esAgg.interval = "$__interval";
|
||||
}
|
||||
|
||||
if (this.esVersion >= 2) {
|
||||
esAgg.format = "epoch_millis";
|
||||
if (settings.missing) {
|
||||
esAgg.missing = settings.missing;
|
||||
}
|
||||
|
||||
return esAgg;
|
||||
};
|
||||
|
||||
ElasticQueryBuilder.prototype.getHistogramAgg = function(aggDef) {
|
||||
var esAgg = {};
|
||||
var settings = aggDef.settings || {};
|
||||
esAgg.interval = settings.interval;
|
||||
esAgg.field = aggDef.field;
|
||||
esAgg.min_doc_count = settings.min_doc_count || 0;
|
||||
|
||||
if (settings.missing) {
|
||||
esAgg.missing = settings.missing;
|
||||
}
|
||||
return esAgg;
|
||||
};
|
||||
|
||||
ElasticQueryBuilder.prototype.getFiltersAgg = function(aggDef) {
|
||||
var filterObj = {};
|
||||
|
||||
for (var i = 0; i < aggDef.settings.filters.length; i++) {
|
||||
var query = aggDef.settings.filters[i].query;
|
||||
filterObj[query] = {
|
||||
query: {
|
||||
query_string: {
|
||||
query: query,
|
||||
analyze_wildcard: true
|
||||
}
|
||||
var label = aggDef.settings.filters[i].label;
|
||||
label = label === '' || label === undefined ? query : label;
|
||||
filterObj[label] = {
|
||||
query_string: {
|
||||
query: query,
|
||||
analyze_wildcard: true
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -88,13 +109,22 @@ function (queryDef) {
|
||||
return filterObj;
|
||||
};
|
||||
|
||||
ElasticQueryBuilder.prototype.documentQuery = function(query) {
|
||||
query.size = 500;
|
||||
ElasticQueryBuilder.prototype.documentQuery = function(query, size) {
|
||||
query.size = size;
|
||||
query.sort = {};
|
||||
query.sort[this.timeField] = {order: 'desc', unmapped_type: 'boolean'};
|
||||
query.fields = ["*", "_source"];
|
||||
query.script_fields = {},
|
||||
query.fielddata_fields = [this.timeField];
|
||||
|
||||
// fields field not supported on ES 5.x
|
||||
if (this.esVersion < 5) {
|
||||
query.fields = ["*", "_source"];
|
||||
}
|
||||
|
||||
query.script_fields = {};
|
||||
if (this.esVersion < 5) {
|
||||
query.fielddata_fields = [this.timeField];
|
||||
} else {
|
||||
query.docvalue_fields = [this.timeField];
|
||||
}
|
||||
return query;
|
||||
};
|
||||
|
||||
@@ -104,17 +134,36 @@ function (queryDef) {
|
||||
}
|
||||
|
||||
var i, filter, condition;
|
||||
var must = query.query.filtered.filter.bool.must;
|
||||
|
||||
for (i = 0; i < adhocFilters.length; i++) {
|
||||
filter = adhocFilters[i];
|
||||
condition = {};
|
||||
condition[filter.key] = filter.value;
|
||||
must.push({"term": condition});
|
||||
switch(filter.operator){
|
||||
case "=":
|
||||
query.query.bool.filter.push({"term": condition});
|
||||
break;
|
||||
case "!=":
|
||||
query.query.bool.filter.push({"bool": {"must_not": {"term": condition}}});
|
||||
break;
|
||||
case "<":
|
||||
condition[filter.key] = {"lt": filter.value};
|
||||
query.query.bool.filter.push({"range": condition});
|
||||
break;
|
||||
case ">":
|
||||
condition[filter.key] = {"gt": filter.value};
|
||||
query.query.bool.filter.push({"range": condition});
|
||||
break;
|
||||
case "=~":
|
||||
query.query.bool.filter.push({"regexp": condition});
|
||||
break;
|
||||
case "!~":
|
||||
query.query.bool.filter.push({"bool": {"must_not": {"regexp": condition}}});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ElasticQueryBuilder.prototype.build = function(target, adhocFilters) {
|
||||
ElasticQueryBuilder.prototype.build = function(target, adhocFilters, queryString) {
|
||||
// make sure query has defaults;
|
||||
target.metrics = target.metrics || [{ type: 'count', id: '1' }];
|
||||
target.dsType = 'elasticsearch';
|
||||
@@ -125,18 +174,16 @@ function (queryDef) {
|
||||
var query = {
|
||||
"size": 0,
|
||||
"query": {
|
||||
"filtered": {
|
||||
"query": {
|
||||
"query_string": {
|
||||
"analyze_wildcard": true,
|
||||
"query": '$lucene_query',
|
||||
"bool": {
|
||||
"filter": [
|
||||
{"range": this.getRangeFilter()},
|
||||
{
|
||||
"query_string": {
|
||||
"analyze_wildcard": true,
|
||||
"query": queryString,
|
||||
}
|
||||
}
|
||||
},
|
||||
"filter": {
|
||||
"bool": {
|
||||
"must": [{"range": this.getRangeFilter()}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -146,10 +193,12 @@ function (queryDef) {
|
||||
// handle document query
|
||||
if (target.bucketAggs.length === 0) {
|
||||
metric = target.metrics[0];
|
||||
if (metric && metric.type !== 'raw_document') {
|
||||
if (!metric || metric.type !== 'raw_document') {
|
||||
throw {message: 'Invalid query'};
|
||||
}
|
||||
return this.documentQuery(query, target);
|
||||
|
||||
var size = (metric.settings && metric.settings.size) || 500;
|
||||
return this.documentQuery(query, size);
|
||||
}
|
||||
|
||||
nestedAggs = query;
|
||||
@@ -163,6 +212,10 @@ function (queryDef) {
|
||||
esAgg["date_histogram"] = this.getDateHistogramAgg(aggDef);
|
||||
break;
|
||||
}
|
||||
case 'histogram': {
|
||||
esAgg["histogram"] = this.getHistogramAgg(aggDef);
|
||||
break;
|
||||
}
|
||||
case 'filters': {
|
||||
esAgg["filters"] = {filters: this.getFiltersAgg(aggDef)};
|
||||
break;
|
||||
@@ -220,37 +273,37 @@ function (queryDef) {
|
||||
var query = {
|
||||
"size": 0,
|
||||
"query": {
|
||||
"filtered": {
|
||||
"filter": {
|
||||
"bool": {
|
||||
"must": [{"range": this.getRangeFilter()}]
|
||||
}
|
||||
}
|
||||
"bool": {
|
||||
"filter": [{"range": this.getRangeFilter()}]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (queryDef.query) {
|
||||
query.query.filtered.query = {
|
||||
query.query.bool.filter.push({
|
||||
"query_string": {
|
||||
"analyze_wildcard": true,
|
||||
"query": queryDef.query,
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
var size = 500;
|
||||
if (queryDef.size) {
|
||||
size = queryDef.size;
|
||||
}
|
||||
|
||||
query.aggs = {
|
||||
"1": {
|
||||
"terms": {
|
||||
"field": queryDef.field,
|
||||
"size": 0,
|
||||
"size": size,
|
||||
"order": {
|
||||
"_term": "asc"
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ function (_) {
|
||||
{text: "Filters", value: 'filters' },
|
||||
{text: "Geo Hash Grid", value: 'geohash_grid', requiresField: true},
|
||||
{text: "Date Histogram", value: 'date_histogram', requiresField: true},
|
||||
{text: "Histogram", value: 'histogram', requiresField: true},
|
||||
],
|
||||
|
||||
orderByOptions: [
|
||||
@@ -69,16 +70,44 @@ function (_) {
|
||||
{text: '1d', value: '1d'},
|
||||
],
|
||||
|
||||
movingAvgModelOptions: [
|
||||
{text: 'Simple', value: 'simple'},
|
||||
{text: 'Linear', value: 'linear'},
|
||||
{text: 'Exponentially Weighted', value: 'ewma'},
|
||||
{text: 'Holt Linear', value: 'holt'},
|
||||
{text: 'Holt Winters', value: 'holt_winters'},
|
||||
],
|
||||
|
||||
pipelineOptions: {
|
||||
'moving_avg' : [
|
||||
{text: 'window', default: 5},
|
||||
{text: 'model', default: 'simple'}
|
||||
{text: 'model', default: 'simple'},
|
||||
{text: 'predict', default: undefined},
|
||||
{text: 'minimize', default: false},
|
||||
],
|
||||
'derivative': [
|
||||
{text: 'unit', default: undefined},
|
||||
]
|
||||
},
|
||||
|
||||
movingAvgModelSettings: {
|
||||
'simple' : [],
|
||||
'linear' : [],
|
||||
'ewma' : [
|
||||
{text: "Alpha", value: "alpha", default: undefined}],
|
||||
'holt' : [
|
||||
{text: "Alpha", value: "alpha", default: undefined},
|
||||
{text: "Beta", value: "beta", default: undefined},
|
||||
],
|
||||
'holt_winters' : [
|
||||
{text: "Alpha", value: "alpha", default: undefined},
|
||||
{text: "Beta", value: "beta", default: undefined},
|
||||
{text: "Gamma", value: "gamma", default: undefined},
|
||||
{text: "Period", value: "period", default: undefined},
|
||||
{text: "Pad", value: "pad", default: undefined, isCheckbox: true},
|
||||
],
|
||||
},
|
||||
|
||||
getMetricAggTypes: function(esVersion) {
|
||||
return _.filter(this.metricAggTypes, function(f) {
|
||||
if (f.minVersion) {
|
||||
@@ -118,6 +147,19 @@ function (_) {
|
||||
return result;
|
||||
},
|
||||
|
||||
getMovingAvgSettings: function(model, filtered) {
|
||||
var filteredResult = [];
|
||||
if (filtered) {
|
||||
_.each(this.movingAvgModelSettings[model], function(setting) {
|
||||
if (!(setting.isCheckbox)) {
|
||||
filteredResult.push(setting);
|
||||
}
|
||||
});
|
||||
return filteredResult;
|
||||
}
|
||||
return this.movingAvgModelSettings[model];
|
||||
},
|
||||
|
||||
getOrderByOptions: function(target) {
|
||||
var self = this;
|
||||
var metricRefs = [];
|
||||
|
||||
@@ -11,7 +11,7 @@ describe('ElasticDatasource', function() {
|
||||
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
|
||||
beforeEach(ctx.providePhase(['templateSrv', 'backendSrv', 'timeSrv']));
|
||||
|
||||
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
|
||||
ctx.$q = $q;
|
||||
@@ -28,7 +28,7 @@ describe('ElasticDatasource', function() {
|
||||
|
||||
describe('When testing datasource with index pattern', function() {
|
||||
beforeEach(function() {
|
||||
createDatasource({url: 'http://es.com', index: '[asd-]YYYY.MM.DD', jsonData: {interval: 'Daily'}});
|
||||
createDatasource({url: 'http://es.com', index: '[asd-]YYYY.MM.DD', jsonData: {interval: 'Daily', esVersion: '2'}});
|
||||
});
|
||||
|
||||
it('should translate index pattern to current day', function() {
|
||||
@@ -42,7 +42,7 @@ describe('ElasticDatasource', function() {
|
||||
ctx.$rootScope.$apply();
|
||||
|
||||
var today = moment.utc().format("YYYY.MM.DD");
|
||||
expect(requestOptions.url).to.be("http://es.com/asd-" + today + '/_stats');
|
||||
expect(requestOptions.url).to.be("http://es.com/asd-" + today + '/_mapping');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,7 +50,7 @@ describe('ElasticDatasource', function() {
|
||||
var requestOptions, parts, header;
|
||||
|
||||
beforeEach(function() {
|
||||
createDatasource({url: 'http://es.com', index: '[asd-]YYYY.MM.DD', jsonData: {interval: 'Daily'}});
|
||||
createDatasource({url: 'http://es.com', index: '[asd-]YYYY.MM.DD', jsonData: {interval: 'Daily', esVersion: '2'}});
|
||||
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
requestOptions = options;
|
||||
@@ -62,7 +62,7 @@ describe('ElasticDatasource', function() {
|
||||
from: moment.utc([2015, 4, 30, 10]),
|
||||
to: moment.utc([2015, 5, 1, 10])
|
||||
},
|
||||
targets: [{ bucketAggs: [], metrics: [], query: 'escape\\:test' }]
|
||||
targets: [{ bucketAggs: [], metrics: [{type: 'raw_document'}], query: 'escape\\:test' }]
|
||||
});
|
||||
|
||||
ctx.$rootScope.$apply();
|
||||
@@ -77,7 +77,7 @@ describe('ElasticDatasource', function() {
|
||||
|
||||
it('should json escape lucene query', function() {
|
||||
var body = angular.fromJson(parts[1]);
|
||||
expect(body.query.filtered.query.query_string.query).to.be('escape\\:test');
|
||||
expect(body.query.bool.filter[1].query_string.query).to.be('escape\\:test');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('ElasticDatasource', function() {
|
||||
var requestOptions, parts, header;
|
||||
|
||||
beforeEach(function() {
|
||||
createDatasource({url: 'http://es.com', index: 'test'});
|
||||
createDatasource({url: 'http://es.com', index: 'test', jsonData: {esVersion: '2'}});
|
||||
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
requestOptions = options;
|
||||
@@ -209,4 +209,79 @@ describe('ElasticDatasource', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('When issuing aggregation query on es5.x', function() {
|
||||
var requestOptions, parts, header;
|
||||
|
||||
beforeEach(function() {
|
||||
createDatasource({url: 'http://es.com', index: 'test', jsonData: {esVersion: '5'}});
|
||||
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
requestOptions = options;
|
||||
return ctx.$q.when({data: {responses: []}});
|
||||
};
|
||||
|
||||
ctx.ds.query({
|
||||
range: { from: moment([2015, 4, 30, 10]), to: moment([2015, 5, 1, 10]) },
|
||||
targets: [{
|
||||
bucketAggs: [
|
||||
{type: 'date_histogram', field: '@timestamp', id: '2'}
|
||||
],
|
||||
metrics: [
|
||||
{type: 'count'}], query: 'test' }
|
||||
]
|
||||
});
|
||||
|
||||
ctx.$rootScope.$apply();
|
||||
parts = requestOptions.data.split('\n');
|
||||
header = angular.fromJson(parts[0]);
|
||||
});
|
||||
|
||||
it('should not set search type to count', function() {
|
||||
expect(header.search_type).to.not.eql('count');
|
||||
});
|
||||
|
||||
it('should set size to 0', function() {
|
||||
var body = angular.fromJson(parts[1]);
|
||||
expect(body.size).to.be(0);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('When issuing metricFind query on es5.x', function() {
|
||||
var requestOptions, parts, header, body;
|
||||
|
||||
beforeEach(function() {
|
||||
createDatasource({url: 'http://es.com', index: 'test', jsonData: {esVersion: '5'}});
|
||||
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
requestOptions = options;
|
||||
return ctx.$q.when({
|
||||
data: {
|
||||
responses: [{aggregations: {"1": [{buckets: {text: 'test', value: '1'}}]}}]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ctx.ds.metricFindQuery('{"find": "terms", "field": "test"}');
|
||||
ctx.$rootScope.$apply();
|
||||
|
||||
parts = requestOptions.data.split('\n');
|
||||
header = angular.fromJson(parts[0]);
|
||||
body = angular.fromJson(parts[1]);
|
||||
});
|
||||
|
||||
it('should not set search type to count', function() {
|
||||
expect(header.search_type).to.not.eql('count');
|
||||
});
|
||||
|
||||
it('should set size to 0', function() {
|
||||
expect(body.size).to.be(0);
|
||||
});
|
||||
|
||||
it('should not set terms aggregation size to 0', function() {
|
||||
expect(body['aggs']['1']['terms'].size).to.not.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -302,7 +302,7 @@ describe('ElasticResponse', function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [{type: 'count', id: '1'}],
|
||||
alias: '{{term @host}} {{metric}} and!',
|
||||
alias: '{{term @host}} {{metric}} and {{not_exist}} {{@host}}',
|
||||
bucketAggs: [
|
||||
{type: 'terms', field: '@host', id: '2'},
|
||||
{type: 'date_histogram', field: '@timestamp', id: '3'}
|
||||
@@ -333,6 +333,16 @@ describe('ElasticResponse', function() {
|
||||
doc_count: 10,
|
||||
key: 'server2',
|
||||
},
|
||||
{
|
||||
"3": {
|
||||
buckets: [
|
||||
{doc_count: 2, key: 1000},
|
||||
{doc_count: 8, key: 2000}
|
||||
]
|
||||
},
|
||||
doc_count: 10,
|
||||
key: 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -343,10 +353,44 @@ describe('ElasticResponse', function() {
|
||||
});
|
||||
|
||||
it('should return 2 series', function() {
|
||||
expect(result.data.length).to.be(2);
|
||||
expect(result.data.length).to.be(3);
|
||||
expect(result.data[0].datapoints.length).to.be(2);
|
||||
expect(result.data[0].target).to.be('server1 Count and!');
|
||||
expect(result.data[1].target).to.be('server2 Count and!');
|
||||
expect(result.data[0].target).to.be('server1 Count and {{not_exist}} server1');
|
||||
expect(result.data[1].target).to.be('server2 Count and {{not_exist}} server2');
|
||||
expect(result.data[2].target).to.be('0 Count and {{not_exist}} 0');
|
||||
});
|
||||
});
|
||||
|
||||
describe('histogram response', function() {
|
||||
var result;
|
||||
|
||||
beforeEach(function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [{type: 'count', id: '1'}],
|
||||
bucketAggs: [{type: 'histogram', field: 'bytes', id: '3'}],
|
||||
}];
|
||||
response = {
|
||||
responses: [{
|
||||
aggregations: {
|
||||
"3": {
|
||||
buckets: [
|
||||
{doc_count: 1, key: 1000},
|
||||
{doc_count: 3, key: 2000},
|
||||
{doc_count: 2, key: 1000},
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||
});
|
||||
|
||||
it('should return docs with byte and count', function() {
|
||||
expect(result.data[0].datapoints.length).to.be(3);
|
||||
expect(result.data[0].datapoints[0].Count).to.be(1);
|
||||
expect(result.data[0].datapoints[0].bytes).to.be(1000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -497,6 +541,46 @@ describe('ElasticResponse', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Multiple metrics of same type', function() {
|
||||
beforeEach(function() {
|
||||
targets = [{
|
||||
refId: 'A',
|
||||
metrics: [
|
||||
{type: 'avg', id: '1', field: 'test'},
|
||||
{type: 'avg', id: '2', field: 'test2'}
|
||||
],
|
||||
bucketAggs: [{id: '2', type: 'terms', field: 'host'}],
|
||||
}];
|
||||
|
||||
response = {
|
||||
responses: [{
|
||||
aggregations: {
|
||||
"2": {
|
||||
buckets: [
|
||||
{
|
||||
"1": { value: 1000},
|
||||
"2": { value: 3000},
|
||||
key: "server-1",
|
||||
doc_count: 369,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
result = new ElasticResponse(targets, response).getTimeSeries();
|
||||
});
|
||||
|
||||
it('should include field in metric name', function() {
|
||||
expect(result.data[0].type).to.be('docs');
|
||||
expect(result.data[0].datapoints[0].Average).to.be(undefined);
|
||||
expect(result.data[0].datapoints[0]['Average test']).to.be(1000);
|
||||
expect(result.data[0].datapoints[0]['Average test2']).to.be(3000);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Raw documents query', function() {
|
||||
beforeEach(function() {
|
||||
targets = [{ refId: 'A', metrics: [{type: 'raw_document', id: '1'}], bucketAggs: [] }];
|
||||
|
||||
@@ -16,7 +16,23 @@ describe('ElasticQueryBuilder', function() {
|
||||
bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '1'}],
|
||||
});
|
||||
|
||||
expect(query.query.filtered.filter.bool.must[0].range["@timestamp"].gte).to.be("$timeFrom");
|
||||
expect(query.query.bool.filter[0].range["@timestamp"].gte).to.be("$timeFrom");
|
||||
expect(query.aggs["1"].date_histogram.extended_bounds.min).to.be("$timeFrom");
|
||||
});
|
||||
|
||||
it('with defaults on es5.x', function() {
|
||||
var builder_5x = new ElasticQueryBuilder({
|
||||
timeField: '@timestamp',
|
||||
esVersion: 5
|
||||
});
|
||||
|
||||
var query = builder_5x.build({
|
||||
metrics: [{type: 'Count', id: '0'}],
|
||||
timeField: '@timestamp',
|
||||
bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '1'}],
|
||||
});
|
||||
|
||||
expect(query.query.bool.filter[0].range["@timestamp"].gte).to.be("$timeFrom");
|
||||
expect(query.aggs["1"].date_histogram.extended_bounds.min).to.be("$timeFrom");
|
||||
});
|
||||
|
||||
@@ -34,39 +50,6 @@ describe('ElasticQueryBuilder', function() {
|
||||
expect(query.aggs["2"].aggs["3"].date_histogram.field).to.be("@timestamp");
|
||||
});
|
||||
|
||||
it('with es1.x and es2.x date histogram queries check time format', function() {
|
||||
var builder_2x = new ElasticQueryBuilder({
|
||||
timeField: '@timestamp',
|
||||
esVersion: 2
|
||||
});
|
||||
|
||||
var query_params = {
|
||||
metrics: [],
|
||||
bucketAggs: [
|
||||
{type: 'date_histogram', field: '@timestamp', id: '1'}
|
||||
],
|
||||
};
|
||||
|
||||
// format should not be specified in 1.x queries
|
||||
expect("format" in builder.build(query_params)["aggs"]["1"]["date_histogram"]).to.be(false);
|
||||
|
||||
// 2.x query should specify format to be "epoch_millis"
|
||||
expect(builder_2x.build(query_params)["aggs"]["1"]["date_histogram"]["format"]).to.be("epoch_millis");
|
||||
});
|
||||
|
||||
it('with es1.x and es2.x range filter check time format', function() {
|
||||
var builder_2x = new ElasticQueryBuilder({
|
||||
timeField: '@timestamp',
|
||||
esVersion: 2
|
||||
});
|
||||
|
||||
// format should not be specified in 1.x queries
|
||||
expect("format" in builder.getRangeFilter()["@timestamp"]).to.be(false);
|
||||
|
||||
// 2.x query should specify format to be "epoch_millis"
|
||||
expect(builder_2x.getRangeFilter()["@timestamp"]["format"]).to.be("epoch_millis");
|
||||
});
|
||||
|
||||
it('with select field', function() {
|
||||
var query = builder.build({
|
||||
metrics: [{type: 'avg', field: '@value', id: '1'}],
|
||||
@@ -138,20 +121,57 @@ describe('ElasticQueryBuilder', function() {
|
||||
],
|
||||
});
|
||||
|
||||
expect(query.aggs["2"].filters.filters["@metric:cpu"].query.query_string.query).to.be("@metric:cpu");
|
||||
expect(query.aggs["2"].filters.filters["@metric:logins.count"].query.query_string.query).to.be("@metric:logins.count");
|
||||
expect(query.aggs["2"].filters.filters["@metric:cpu"].query_string.query).to.be("@metric:cpu");
|
||||
expect(query.aggs["2"].filters.filters["@metric:logins.count"].query_string.query).to.be("@metric:logins.count");
|
||||
expect(query.aggs["2"].aggs["4"].date_histogram.field).to.be("@timestamp");
|
||||
});
|
||||
|
||||
it('with filters aggs on es5.x', function() {
|
||||
var builder_5x = new ElasticQueryBuilder({
|
||||
timeField: '@timestamp',
|
||||
esVersion: 5
|
||||
});
|
||||
var query = builder_5x.build({
|
||||
metrics: [{type: 'count', id: '1'}],
|
||||
timeField: '@timestamp',
|
||||
bucketAggs: [
|
||||
{
|
||||
id: '2',
|
||||
type: 'filters',
|
||||
settings: {
|
||||
filters: [
|
||||
{query: '@metric:cpu' },
|
||||
{query: '@metric:logins.count' },
|
||||
]
|
||||
}
|
||||
},
|
||||
{type: 'date_histogram', field: '@timestamp', id: '4'}
|
||||
],
|
||||
});
|
||||
|
||||
expect(query.aggs["2"].filters.filters["@metric:cpu"].query_string.query).to.be("@metric:cpu");
|
||||
expect(query.aggs["2"].filters.filters["@metric:logins.count"].query_string.query).to.be("@metric:logins.count");
|
||||
expect(query.aggs["2"].aggs["4"].date_histogram.field).to.be("@timestamp");
|
||||
});
|
||||
|
||||
it('with raw_document metric', function() {
|
||||
var query = builder.build({
|
||||
metrics: [{type: 'raw_document', id: '1'}],
|
||||
metrics: [{type: 'raw_document', id: '1',settings: {}}],
|
||||
timeField: '@timestamp',
|
||||
bucketAggs: [],
|
||||
});
|
||||
|
||||
expect(query.size).to.be(500);
|
||||
});
|
||||
it('with raw_document metric size set', function() {
|
||||
var query = builder.build({
|
||||
metrics: [{type: 'raw_document', id: '1',settings: {size: 1337}}],
|
||||
timeField: '@timestamp',
|
||||
bucketAggs: [],
|
||||
});
|
||||
|
||||
expect(query.size).to.be(1337);
|
||||
});
|
||||
|
||||
it('with moving average', function() {
|
||||
var query = builder.build({
|
||||
@@ -238,16 +258,42 @@ describe('ElasticQueryBuilder', function() {
|
||||
expect(firstLevel.aggs["2"].derivative.buckets_path).to.be("3");
|
||||
});
|
||||
|
||||
it('with histogram', function() {
|
||||
var query = builder.build({
|
||||
metrics: [
|
||||
{id: '1', type: 'count' },
|
||||
],
|
||||
bucketAggs: [
|
||||
{type: 'histogram', field: 'bytes', id: '3', settings: {interval: 10, min_doc_count: 2, missing: 5}}
|
||||
],
|
||||
});
|
||||
|
||||
var firstLevel = query.aggs["3"];
|
||||
expect(firstLevel.histogram.field).to.be('bytes');
|
||||
expect(firstLevel.histogram.interval).to.be(10);
|
||||
expect(firstLevel.histogram.min_doc_count).to.be(2);
|
||||
expect(firstLevel.histogram.missing).to.be(5);
|
||||
});
|
||||
|
||||
it('with adhoc filters', function() {
|
||||
var query = builder.build({
|
||||
metrics: [{type: 'Count', id: '0'}],
|
||||
timeField: '@timestamp',
|
||||
bucketAggs: [{type: 'date_histogram', field: '@timestamp', id: '3'}],
|
||||
}, [
|
||||
{key: 'key1', operator: '=', value: 'value1'}
|
||||
{key: 'key1', operator: '=', value: 'value1'},
|
||||
{key: 'key2', operator: '!=', value: 'value2'},
|
||||
{key: 'key3', operator: '<', value: 'value3'},
|
||||
{key: 'key4', operator: '>', value: 'value4'},
|
||||
{key: 'key5', operator: '=~', value: 'value5'},
|
||||
{key: 'key6', operator: '!~', value: 'value6'},
|
||||
]);
|
||||
|
||||
expect(query.query.filtered.filter.bool.must[1].term["key1"]).to.be("value1");
|
||||
expect(query.query.bool.filter[2].term["key1"]).to.be("value1");
|
||||
expect(query.query.bool.filter[3].bool.must_not.term["key2"]).to.be("value2");
|
||||
expect(query.query.bool.filter[4].range["key3"].lt).to.be("value3");
|
||||
expect(query.query.bool.filter[5].range["key4"].gt).to.be("value4");
|
||||
expect(query.query.bool.filter[6].regexp["key5"]).to.be("value5");
|
||||
expect(query.query.bool.filter[7].bool.must_not.regexp["key6"]).to.be("value6");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -95,5 +95,11 @@ describe('ElasticQueryDef', function() {
|
||||
expect(queryDef.getMetricAggTypes(2).length).to.be(11);
|
||||
});
|
||||
});
|
||||
|
||||
describe('using esversion 5', function() {
|
||||
it('should get pipeline aggs', function() {
|
||||
expect(queryDef.getMetricAggTypes(5).length).to.be(11);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<query-editor-row ctrl="ctrl">
|
||||
<li class="tight-form-item">
|
||||
Stream
|
||||
</li>
|
||||
<li>
|
||||
<input type="text" class="tight-form-input input-large" ng-model="ctrl.target.stream">
|
||||
</li>
|
||||
<query-editor-row query-ctrl="ctrl" can-collapse="false">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<label class="gf-form-label width-8">Stream</label>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.stream" spellcheck='false' placeholder="metric">
|
||||
</div>
|
||||
</div>
|
||||
</query-editor-row>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "Grafana Live",
|
||||
"id": "grafana-live",
|
||||
|
||||
"metrics": true
|
||||
}
|
||||
@@ -1,20 +1,40 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
class GrafanaDatasource {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv) {}
|
||||
constructor(private backendSrv, private $q) {}
|
||||
|
||||
query(options) {
|
||||
return this.backendSrv.get('/api/metrics/test', {
|
||||
return this.backendSrv.get('/api/tsdb/testdata/random-walk', {
|
||||
from: options.range.from.valueOf(),
|
||||
to: options.range.to.valueOf(),
|
||||
scenario: 'random_walk',
|
||||
interval: options.intervalMs,
|
||||
maxDataPoints: options.maxDataPoints
|
||||
intervalMs: options.intervalMs,
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
}).then(res => {
|
||||
var data = [];
|
||||
|
||||
if (res.results) {
|
||||
_.forEach(res.results, queryRes => {
|
||||
for (let series of queryRes.series) {
|
||||
data.push({
|
||||
target: series.name,
|
||||
datapoints: series.points
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {data: data};
|
||||
});
|
||||
}
|
||||
|
||||
metricFindQuery(options) {
|
||||
return this.$q.when({data: []});
|
||||
}
|
||||
|
||||
annotationQuery(options) {
|
||||
return this.backendSrv.get('/api/annotations', {
|
||||
from: options.range.from.valueOf(),
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
<div class="gf-form-group">
|
||||
<h6>Filters</h6>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-7">Type</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.annotation.type" ng-options="f.value as f.text for f in [{text: 'Alert', value: 'alert'}]">
|
||||
<select class="gf-form-input" ng-model="ctrl.annotation.type" ng-options="f.value as f.text for f in [{text: 'Event', value: 'event'}, {text: 'Alert', value: 'alert'}]">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<query-editor-row query-ctrl="ctrl" can-collapse="false">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">Test metric (fake data source)</label>
|
||||
<label class="gf-form-label">Test data: random walk</label>
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</query-editor-row>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "Grafana",
|
||||
"name": "-- Grafana --",
|
||||
"id": "grafana",
|
||||
|
||||
"builtIn": true,
|
||||
|
||||
@@ -126,6 +126,10 @@ export function GraphiteDatasource(instanceSettings, $q, backendSrv, templateSrv
|
||||
}
|
||||
};
|
||||
|
||||
this.targetContainsTemplate = function(target) {
|
||||
return templateSrv.variableExists(target.target);
|
||||
};
|
||||
|
||||
this.translateTime = function(date, roundUp) {
|
||||
if (_.isString(date)) {
|
||||
if (date === 'now') {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-13">Graphite metrics query</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.target' placeholder=""></input>
|
||||
<span class="gf-form-label width-12">Graphite query</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.target' placeholder="Example: statsd.application.counters.*.count"></input>
|
||||
</div>
|
||||
|
||||
<h5 class="section-heading">Or</h5>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-13">Or Graphite events query</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.tags' placeholder=""></input>
|
||||
<span class="gf-form-label width-12">Graphite events tags</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.tags' placeholder="Example: event_tag_name"></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<query-editor-row query-ctrl="ctrl" has-text-edit-mode="true">
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.target.textEditor">
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.target" spellcheck="false" ng-blur="ctrl.refresh()"></input>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.target" spellcheck="false" ng-blur="ctrl.targetTextChanged()"></input>
|
||||
</div>
|
||||
|
||||
<div ng-hide="ctrl.target.textEditor">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<section class="grafana-metric-options gf-form-group">
|
||||
<section class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-15">
|
||||
<span class="gf-form-label width-8">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/graphite_logo.png",
|
||||
|
||||
@@ -55,7 +55,7 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
|
||||
try {
|
||||
this.parseTargeRecursive(astNode, null, 0);
|
||||
this.parseTargetRecursive(astNode, null, 0);
|
||||
} catch (err) {
|
||||
console.log('error parsing target:', err.message);
|
||||
this.error = err.message;
|
||||
@@ -72,7 +72,7 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
func.params[index] = value;
|
||||
}
|
||||
|
||||
parseTargeRecursive(astNode, func, index) {
|
||||
parseTargetRecursive(astNode, func, index) {
|
||||
if (astNode === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -81,7 +81,7 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
case 'function':
|
||||
var innerFunc = gfunc.createFuncInstance(astNode.name, { withDefaultParams: false });
|
||||
_.each(astNode.params, (param, index) => {
|
||||
this.parseTargeRecursive(param, innerFunc, index);
|
||||
this.parseTargetRecursive(param, innerFunc, index);
|
||||
});
|
||||
|
||||
innerFunc.updateText();
|
||||
@@ -128,6 +128,10 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
|
||||
var path = this.getSegmentPathUpTo(fromIndex + 1);
|
||||
if (path === "") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return this.datasource.metricFindQuery(path).then(segments => {
|
||||
if (segments.length === 0) {
|
||||
if (path !== '') {
|
||||
@@ -161,7 +165,7 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
|
||||
return this.datasource.metricFindQuery(query).then(segments => {
|
||||
var altSegments = _.map(segments, segment => {
|
||||
return this.uiSegmentSrv.newSegment({ value: segment.text, expandable: segment.expandable });
|
||||
return this.uiSegmentSrv.newSegment({value: segment.text, expandable: segment.expandable});
|
||||
});
|
||||
|
||||
if (altSegments.length === 0) { return altSegments; }
|
||||
@@ -205,8 +209,62 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
|
||||
targetTextChanged() {
|
||||
this.parseTarget();
|
||||
this.panelCtrl.refresh();
|
||||
this.updateModelTarget();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
updateModelTarget() {
|
||||
// render query
|
||||
if (!this.target.textEditor) {
|
||||
var metricPath = this.getSegmentPathUpTo(this.segments.length);
|
||||
this.target.target = _.reduce(this.functions, this.wrapFunction, metricPath);
|
||||
}
|
||||
|
||||
this.updateRenderedTarget(this.target);
|
||||
|
||||
// loop through other queries and update targetFull as needed
|
||||
for (const target of this.panelCtrl.panel.targets || []) {
|
||||
if (target.refId !== this.target.refId) {
|
||||
this.updateRenderedTarget(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateRenderedTarget(target) {
|
||||
// render nested query
|
||||
var targetsByRefId = _.keyBy(this.panelCtrl.panel.targets, 'refId');
|
||||
|
||||
// no references to self
|
||||
delete targetsByRefId[target.refId];
|
||||
|
||||
var nestedSeriesRefRegex = /\#([A-Z])/g;
|
||||
var targetWithNestedQueries = target.target;
|
||||
|
||||
// Keep interpolating until there are no query references
|
||||
// The reason for the loop is that the referenced query might contain another reference to another query
|
||||
while (targetWithNestedQueries.match(nestedSeriesRefRegex)) {
|
||||
var updated = targetWithNestedQueries.replace(nestedSeriesRefRegex, (match, g1) => {
|
||||
var t = targetsByRefId[g1];
|
||||
if (!t) {
|
||||
return match;
|
||||
}
|
||||
|
||||
// no circular references
|
||||
delete targetsByRefId[g1];
|
||||
return t.target;
|
||||
});
|
||||
|
||||
if (updated === targetWithNestedQueries) {
|
||||
break;
|
||||
}
|
||||
|
||||
targetWithNestedQueries = updated;
|
||||
}
|
||||
|
||||
delete target.targetFull;
|
||||
if (target.target !== targetWithNestedQueries) {
|
||||
target.targetFull = targetWithNestedQueries;
|
||||
}
|
||||
}
|
||||
|
||||
targetChanged() {
|
||||
@@ -215,11 +273,11 @@ export class GraphiteQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
|
||||
var oldTarget = this.target.target;
|
||||
var target = this.getSegmentPathUpTo(this.segments.length);
|
||||
this.target.target = _.reduce(this.functions, this.wrapFunction, target);
|
||||
this.updateModelTarget();
|
||||
|
||||
if (this.target.target !== oldTarget) {
|
||||
if (this.segments[this.segments.length - 1].value !== 'select metric') {
|
||||
var lastSegment = this.segments.length > 0 ? this.segments[this.segments.length - 1] : {};
|
||||
if (lastSegment.value !== 'select metric') {
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ describe('GraphiteQueryCtrl', function() {
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.controllers'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(angularMocks.module(function($compileProvider) {
|
||||
$compileProvider.preAssignBindingsEnabled(true);
|
||||
}));
|
||||
|
||||
beforeEach(ctx.providePhase());
|
||||
beforeEach(angularMocks.inject(($rootScope, $controller, $q) => {
|
||||
@@ -160,4 +163,47 @@ describe('GraphiteQueryCtrl', function() {
|
||||
expect(ctx.panelCtrl.refresh.called).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when updating targets with nested query', function() {
|
||||
beforeEach(function() {
|
||||
ctx.ctrl.target.target = 'scaleToSeconds(#A)';
|
||||
ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
|
||||
ctx.ctrl.panelCtrl.panel.targets = [ {
|
||||
target: 'nested.query.count',
|
||||
refId: 'A'
|
||||
}];
|
||||
|
||||
ctx.ctrl.updateModelTarget();
|
||||
});
|
||||
|
||||
it('target should remain the same', function() {
|
||||
expect(ctx.ctrl.target.target).to.be('scaleToSeconds(#A)');
|
||||
});
|
||||
|
||||
it('targetFull should include nexted queries', function() {
|
||||
expect(ctx.ctrl.target.targetFull).to.be('scaleToSeconds(nested.query.count)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when updating target used in other query', function() {
|
||||
beforeEach(function() {
|
||||
ctx.ctrl.target.target = 'metrics.a.count';
|
||||
ctx.ctrl.target.refId = 'A';
|
||||
ctx.ctrl.datasource.metricFindQuery = sinon.stub().returns(ctx.$q.when([{expandable: false}]));
|
||||
ctx.ctrl.parseTarget();
|
||||
|
||||
ctx.ctrl.panelCtrl.panel.targets = [
|
||||
ctx.ctrl.target, {target: 'sumSeries(#A)', refId: 'B'}
|
||||
];
|
||||
|
||||
ctx.ctrl.updateModelTarget();
|
||||
});
|
||||
|
||||
it('targetFull of other query should update', function() {
|
||||
expect(ctx.ctrl.panel.targets[1].targetFull).to.be('sumSeries(metrics.a.count)');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# InfluxDB Datasource - Native Plugin
|
||||
|
||||
Grafana ships with **built in** support for InfluxDB 0.9.
|
||||
Grafana ships with **built in** support for InfluxDB 0.9.
|
||||
|
||||
There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0.8.x and InfluxDB 0.9.x. The API and capabilities of InfluxDB 0.9.x are completely different from InfluxDB 0.8.x which is why Grafana handles them as different data sources.
|
||||
|
||||
This is the plugin for InfluxDB 0.9. It is rapidly evolving and we continue to track its API.
|
||||
This is the plugin for InfluxDB 0.9. It is rapidly evolving and we continue to track its API.
|
||||
|
||||
InfluxDB 0.8 is no longer maintained by InfluxDB Inc, but we provide support as a convenience to existing users. You can find it [here](https://grafana.net/plugins/grafana-influxdb-08-datasource).
|
||||
InfluxDB 0.8 is no longer maintained by InfluxDB Inc, but we provide support as a convenience to existing users. You can find it [here](https://grafana.com/plugins/grafana-influxdb-08-datasource).
|
||||
|
||||
Read more about InfluxDB here:
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ export default class InfluxDatasource {
|
||||
|
||||
query(options) {
|
||||
var timeFilter = this.getTimeFilter(options);
|
||||
var scopedVars = options.scopedVars ? _.cloneDeep(options.scopedVars) : {};
|
||||
var scopedVars = options.scopedVars;
|
||||
var targets = _.cloneDeep(options.targets);
|
||||
var queryTargets = [];
|
||||
var queryModel;
|
||||
@@ -56,8 +56,8 @@ export default class InfluxDatasource {
|
||||
|
||||
queryTargets.push(target);
|
||||
|
||||
// build query
|
||||
scopedVars.interval = {value: target.interval || options.interval};
|
||||
// backward compatability
|
||||
scopedVars.interval = scopedVars.__interval;
|
||||
|
||||
queryModel = new InfluxQuery(target, this.templateSrv, scopedVars);
|
||||
return queryModel.render(true);
|
||||
@@ -120,7 +120,7 @@ export default class InfluxDatasource {
|
||||
|
||||
return {data: seriesList};
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
annotationQuery(options) {
|
||||
if (!options.annotation.query) {
|
||||
@@ -137,7 +137,25 @@ export default class InfluxDatasource {
|
||||
}
|
||||
return new InfluxSeries({series: data.results[0].series, annotation: options.annotation}).getAnnotations();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
targetContainsTemplate(target) {
|
||||
for (let group of target.groupBy) {
|
||||
for (let param of group.params) {
|
||||
if (this.templateSrv.variableExists(param)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i in target.tags) {
|
||||
if (this.templateSrv.variableExists(target.tags[i].value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
metricFindQuery(query) {
|
||||
var interpolated = this.templateSrv.replace(query, null, 'regex');
|
||||
@@ -175,8 +193,14 @@ export default class InfluxDatasource {
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
return this.metricFindQuery('SHOW MEASUREMENTS LIMIT 1').then(() => {
|
||||
return this.metricFindQuery('SHOW DATABASES').then(res => {
|
||||
let found = _.find(res, {text: this.database});
|
||||
if (!found) {
|
||||
return { status: "error", message: "Could not find the specified database name.", title: "DB Not found" };
|
||||
}
|
||||
return { status: "success", message: "Data source is working", title: "Success" };
|
||||
}).catch(err => {
|
||||
return { status: "error", message: err.message, title: "Test Failed" };
|
||||
});
|
||||
}
|
||||
|
||||
@@ -186,10 +210,12 @@ export default class InfluxDatasource {
|
||||
var currentUrl = self.urls.shift();
|
||||
self.urls.push(currentUrl);
|
||||
|
||||
var params: any = {
|
||||
u: self.username,
|
||||
p: self.password,
|
||||
};
|
||||
var params: any = {};
|
||||
|
||||
if (self.username) {
|
||||
params.username = self.username;
|
||||
params.password = self.password;
|
||||
}
|
||||
|
||||
if (self.database) {
|
||||
params.db = self.database;
|
||||
@@ -223,18 +249,18 @@ export default class InfluxDatasource {
|
||||
}, function(err) {
|
||||
if (err.status !== 0 || err.status >= 300) {
|
||||
if (err.data && err.data.error) {
|
||||
throw { message: 'InfluxDB Error Response: ' + err.data.error, data: err.data, config: err.config };
|
||||
throw { message: 'InfluxDB Error: ' + err.data.error, data: err.data, config: err.config };
|
||||
} else {
|
||||
throw { message: 'InfluxDB Error: ' + err.message, data: err.data, config: err.config };
|
||||
throw { message: 'Network Error: ' + err.statusText + '(' + err.status + ')', data: err.data, config: err.config };
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
getTimeFilter(options) {
|
||||
var from = this.getInfluxTime(options.rangeRaw.from, false);
|
||||
var until = this.getInfluxTime(options.rangeRaw.to, true);
|
||||
var fromIsAbsolute = from[from.length-1] === 's';
|
||||
var fromIsAbsolute = from[from.length-1] === 'ms';
|
||||
|
||||
if (until === 'now()' && !fromIsAbsolute) {
|
||||
return 'time > ' + from;
|
||||
@@ -257,7 +283,8 @@ export default class InfluxDatasource {
|
||||
}
|
||||
date = dateMath.parse(date, roundUp);
|
||||
}
|
||||
return (date.valueOf() / 1000).toFixed(0) + 's';
|
||||
|
||||
return date.valueOf() + 'ms';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="256px" height="265px" viewBox="0 0 256 265" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M82.472546,256.199228 C75.4995714,246.636291 77.6910777,233.487253 87.2540143,226.514279 C89.2462927,225.119684 91.4377991,224.123545 93.6293054,223.326633 L94.0277611,215.357519 C88.8478371,213.166013 84.6640523,207.587633 84.2655966,202.009254 L76.4957106,200.016975 C75.1011157,202.009254 73.5072929,203.603076 71.5150144,204.997671 C61.9520778,211.970646 48.6038121,209.77914 41.8300654,200.216203 C34.8570907,190.653266 37.0485971,177.504229 46.6115337,170.531254 C56.1744703,163.558279 69.3235081,165.749786 76.2964827,175.312722 C78.0895334,177.902684 79.4841283,180.891102 80.0818118,183.87952 L93.6293054,187.465621 C97.2154066,186.071026 101.199964,185.871798 104.786065,187.266393 L112.555951,181.688013 C110.563672,173.519672 113.751318,164.554419 120.724292,159.573722 C130.287229,152.600748 143.635495,154.792254 150.409242,164.355191 C157.382216,173.7189 155.19071,187.067165 145.627773,194.04014 C138.654799,199.020836 129.29109,199.419292 122.118887,195.036279 L114.349001,200.614659 C114.548229,204.399988 113.153634,208.185317 110.7629,211.173735 L109.965989,225.119684 C112.755179,226.713507 115.145913,228.705785 116.938963,231.295747 C123.71271,240.858684 121.720432,254.007722 112.157495,260.980696 C102.793786,267.953671 89.4455206,265.762164 82.472546,256.199228 L82.472546,256.199228 Z" fill="#0690BA"></path>
|
||||
<path d="M112.157495,234.68262 C109.965989,231.694203 106.977571,229.701924 103.789926,228.905013 L104.985293,208.583773 C108.172938,205.993811 109.368305,201.810026 107.97371,198.024697 L122.318115,187.664849 C127.498039,193.044001 136.064837,193.840912 142.2409,189.258672 C149.213874,184.277975 150.608469,174.715039 145.627773,167.742064 C140.647077,160.968317 131.08414,159.374495 124.111166,164.355191 C117.935103,168.937431 115.942824,177.305001 119.528925,183.87952 L104.985293,194.239368 C101.996875,191.649406 97.6138623,191.450178 94.0277611,193.641684 L74.3042043,188.46176 C74.5034321,185.074887 73.5072929,181.488786 71.5150144,178.699596 C66.5343183,171.925849 56.7721538,170.332026 49.9984071,175.312722 C43.0254324,180.293418 41.6308375,189.856355 46.6115337,196.82933 C51.5922298,203.802304 61.1551664,205.196899 68.1281411,200.216203 C70.7181031,198.223925 72.5111537,195.83319 73.5072929,193.044001 L90.8401155,197.626241 C89.4455206,200.415431 89.8439763,204.001532 91.8362547,206.591494 C93.8285332,209.380684 97.0161788,210.576051 100.203824,210.376823 L99.2076851,228.307329 C96.2192674,228.307329 93.2308497,229.303469 90.6408877,231.295747 C83.8671409,236.276443 82.2733181,245.83938 87.2540143,252.812354 C92.2347104,259.785329 101.797647,261.179924 108.770622,256.199228 C115.743596,251.218532 117.138191,241.655595 112.157495,234.68262 L112.157495,234.68262 Z M127.896495,169.734343 C131.881052,166.945153 137.260204,167.742064 140.049394,171.726621 C142.838583,175.711178 142.041672,181.09033 138.057115,183.87952 C134.86947,186.270254 130.486457,186.071026 127.498039,183.680292 L130.088001,181.688013 C131.881052,182.684153 134.471014,182.684153 136.264064,181.289558 C138.854026,179.496507 139.45171,175.910406 137.459432,173.320444 C135.666381,170.730482 132.08028,170.132798 129.490318,172.125077 C127.697267,173.519672 126.900356,175.711178 127.298811,177.902684 L124.708849,179.894963 C123.513482,176.308862 124.708849,172.125077 127.896495,169.734343 L127.896495,169.734343 Z M64.5420398,195.235507 C60.3582551,198.223925 54.5806475,197.227785 51.5922298,193.044001 C48.6038121,188.860216 49.5999514,183.082608 53.7837361,180.094191 C57.9675209,177.105773 63.7451284,178.101912 66.7335461,182.086469 C67.7296854,183.481064 68.3273689,185.074887 68.5265967,186.66871 L65.3389512,185.871798 C65.1397234,185.274115 64.9404955,184.477203 64.342812,183.87952 C62.3505335,181.09033 58.3659766,180.492646 55.5767867,182.484925 C52.7875969,184.477203 52.1899134,188.46176 54.1821918,191.25095 C56.1744703,194.04014 60.1590272,194.637823 62.9482171,192.645545 C63.7451284,192.047861 64.342812,191.25095 64.7412677,190.454039 L67.7296854,191.25095 C66.932774,192.844773 65.9366347,194.239368 64.5420398,195.235507 L64.5420398,195.235507 Z M92.2347104,249.226253 C89.2462927,245.042468 90.242432,239.264861 94.2269889,236.276443 C95.6215838,235.280304 97.2154066,234.68262 98.8092294,234.483393 L98.6100015,237.671038 C97.6138623,237.870266 96.8169509,238.069494 96.0200395,238.667177 C93.2308497,240.659456 92.6331661,244.644013 94.6254446,247.433203 C96.6177231,250.222392 100.60228,250.820076 103.39147,248.827798 C106.18066,246.835519 106.778343,242.850962 104.786065,240.061772 C104.387609,239.464089 103.789926,239.065633 103.192242,238.667177 L103.39147,235.280304 C104.786065,235.877988 106.18066,236.874127 107.176799,238.268722 C110.165217,242.452506 109.169077,248.230114 104.985293,251.218532 C101.000736,254.406177 95.2231281,253.410038 92.2347104,249.226253 L92.2347104,249.226253 Z" fill="#C3E8F2"></path>
|
||||
<path d="M239.065633,223.924317 C227.908874,227.510418 215.955203,221.334355 212.369102,210.177595 C211.57219,207.786861 211.372962,205.595355 211.372962,203.204621 L203.802304,200.415431 C200.216203,204.798444 193.442456,206.790722 188.063304,205.396127 L183.680292,212.169874 C185.074887,213.962924 186.270254,216.154431 186.867937,218.545165 C190.454039,229.701924 184.277975,241.655595 173.121216,245.440924 C161.964457,249.027025 150.010786,242.850962 146.424685,231.694203 C142.838583,220.537443 149.014647,208.583773 160.171406,204.997671 C163.159824,204.001532 166.347469,203.802304 169.535115,204.20076 L177.105773,192.446317 C176.906545,188.660988 177.902684,184.875659 180.492646,181.887241 L177.504229,172.72276 C169.136659,172.125077 161.765229,166.148241 158.976039,158.179128 C155.389938,147.022368 161.566001,135.068697 172.72276,131.482596 C183.87952,127.896495 195.83319,134.072558 199.419292,145.229318 C202.009254,153.397659 199.419292,162.362912 193.044001,167.941292 L196.032418,177.105773 C199.817747,178.101912 203.005393,180.492646 204.997671,183.87952 L214.560608,188.46176 C216.951342,186.270254 223.127405,185.074887 226.115823,184.078748 C237.272582,180.492646 249.226253,186.66871 253.011582,197.825469 C256.398456,208.384545 250.222392,220.338216 239.065633,223.924317 L239.065633,223.924317 Z" fill="#0690BA"></path>
|
||||
<path d="M227.709646,189.059444 C224.322772,190.254811 221.334355,192.446317 219.541304,195.235507 L200.415431,187.864077 C199.020836,184.078748 195.235507,181.688013 191.25095,181.887241 L185.871798,165.152102 C192.645545,161.765229 196.032418,153.995343 193.641684,146.623912 C191.051722,138.455571 182.285697,134.072558 174.316583,136.66252 C166.347469,139.252482 161.765229,148.018507 164.355191,155.987621 C166.745925,163.359052 174.117355,167.742064 181.488786,166.347469 L186.867937,183.281836 C183.481064,185.473342 181.887241,189.457899 183.082608,193.442456 L171.925849,210.576051 C168.738203,209.380684 165.152102,209.181456 161.765229,210.177595 C153.596887,212.767557 149.213874,221.533583 151.803836,229.502696 C154.393798,237.671038 163.159824,242.054051 171.128938,239.464089 C179.297279,236.874127 183.680292,228.108102 181.09033,220.138988 C180.094191,217.15057 178.30114,214.560608 175.910406,212.767557 L185.67257,197.626241 C188.063304,199.817747 191.450178,200.614659 194.637823,199.61852 C197.825469,198.62238 200.016975,196.032418 200.813887,192.844773 L217.549026,199.419292 C216.752114,202.208482 216.752114,205.396127 217.748254,208.384545 C220.338216,216.552886 229.104241,220.935899 237.073355,218.345937 C245.241696,215.755975 249.624709,206.98995 247.034747,199.020836 C244.444785,190.852494 235.877988,186.469482 227.709646,189.059444 L227.709646,189.059444 Z M170.92971,153.995343 C169.535115,149.413102 171.925849,144.432406 176.508089,143.037811 C181.09033,141.443988 186.071026,144.03395 187.465621,148.616191 C188.660988,152.40152 187.067165,156.585305 183.87952,158.577583 L182.88338,155.589166 C184.477203,154.194571 185.274115,151.803836 184.477203,149.61233 C183.481064,146.623912 180.293418,145.03009 177.305001,146.026229 C174.316583,147.022368 172.72276,150.210014 173.7189,153.198431 C174.515811,155.389938 176.308862,156.784533 178.500368,157.182988 L179.496507,160.370634 C175.711178,160.171406 172.125077,157.780672 170.92971,153.995343 L170.92971,153.995343 Z M175.51195,222.131266 C177.105773,227.111962 174.316583,232.291886 169.535115,233.885709 C164.554419,235.479532 159.374495,232.690342 157.780672,227.908874 C156.186849,223.127405 158.976039,217.748254 163.757507,216.154431 C165.35133,215.556747 167.144381,215.556747 168.738203,215.955203 L166.945153,218.744393 C166.347469,218.744393 165.550558,218.744393 164.753646,218.943621 C161.566001,219.93976 159.77295,223.525861 160.76909,226.912734 C161.765229,230.10038 165.35133,231.893431 168.738203,230.897291 C171.925849,229.901152 173.7189,226.315051 172.72276,223.127405 C172.324305,222.131266 171.925849,221.334355 171.128938,220.736671 L172.921988,218.146709 C173.918127,219.142848 174.914267,220.537443 175.51195,222.131266 L175.51195,222.131266 Z M235.280304,212.56833 C230.299608,214.162152 225.119684,211.372962 223.525861,206.591494 C222.928178,204.997671 222.928178,203.204621 223.326633,201.610798 L226.315051,202.806165 C226.115823,203.802304 226.115823,204.798444 226.514279,205.595355 C227.510418,208.982228 231.096519,210.576051 234.483393,209.579912 C237.870266,208.583773 239.663317,204.997671 238.46795,201.610798 C237.47181,198.423152 233.885709,196.630102 230.498836,197.626241 C229.901152,197.825469 229.104241,198.223925 228.705785,198.62238 L225.51814,197.427013 C226.514279,196.231646 227.908874,195.235507 229.701924,194.637823 C234.68262,193.044001 239.862544,195.83319 241.456367,200.614659 C242.850962,205.794583 240.261,210.974507 235.280304,212.56833 L235.280304,212.56833 Z" fill="#C3E8F2"></path>
|
||||
<path d="M255.203089,66.7335461 C255.203089,78.4879891 245.640152,88.0509257 233.885709,88.0509257 C231.494975,88.0509257 229.104241,87.65247 223.326633,86.4571029 L218.345937,92.6331661 C224.721228,97.8130901 224.721228,104.786065 221.73281,109.567533 L226.713507,115.743596 C228.905013,114.946685 231.295747,114.548229 233.686481,114.548229 C245.440924,114.548229 255.003861,124.111166 255.003861,135.865609 C255.003861,147.620052 245.440924,157.182988 233.686481,157.182988 C221.932038,157.182988 212.369102,147.620052 212.369102,135.865609 C212.369102,132.677963 213.166013,129.490318 214.36138,126.701128 L205.595355,115.942824 C201.810026,114.946685 198.62238,112.755179 196.430874,109.567533 L186.867937,109.567533 C184.078748,117.337419 176.308862,122.517343 167.742064,122.517343 C155.987621,122.517343 146.424685,112.954406 146.424685,101.199964 C146.424685,89.4455206 155.987621,79.882584 167.742064,80.0818118 C176.308862,80.0818118 184.078748,85.4609637 187.266393,93.2308497 L196.82933,93.2308497 C199.020836,90.0432041 202.208482,87.65247 205.993811,86.8555586 L214.759836,76.0972549 C213.365241,73.3080651 212.767557,70.1204195 212.767557,66.932774 C212.767557,55.1783311 222.330494,45.6153944 234.084937,45.6153944 C245.83938,45.6153944 255.203089,55.1783311 255.203089,66.7335461 L255.203089,66.7335461 Z" fill="#0690BA"></path>
|
||||
<path d="M218.545165,66.7335461 C218.545165,70.3196474 219.740532,73.7065207 221.932038,76.2964827 L208.982228,92.0354826 C204.997671,92.2347104 201.41157,95.0239003 200.415431,98.8092294 L182.684153,98.8092294 C181.488786,91.4377991 175.113494,85.6601915 167.542836,85.6601915 C158.976039,85.6601915 152.202292,92.4339383 152.202292,101.000736 C152.202292,109.368305 158.976039,116.34128 167.542836,116.34128 C175.312722,116.34128 181.688013,110.563672 182.88338,103.192242 L200.614659,103.192242 C201.610798,106.977571 204.997671,109.766761 209.181456,109.965989 L222.131266,125.704989 C219.93976,128.294951 218.744393,131.681824 218.744393,135.267925 C218.744393,143.834723 225.717367,150.608469 234.084937,150.608469 C242.651734,150.608469 249.425481,143.635495 249.425481,135.267925 C249.425481,126.701128 242.651734,119.927381 234.084937,119.927381 C230.897291,119.927381 227.908874,120.92352 225.51814,122.517343 L214.162152,108.571394 C216.951342,106.977571 218.744393,103.989153 218.744393,100.801508 C218.744393,97.4146344 216.951342,94.4262168 214.162152,92.832394 L225.51814,78.8864447 C227.908874,80.4802675 230.897291,81.4764067 234.084937,81.4764067 C242.651734,81.4764067 249.425481,74.5034321 249.425481,66.1358626 C249.425481,57.5690652 242.452506,50.7953184 234.084937,50.7953184 C225.51814,51.393002 218.545165,58.3659766 218.545165,66.7335461 L218.545165,66.7335461 Z M167.742064,109.965989 C162.960596,109.965989 158.976039,105.981432 158.976039,101.199964 C158.976039,96.4184952 162.960596,92.4339383 167.742064,92.4339383 C171.726621,92.4339383 175.113494,95.2231281 176.109634,98.8092294 L172.921988,98.8092294 C172.125077,96.8169509 170.132798,95.422356 167.742064,95.422356 C164.554419,95.422356 162.163684,98.012318 162.163684,101.000736 C162.163684,104.188381 164.753646,106.778343 167.742064,106.778343 C170.132798,106.778343 172.125077,105.383748 172.921988,103.39147 L176.109634,103.39147 C175.113494,107.376027 171.726621,109.965989 167.742064,109.965989 L167.742064,109.965989 Z M233.885709,126.701128 C239.065633,126.701128 243.249418,130.884913 243.249418,136.064837 C243.249418,141.244761 239.065633,145.428545 233.885709,145.428545 C228.705785,145.428545 224.522,141.244761 224.522,136.064837 C224.522,134.271786 225.119684,132.677963 225.916595,131.283368 L228.108102,133.87333 C227.908874,134.471014 227.709646,135.267925 227.709646,136.064837 C227.709646,139.45171 230.498836,142.2409 233.885709,142.2409 C237.272582,142.2409 240.061772,139.45171 240.061772,136.064837 C240.061772,132.677963 237.272582,129.888773 233.885709,129.888773 C232.88957,129.888773 231.893431,130.088001 231.096519,130.486457 L229.104241,128.095723 C230.498836,127.099584 232.291886,126.701128 233.885709,126.701128 L233.885709,126.701128 Z M243.249418,66.7335461 C243.249418,71.9134701 239.065633,76.0972549 233.885709,76.0972549 C232.092658,76.0972549 230.498836,75.6987992 229.104241,74.70266 L231.096519,72.3119258 C231.893431,72.7103815 232.88957,72.9096094 233.885709,73.1088372 C237.272582,73.1088372 240.061772,70.3196474 240.061772,66.932774 C240.061772,63.5459006 237.272582,60.7567107 233.885709,60.7567107 C230.498836,60.7567107 227.709646,63.5459006 227.709646,66.932774 C227.709646,67.7296854 227.908874,68.3273689 228.108102,68.9250524 L225.916595,71.5150144 C225.119684,70.1204195 224.522,68.5265967 224.522,66.7335461 C224.522,61.5536221 228.705785,57.3698374 233.885709,57.3698374 C239.065633,57.5690652 243.249418,61.5536221 243.249418,66.7335461 L243.249418,66.7335461 Z" fill="#C3E8F2"></path>
|
||||
<path d="M110.7629,1.98449612 C121.91966,5.57059736 128.095723,17.723496 124.310394,28.8802554 C123.513482,31.2709895 122.517343,33.263268 120.92352,35.2555464 L125.306533,42.0292932 C130.884913,40.6346983 137.459432,42.8262046 141.045533,47.2092172 L148.616191,44.4200274 C148.616191,42.0292932 149.014647,39.6385591 149.61233,37.4470527 C153.198431,26.2902934 165.35133,20.1142301 176.508089,23.8995592 C187.664849,27.4856604 193.840912,39.4393312 190.055583,50.7953184 C186.469482,61.9520778 174.316583,68.1281411 163.159824,64.342812 C160.171406,63.3466727 157.382216,61.75285 155.19071,59.5613437 L142.2409,64.5420398 C140.248621,67.7296854 137.060976,70.3196474 133.275647,71.3157866 L130.287229,80.4802675 C136.66252,86.0586472 139.252482,95.0239003 136.66252,103.192242 C133.076419,114.349001 120.92352,120.525065 109.766761,116.739736 C98.8092294,112.555951 92.6331661,100.60228 96.2192674,89.4455206 C98.8092294,81.2771789 106.379888,75.4995714 114.747457,74.9018878 L117.735875,65.7374069 C115.345141,62.7489892 114.149774,58.9636601 114.548229,54.9791032 L106.977571,43.2246603 C103.789926,43.623116 100.60228,43.4238881 97.6138623,42.4277489 C86.4571029,38.8416477 80.2810397,26.8879769 83.8671409,15.7312175 C87.65247,4.57445812 99.6061408,-1.60160511 110.7629,1.98449612 L110.7629,1.98449612 Z" fill="#0690BA"></path>
|
||||
<path d="M99.4069129,36.8493692 C102.793786,38.0447363 106.379888,37.8455084 109.567533,36.6501414 L120.525065,53.7837361 C119.329698,57.7682931 121.122748,61.75285 124.310394,63.9443563 L118.732014,80.6794954 C111.360584,79.4841283 103.989153,83.6679131 101.598419,91.0393434 C99.0084572,99.2076851 103.39147,107.774482 111.360584,110.563672 C119.329698,113.153634 128.095723,108.770622 130.884913,100.801508 C133.275647,93.4300775 129.888773,85.6601915 123.115027,82.2733181 L128.693406,65.5381791 C132.677963,65.7374069 136.463292,63.3466727 137.857887,59.5613437 L156.98376,52.1899134 C158.776811,54.9791032 161.566001,57.3698374 165.152102,58.3659766 C173.320444,60.9559386 181.887241,56.572926 184.676431,48.4045843 C187.266393,40.2362426 182.88338,31.6694452 174.715039,28.8802554 C166.546697,26.2902934 157.9799,30.673306 155.19071,38.6424198 C154.194571,41.6308375 154.194571,44.8184831 154.991482,47.6076729 L138.256343,54.1821918 C137.658659,51.1937741 135.467153,48.4045843 132.279508,47.4084451 C129.091862,46.4123058 125.704989,47.2092172 123.314254,49.2014957 L113.55209,34.0601794 C115.942824,32.2671287 117.735875,29.6771667 118.732014,26.6887491 C121.321976,18.5204074 116.938963,9.95360997 108.770622,7.36364797 C100.60228,4.77368597 92.0354826,9.15669859 89.4455206,17.3250403 C86.8555586,25.493382 91.2385712,34.2594072 99.4069129,36.8493692 L99.4069129,36.8493692 Z M124.708849,98.8092294 C123.115027,103.39147 118.333558,105.981432 113.751318,104.387609 C109.169077,102.793786 106.579115,98.012318 108.172938,93.4300775 C109.368305,89.6447484 113.153634,87.2540143 116.938963,87.4532421 L115.942824,90.4416598 C113.751318,90.6408877 111.958267,92.2347104 111.161356,94.2269889 C110.165217,97.2154066 111.759039,100.403052 114.747457,101.399191 C117.735875,102.395331 120.92352,100.801508 121.91966,97.8130901 C122.716571,95.6215838 121.91966,93.2308497 120.325837,91.8362547 L121.321976,88.6486092 C124.509622,90.8401155 126.103444,94.8246724 124.708849,98.8092294 L124.708849,98.8092294 Z M160.968317,40.8339261 C162.56214,35.85323 167.742064,33.263268 172.72276,34.8570907 C177.504229,36.4509135 180.293418,41.8300654 178.699596,46.6115337 C177.105773,51.393002 171.925849,54.1821918 166.945153,52.5883691 C165.35133,51.9906855 163.956735,50.9945463 162.960596,49.7991792 L166.148241,48.6038121 C166.745925,49.0022678 167.343608,49.4007235 168.14052,49.5999514 C171.328165,50.5960906 174.914267,48.80304 175.910406,45.6153944 C176.906545,42.4277489 175.113494,38.8416477 171.925849,37.8455084 C168.738203,36.8493692 165.152102,38.6424198 163.956735,41.8300654 C163.757507,42.8262046 163.558279,43.8223438 163.757507,44.6192552 L160.76909,45.8146223 C160.569862,44.2207995 160.569862,42.4277489 160.968317,40.8339261 L160.968317,40.8339261 Z M106.977571,13.3404834 C111.958267,14.9343061 114.548229,20.1142301 112.954406,25.0949263 C112.356723,26.6887491 111.559812,28.083344 110.165217,29.2787111 L108.571394,26.6887491 C109.169077,26.0910655 109.766761,25.2941541 110.165217,24.2980149 C111.161356,20.9111415 109.368305,17.5242681 106.18066,16.328901 C102.993014,15.3327618 99.4069129,17.1258124 98.2115458,20.313458 C97.2154066,23.5011035 99.0084572,27.0872047 102.196103,28.083344 C102.793786,28.2825718 103.590698,28.4817997 104.188381,28.2825718 L105.981432,31.0717617 C104.387609,31.4702174 102.793786,31.4702174 101.000736,30.8725338 C96.0200395,29.2787111 93.4300775,24.098787 95.0239003,19.1180909 C96.8169509,14.5358504 102.196103,11.7466606 106.977571,13.3404834 L106.977571,13.3404834 Z" fill="#C3E8F2"></path>
|
||||
<path d="M34.6578629,114.149774 C36.6501414,115.544368 38.2439641,117.337419 39.6385591,119.329698 L47.4084451,117.337419 C47.8069007,111.759039 51.9906855,105.981432 57.1706095,103.989153 L56.7721538,96.0200395 C54.5806475,95.422356 52.3891412,94.2269889 50.3968627,92.832394 C41.033154,85.6601915 38.8416477,72.3119258 45.8146223,62.7489892 C52.7875969,53.1860526 65.9366347,51.1937741 75.4995714,58.1667487 C84.8632801,65.1397234 87.0547864,78.2887612 80.0818118,87.8516978 C78.2887612,90.4416598 75.8980271,92.4339383 73.1088372,94.0277611 L73.9057486,107.97371 C76.2964827,110.962128 77.6910777,114.747457 77.4918498,118.532786 L85.2617358,124.111166 C92.4339383,119.728153 101.797647,120.126609 108.770622,125.107305 C118.333558,132.08028 120.325837,145.428545 113.352862,154.792254 C106.579115,164.355191 93.2308497,166.347469 83.6679131,159.573722 C76.6949384,154.593026 73.5072929,145.627773 75.4995714,137.459432 L67.7296854,131.881052 C64.1435841,133.275647 60.1590272,133.275647 56.572926,131.681824 L43.0254324,135.267925 C42.4277489,138.455571 41.2323818,141.443988 39.2401034,143.834723 C32.4663566,153.198431 19.3173187,155.389938 9.75438212,148.416963 C0.191445508,141.643216 -1.80083295,128.294951 4.97291382,118.732014 C11.9458884,109.169077 25.0949263,107.176799 34.6578629,114.149774 Z" fill="#0690BA"></path>
|
||||
<path d="M34.6578629,140.447849 C36.8493692,137.459432 37.8455084,134.072558 37.6462806,130.685685 L57.3698374,125.505761 C60.7567107,127.697267 65.1397234,127.498039 68.3273689,124.908077 L82.6717738,135.267925 C79.2849004,141.842444 81.0779511,150.210014 87.2540143,154.792254 C94.2269889,159.77295 103.789926,158.378355 108.770622,151.405381 C113.751318,144.631634 112.356723,134.86947 105.383748,129.888773 C99.2076851,125.306533 90.6408877,126.103444 85.2617358,131.482596 L70.9173309,121.122748 C72.3119258,117.536647 71.3157866,113.153634 68.1281411,110.563672 L66.932774,90.242432 C70.3196474,89.4455206 73.3080651,87.4532421 75.3003435,84.4648244 C80.2810397,77.4918498 78.6872169,67.9289132 71.9134701,62.9482171 C65.1397234,57.9675209 55.3775589,59.3621158 50.3968627,66.3350904 C45.4161666,73.1088372 46.8107615,82.8710017 53.7837361,87.8516978 C56.3736981,89.6447484 59.3621158,90.6408877 62.3505335,90.8401155 L63.3466727,108.770622 C60.1590272,108.372166 56.9713817,109.766761 54.9791032,112.555951 C52.9868247,115.345141 52.7875969,118.732014 53.982964,121.521204 L36.6501414,126.103444 C35.6540021,123.314254 33.8609515,120.724292 31.2709895,118.931242 C24.2980149,113.950546 14.7350783,115.544368 9.75438212,122.318115 C4.77368597,129.091862 6.16828089,138.854026 13.1412555,143.834723 C20.1142301,148.815419 29.6771667,147.221596 34.6578629,140.447849 L34.6578629,140.447849 Z M101.399191,135.267925 C105.383748,138.057115 106.18066,143.635495 103.39147,147.420824 C100.60228,151.405381 95.0239003,152.202292 91.2385712,149.413102 C88.0509257,147.022368 86.8555586,142.838583 88.2501535,139.252482 L90.8401155,141.244761 C90.4416598,143.436267 91.2385712,145.627773 93.0316218,147.022368 C95.6215838,148.815419 99.2076851,148.217735 101.000736,145.827001 C102.793786,143.237039 102.395331,139.650938 99.8053686,137.857887 C98.012318,136.463292 95.422356,136.463292 93.6293054,137.459432 L90.6408877,135.467153 C93.8285332,133.076419 98.012318,132.877191 101.399191,135.267925 L101.399191,135.267925 Z M57.3698374,82.8710017 C53.1860526,79.882584 52.3891412,73.9057486 55.3775589,69.9211917 C58.3659766,65.7374069 64.342812,64.9404955 68.3273689,67.9289132 C72.5111537,70.9173309 73.5072929,76.8941663 70.3196474,80.8787232 C69.3235081,82.2733181 67.9289132,83.2694574 66.5343183,83.8671409 L66.3350904,80.4802675 C66.932774,80.0818118 67.5304575,79.6833561 67.9289132,79.0856726 C69.9211917,76.2964827 69.3235081,72.3119258 66.5343183,70.3196474 C63.7451284,68.3273689 59.7605715,68.9250524 57.7682931,71.7142423 C55.7760146,74.5034321 56.3736981,78.4879891 59.162888,80.4802675 C59.9597994,81.0779511 60.9559386,81.4764067 61.75285,81.4764067 L61.9520778,84.6640523 C60.5574829,84.4648244 58.9636601,83.8671409 57.3698374,82.8710017 L57.3698374,82.8710017 Z M14.7350783,125.904216 C17.723496,121.720432 23.7003314,120.724292 27.6848883,123.911938 C29.0794832,124.908077 30.0756224,126.302672 30.8725338,127.697267 L27.6848883,128.494178 C27.2864326,127.697267 26.6887491,126.900356 25.8918377,126.302672 C23.1026478,124.310394 19.1180909,124.908077 17.1258124,127.697267 C15.133534,130.486457 15.7312175,134.471014 18.5204074,136.463292 C21.3095972,138.455571 25.2941541,137.857887 27.2864326,135.068697 C27.6848883,134.471014 28.083344,133.87333 28.2825718,133.076419 L31.6694452,132.279508 C31.4702174,133.87333 31.0717617,135.467153 29.8763946,136.861748 C26.8879769,141.045533 21.1103694,141.842444 16.9265846,138.854026 C12.543572,135.865609 11.7466606,129.888773 14.7350783,125.904216 L14.7350783,125.904216 Z" fill="#C3E8F2"></path>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<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"
|
||||
viewBox="0 0 78.8 79.9" style="enable-background:new 0 0 78.8 79.9;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:url(#symbol_1_);}
|
||||
</style>
|
||||
<g id="influxdb_logo">
|
||||
|
||||
<linearGradient id="symbol_1_" gradientUnits="userSpaceOnUse" x1="41.5273" y1="41.85" x2="319.2919" y2="41.85" gradientTransform="matrix(1 0 0 -1 0 81.8)">
|
||||
<stop offset="0" style="stop-color:#4591ED"/>
|
||||
<stop offset="1" style="stop-color:#00C9FF"/>
|
||||
</linearGradient>
|
||||
<path id="symbol_2_" class="st0" d="M78.7,48.2L71.1,15c-0.4-1.8-2.1-3.6-3.9-4.1L32.3,0.2c-0.5-0.1-1-0.2-1.5-0.2
|
||||
c-1.5,0-3.1,0.6-4,1.5l-25,23.2c-1.3,1.2-2.1,3.6-1.7,5.4l8.1,35.5c0.4,1.8,2.1,3.6,3.9,4.1l32.6,10c0.5,0.1,1,0.2,1.5,0.2
|
||||
c1.5,0,3.1-0.6,4-1.5l26.7-24.8C78.4,52.4,79.1,50,78.7,48.2z M35.9,8l23.9,7.3c0.9,0.3,0.9,0.7,0,0.9l-12.6,2.9
|
||||
c-1,0.2-2.3-0.2-2.9-0.9l-8.8-9.5C34.8,8.1,35,7.8,35.9,8z M50.8,50.9c0.2,1-0.4,1.5-1.3,1.2l-25.8-7.9c-0.9-0.3-1.1-1.1-0.4-1.7
|
||||
l19.8-18.4c0.7-0.7,1.5-0.4,1.7,0.5L50.8,50.9z M8.3,27.5L29.3,8c0.7-0.7,1.8-0.6,2.5,0.1l10.5,11.3c0.7,0.7,0.6,1.8-0.1,2.5
|
||||
l-21,19.5c-0.7,0.7-1.8,0.6-2.5-0.1L8.2,30C7.6,29.3,7.6,28.2,8.3,27.5z M13.4,58.5L7.8,34.2c-0.2-1,0.1-1.1,0.8-0.4l8.8,9.5
|
||||
c0.7,0.7,1,2.1,0.7,3l-3.8,12.3C14.1,59.4,13.6,59.4,13.4,58.5z M44.1,72.6l-27.3-8.4c-0.9-0.3-1.5-1.3-1.2-2.2l4.5-14.8
|
||||
c0.3-0.9,1.3-1.5,2.2-1.2l27.3,8.4c0.9,0.3,1.5,1.3,1.2,2.2l-4.5,14.8C46,72.4,45,72.9,44.1,72.6z M68.4,52.7l-18.3,17
|
||||
c-0.7,0.7-1.1,0.4-0.8-0.5l3.8-12.3c0.3-0.9,1.3-1.9,2.3-2.1L68,51.9C68.9,51.7,69.1,52.1,68.4,52.7z M70.4,49.1l-15.1,3.4
|
||||
c-1,0.2-1.9-0.4-2.1-1.3l-6.4-27.9c-0.2-1,0.4-1.9,1.3-2.1l15.1-3.4c1-0.2,1.9,0.4,2.1,1.3L71.7,47C71.9,47.9,71.3,48.9,70.4,49.1z
|
||||
"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -21,9 +21,10 @@ export default class InfluxQuery {
|
||||
target.policy = target.policy || 'default';
|
||||
target.dsType = 'influxdb';
|
||||
target.resultFormat = target.resultFormat || 'time_series';
|
||||
target.orderByTime = target.orderByTime || 'ASC';
|
||||
target.tags = target.tags || [];
|
||||
target.groupBy = target.groupBy || [
|
||||
{type: 'time', params: ['$interval']},
|
||||
{type: 'time', params: ['$__interval']},
|
||||
{type: 'fill', params: ['null']},
|
||||
];
|
||||
target.select = target.select || [[
|
||||
@@ -167,7 +168,7 @@ export default class InfluxQuery {
|
||||
var policy = this.target.policy;
|
||||
var measurement = this.target.measurement || 'measurement';
|
||||
|
||||
if (!measurement.match('^/.*/')) {
|
||||
if (!measurement.match('^/.*/$')) {
|
||||
measurement = '"' + measurement+ '"';
|
||||
} else if (interpolate) {
|
||||
measurement = this.templateSrv.replace(measurement, this.scopedVars, 'regex');
|
||||
@@ -193,8 +194,8 @@ export default class InfluxQuery {
|
||||
}
|
||||
|
||||
var escapedValues = _.map(value, kbn.regexEscape);
|
||||
return escapedValues.join('|');
|
||||
};
|
||||
return '(' + escapedValues.join('|') + ')';
|
||||
}
|
||||
|
||||
render(interpolate?) {
|
||||
var target = this.target;
|
||||
@@ -249,6 +250,18 @@ export default class InfluxQuery {
|
||||
query += ' fill(' + target.fill + ')';
|
||||
}
|
||||
|
||||
if (target.orderByTime === 'DESC') {
|
||||
query += ' ORDER BY time DESC';
|
||||
}
|
||||
|
||||
if (target.limit) {
|
||||
query += ' LIMIT ' + target.limit;
|
||||
}
|
||||
|
||||
if (target.slimit) {
|
||||
query += ' SLIMIT ' + target.slimit;
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ function (_, TableModel) {
|
||||
_.each(this.series, function (series) {
|
||||
var titleCol = null;
|
||||
var timeCol = null;
|
||||
var tagsCol = null;
|
||||
var tagsCol = [];
|
||||
var textCol = null;
|
||||
|
||||
_.each(series.columns, function(column, index) {
|
||||
@@ -89,7 +89,7 @@ function (_, TableModel) {
|
||||
if (column === 'sequence_number') { return; }
|
||||
if (!titleCol) { titleCol = index; }
|
||||
if (column === self.annotation.titleColumn) { titleCol = index; return; }
|
||||
if (column === self.annotation.tagsColumn) { tagsCol = index; return; }
|
||||
if (_.includes((self.annotation.tagsColumn || '').replace(' ', '').split(","), column)) { tagsCol.push(index); return; }
|
||||
if (column === self.annotation.textColumn) { textCol = index; return; }
|
||||
});
|
||||
|
||||
@@ -98,7 +98,8 @@ function (_, TableModel) {
|
||||
annotation: self.annotation,
|
||||
time: + new Date(value[timeCol]),
|
||||
title: value[titleCol],
|
||||
tags: value[tagsCol],
|
||||
// Remove empty values, then split in different tags for comma separated values
|
||||
tags: _.flatten(tagsCol.filter(function (t) { return value[t]; }).map(function(t) { return value[t].split(","); })),
|
||||
text: value[textCol]
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
|
||||
<h5 class="section-heading">Query</h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.query' placeholder="select text from events where $timeFilter"></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h5 class="section-heading">Column mappings <tip>If your influxdb query returns more than one column you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
|
||||
<h5 class="section-heading">Field mappings <tip>If your influxdb query returns more than one field you need to specify the column names below. An annotation event is composed of a title, tags, and an additional text field.</tip></h5>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
|
||||
|
||||
<div class="gf-form" ng-if="ctrl.target.rawQuery">
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.query" spellcheck="false" ng-blur="ctrl.refresh()"></input>
|
||||
</div>
|
||||
<div ng-if="ctrl.target.rawQuery">
|
||||
<div class="gf-form">
|
||||
<textarea rows="3" class="gf-form-input" ng-model="ctrl.target.query" spellcheck="false" placeholder="InfuxDB Query" ng-model-onblur ng-change="ctrl.refresh()"></textarea>
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword">FORMAT AS</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form max-width-25" ng-hide="ctrl.target.resultFormat === 'table'">
|
||||
<label class="gf-form-label query-keyword">ALIAS BY</label>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-if="!ctrl.target.rawQuery">
|
||||
|
||||
@@ -11,7 +28,7 @@
|
||||
<label class="gf-form-label query-keyword width-7">FROM</label>
|
||||
|
||||
<metric-segment segment="ctrl.policySegment" get-options="ctrl.getPolicySegments()" on-change="ctrl.policyChanged()"></metric-segment>
|
||||
<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements(measurementFilter)" on-change="ctrl.measurementChanged()"></metric-segment>
|
||||
<metric-segment segment="ctrl.measurementSegment" get-options="ctrl.getMeasurements($query)" on-change="ctrl.measurementChanged()"></metric-segment>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
@@ -72,22 +89,57 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-30">
|
||||
<label class="gf-form-label query-keyword width-7">ALIAS BY</label>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">Format as</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
|
||||
<div class="gf-form-inline" ng-if="ctrl.target.orderByTime === 'DESC'">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">ORDER BY</label>
|
||||
<label class="gf-form-label pointer" ng-click="ctrl.removeOrderByTime()">time <span classs="query-keyword">DESC</span> <i class="fa fa-remove"></i></label>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-if="ctrl.target.limit">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">LIMIT</label>
|
||||
<input type="text" class="gf-form-input width-9" ng-model="ctrl.target.limit" spellcheck='false' placeholder="No Limit" ng-blur="ctrl.refresh()">
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-if="ctrl.target.slimit">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">SLIMIT</label>
|
||||
<input type="text" class="gf-form-input width-9" ng-model="ctrl.target.slimit" spellcheck='false' placeholder="No Limit" ng-blur="ctrl.refresh()">
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword width-7">FORMAT AS</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.resultFormat" ng-options="f.value as f.text for f in ctrl.resultFormats" ng-change="ctrl.refresh()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-hide="ctrl.target.resultFormat === 'table'">
|
||||
<div class="gf-form max-width-30">
|
||||
<label class="gf-form-label query-keyword width-7">ALIAS BY</label>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.alias" spellcheck='false' placeholder="Naming pattern" ng-blur="ctrl.refresh()">
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</query-editor-row>
|
||||
|
||||
@@ -4,16 +4,15 @@
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label"><i class="fa fa-wrench"></i></span>
|
||||
<span class="gf-form-label width-11">Group by time interval</span>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.panelCtrl.panel.interval" ng-blur="ctrl.panelCtrl.refresh();"
|
||||
<input type="text" class="gf-form-input width-16" ng-model="ctrl.panelCtrl.panel.interval" ng-blur="ctrl.panelCtrl.refresh();"
|
||||
spellcheck='false' placeholder="example: >10s">
|
||||
<span class="gf-form-label"><i class="fa fa-question-circle" bs-tooltip="'Set a low limit by having a greater sign: example: >60s'" data-placement="right"></i></span>
|
||||
<info-popover mode="right-absolute">
|
||||
Set a low limit by having a greater sign: example: >60s
|
||||
</info-popover>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<!--span class="gf-form-label">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</span-->
|
||||
<span class="gf-form-label width-10">
|
||||
<a ng-click="ctrl.panelCtrl.toggleEditorHelp(1);" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
@@ -23,7 +22,7 @@
|
||||
<span class="gf-form-label width-10">
|
||||
<a ng-click="ctrl.panelCtrl.toggleEditorHelp(2)" bs-tooltip="'click to show helpful info'" data-placement="bottom">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
stacking & and fill
|
||||
stacking & fill
|
||||
</a>
|
||||
</span>
|
||||
<span class="gf-form-label width-10">
|
||||
@@ -55,7 +54,7 @@
|
||||
<div class="grafana-info-box span6" ng-if="ctrl.panelCtrl.editorHelpIndex === 2">
|
||||
<h5>Stacking and fill</h5>
|
||||
<ul>
|
||||
<li>When stacking is enabled it important that points align</li>
|
||||
<li>When stacking is enabled it is important that points align</li>
|
||||
<li>If there are missing points for one series it can cause gaps or missing bars</li>
|
||||
<li>You must use fill(0), and select a group by time low limit</li>
|
||||
<li>Use the group by time option below your queries and specify for example >10s if your metrics are written every 10 seconds</li>
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
"defaultMatchFormat": "regex values",
|
||||
"metrics": true,
|
||||
"annotations": true,
|
||||
"alerting": true,
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/influxdb_logo.svg",
|
||||
|
||||
@@ -56,8 +56,11 @@ function (_) {
|
||||
query += ' WITH MEASUREMENT =~ /' + withMeasurementFilter +'/';
|
||||
}
|
||||
} else if (type === 'FIELDS') {
|
||||
query = 'SHOW FIELD KEYS FROM "' + this.target.measurement + '"';
|
||||
return query;
|
||||
if (!this.target.measurement.match('^/.*/')) {
|
||||
return 'SHOW FIELD KEYS FROM "' + this.target.measurement + '"';
|
||||
} else {
|
||||
return 'SHOW FIELD KEYS FROM ' + this.target.measurement;
|
||||
}
|
||||
} else if (type === 'RETENTION POLICIES') {
|
||||
query = 'SHOW RETENTION POLICIES on "' + this.database + '"';
|
||||
return query;
|
||||
@@ -88,7 +91,13 @@ function (_) {
|
||||
query += ' WHERE ' + whereConditions.join(' ');
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'MEASUREMENTS')
|
||||
{
|
||||
query += ' LIMIT 100';
|
||||
//Solve issue #2524 by limiting the number of measurements returned
|
||||
//LIMIT must be after WITH MEASUREMENT and WHERE clauses
|
||||
//This also could be used for TAG KEYS and TAG VALUES, if desired
|
||||
}
|
||||
return query;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
queryBuilder: any;
|
||||
groupBySegment: any;
|
||||
resultFormats: any[];
|
||||
orderByTime: any[];
|
||||
policySegment: any;
|
||||
tagSegments: any[];
|
||||
selectMenu: any;
|
||||
@@ -32,7 +33,6 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
{text: 'Time series', value: 'time_series'},
|
||||
{text: 'Table', value: 'table'},
|
||||
];
|
||||
|
||||
this.policySegment = uiSegmentSrv.newSegment(this.target.policy);
|
||||
|
||||
if (!this.target.measurement) {
|
||||
@@ -65,6 +65,10 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
this.removeTagFilterSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove tag filter --'});
|
||||
}
|
||||
|
||||
removeOrderByTime() {
|
||||
this.target.orderByTime = 'ASC';
|
||||
}
|
||||
|
||||
buildSelectMenu() {
|
||||
var categories = queryPart.getCategories();
|
||||
this.selectMenu = _.reduce(categories, function(memo, cat, key) {
|
||||
@@ -87,6 +91,15 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
if (!this.queryModel.hasFill()) {
|
||||
options.push(this.uiSegmentSrv.newSegment({value: 'fill(null)'}));
|
||||
}
|
||||
if (!this.target.limit) {
|
||||
options.push(this.uiSegmentSrv.newSegment({value: 'LIMIT'}));
|
||||
}
|
||||
if (!this.target.slimit) {
|
||||
options.push(this.uiSegmentSrv.newSegment({value: 'SLIMIT'}));
|
||||
}
|
||||
if (this.target.orderByTime === 'ASC') {
|
||||
options.push(this.uiSegmentSrv.newSegment({value: 'ORDER BY time DESC'}));
|
||||
}
|
||||
if (!this.queryModel.hasGroupByTime()) {
|
||||
options.push(this.uiSegmentSrv.newSegment({value: 'time($interval)'}));
|
||||
}
|
||||
@@ -98,7 +111,24 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
|
||||
groupByAction() {
|
||||
this.queryModel.addGroupBy(this.groupBySegment.value);
|
||||
switch (this.groupBySegment.value) {
|
||||
case 'LIMIT': {
|
||||
this.target.limit = 10;
|
||||
break;
|
||||
}
|
||||
case 'SLIMIT': {
|
||||
this.target.slimit = 10;
|
||||
break;
|
||||
}
|
||||
case 'ORDER BY time DESC': {
|
||||
this.target.orderByTime = 'DESC';
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this.queryModel.addGroupBy(this.groupBySegment.value);
|
||||
}
|
||||
}
|
||||
|
||||
var plusButton = this.uiSegmentSrv.newPlusButton();
|
||||
this.groupBySegment.value = plusButton.value;
|
||||
this.groupBySegment.html = plusButton.html;
|
||||
|
||||
@@ -15,6 +15,7 @@ var categories = {
|
||||
Aggregations: [],
|
||||
Selectors: [],
|
||||
Transformations: [],
|
||||
Predictors: [],
|
||||
Math: [],
|
||||
Aliasing: [],
|
||||
Fields: [],
|
||||
@@ -27,7 +28,7 @@ function createPart(part): any {
|
||||
}
|
||||
|
||||
return new QueryPart(part, def);
|
||||
};
|
||||
}
|
||||
|
||||
function register(options: any) {
|
||||
index[options.type] = new QueryPartDef(options);
|
||||
@@ -173,6 +174,15 @@ register({
|
||||
renderer: functionRenderer,
|
||||
});
|
||||
|
||||
register({
|
||||
type: 'mode',
|
||||
addStrategy: replaceAggregationAddStrategy,
|
||||
category: categories.Aggregations,
|
||||
params: [],
|
||||
defaultParams: [],
|
||||
renderer: functionRenderer,
|
||||
});
|
||||
|
||||
register({
|
||||
type: 'sum',
|
||||
addStrategy: replaceAggregationAddStrategy,
|
||||
@@ -224,11 +234,20 @@ register({
|
||||
type: 'moving_average',
|
||||
addStrategy: addTransformationStrategy,
|
||||
category: categories.Transformations,
|
||||
params: [{ name: "window", type: "number", options: [5, 10, 20, 30, 40]}],
|
||||
params: [{ name: "window", type: "int", options: [5, 10, 20, 30, 40]}],
|
||||
defaultParams: [10],
|
||||
renderer: functionRenderer,
|
||||
});
|
||||
|
||||
register({
|
||||
type: 'cumulative_sum',
|
||||
addStrategy: addTransformationStrategy,
|
||||
category: categories.Transformations,
|
||||
params: [],
|
||||
defaultParams: [],
|
||||
renderer: functionRenderer,
|
||||
});
|
||||
|
||||
register({
|
||||
type: 'stddev',
|
||||
addStrategy: addTransformationStrategy,
|
||||
@@ -241,15 +260,15 @@ register({
|
||||
register({
|
||||
type: 'time',
|
||||
category: groupByTimeFunctions,
|
||||
params: [{ name: "interval", type: "time", options: ['auto', '1s', '10s', '1m', '5m', '10m', '15m', '1h'] }],
|
||||
defaultParams: ['auto'],
|
||||
params: [{ name: "interval", type: "time", options: ['$__interval', '1s', '10s', '1m', '5m', '10m', '15m', '1h']}],
|
||||
defaultParams: ['$__interval'],
|
||||
renderer: functionRenderer,
|
||||
});
|
||||
|
||||
register({
|
||||
type: 'fill',
|
||||
category: groupByTimeFunctions,
|
||||
params: [{ name: "fill", type: "string", options: ['none', 'null', '0', 'previous'] }],
|
||||
params: [{ name: "fill", type: "string", options: ['none', 'null', '0', 'previous', 'linear'] }],
|
||||
defaultParams: ['null'],
|
||||
renderer: functionRenderer,
|
||||
});
|
||||
@@ -263,6 +282,25 @@ register({
|
||||
renderer: functionRenderer,
|
||||
});
|
||||
|
||||
// predictions
|
||||
register({
|
||||
type: 'holt_winters',
|
||||
addStrategy: addTransformationStrategy,
|
||||
category: categories.Predictors,
|
||||
params: [{ name: "number", type: "int", options: [5, 10, 20, 30, 40]}, { name: "season", type: "int", options: [0, 1, 2, 5, 10]}],
|
||||
defaultParams: [10, 2],
|
||||
renderer: functionRenderer,
|
||||
});
|
||||
|
||||
register({
|
||||
type: 'holt_winters_with_fit',
|
||||
addStrategy: addTransformationStrategy,
|
||||
category: categories.Predictors,
|
||||
params: [{ name: "number", type: "int", options: [5, 10, 20, 30, 40]}, { name: "season", type: "int", options: [0, 1, 2, 5, 10]}],
|
||||
defaultParams: [10, 2],
|
||||
renderer: functionRenderer,
|
||||
});
|
||||
|
||||
// Selectors
|
||||
register({
|
||||
type: 'bottom',
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('InfluxQuery', function() {
|
||||
}, templateSrv, {});
|
||||
|
||||
var queryText = query.render();
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)');
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($__interval) fill(null)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ describe('InfluxQuery', function() {
|
||||
}, templateSrv, {});
|
||||
|
||||
var queryText = query.render();
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "5m_avg"."cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)');
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "5m_avg"."cpu" WHERE $timeFilter GROUP BY time($__interval) fill(null)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('InfluxQuery', function() {
|
||||
}, templateSrv, {});
|
||||
|
||||
var queryText = query.render();
|
||||
expect(queryText).to.be('SELECT mean("value") /100 AS "text" FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(null)');
|
||||
expect(queryText).to.be('SELECT mean("value") /100 AS "text" FROM "cpu" WHERE $timeFilter GROUP BY time($__interval) fill(null)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('InfluxQuery', function() {
|
||||
var queryText = query.render();
|
||||
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server\\\\1\' AND $timeFilter'
|
||||
+ ' GROUP BY time($interval)');
|
||||
+ ' GROUP BY time($__interval)');
|
||||
});
|
||||
|
||||
it('should switch regex operator with tag value is regex', function() {
|
||||
@@ -69,7 +69,7 @@ describe('InfluxQuery', function() {
|
||||
}, templateSrv, {});
|
||||
|
||||
var queryText = query.render();
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "app" =~ /e.*/ AND $timeFilter GROUP BY time($interval)');
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "app" =~ /e.*/ AND $timeFilter GROUP BY time($__interval)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,7 +83,7 @@ describe('InfluxQuery', function() {
|
||||
|
||||
var queryText = query.render();
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' AND "app" = \'email\' AND ' +
|
||||
'$timeFilter GROUP BY time($interval)');
|
||||
'$timeFilter GROUP BY time($__interval)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('InfluxQuery', function() {
|
||||
|
||||
var queryText = query.render();
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE "hostname" = \'server1\' OR "hostname" = \'server2\' AND ' +
|
||||
'$timeFilter GROUP BY time($interval)');
|
||||
'$timeFilter GROUP BY time($__interval)');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,8 +123,7 @@ describe('InfluxQuery', function() {
|
||||
}, templateSrv, {});
|
||||
|
||||
var queryText = query.render();
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter ' +
|
||||
'GROUP BY time($interval), "host"');
|
||||
expect(queryText).to.be('SELECT mean("value") FROM "cpu" WHERE $timeFilter GROUP BY time($__interval), "host"');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -148,7 +147,7 @@ describe('InfluxQuery', function() {
|
||||
groupBy: [{type: 'time'}, {type: 'fill', params: ['0']}],
|
||||
}, templateSrv, {});
|
||||
var queryText = query.render();
|
||||
expect(queryText).to.be('SELECT "value" FROM "cpu" WHERE $timeFilter GROUP BY time($interval) fill(0)');
|
||||
expect(queryText).to.be('SELECT "value" FROM "cpu" WHERE $timeFilter GROUP BY time($__interval) fill(0)');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
|
||||
import InfluxSeries from '../influx_series';
|
||||
|
||||
describe('when generating timeseries from influxdb response', function() {
|
||||
|
||||
describe('given multiple fields for series', function() {
|
||||
var options = {
|
||||
alias: '',
|
||||
@@ -68,6 +67,7 @@ describe('when generating timeseries from influxdb response', function() {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('given measurement with default fieldname', function() {
|
||||
var options = { series: [
|
||||
{
|
||||
@@ -96,6 +96,7 @@ describe('when generating timeseries from influxdb response', function() {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given two series', function() {
|
||||
var options = {
|
||||
alias: '',
|
||||
@@ -206,5 +207,58 @@ describe('when generating timeseries from influxdb response', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('given annotation response', function() {
|
||||
describe('with empty tagsColumn', function() {
|
||||
var options = {
|
||||
alias: '',
|
||||
annotation: {},
|
||||
series: [
|
||||
{
|
||||
name: "logins.count",
|
||||
tags: {datacenter: 'Africa', server: 'server2'},
|
||||
columns: ["time", "datacenter", "hostname", "source", "value"],
|
||||
values: [
|
||||
[1481549440372, "America", "10.1.100.10", "backend", 215.7432653659507],
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
it('should multiple tags', function() {
|
||||
var series = new InfluxSeries(options);
|
||||
var annotations = series.getAnnotations();
|
||||
|
||||
expect(annotations[0].tags.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('given annotation response', function() {
|
||||
var options = {
|
||||
alias: '',
|
||||
annotation: {
|
||||
tagsColumn: 'datacenter, source'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: "logins.count",
|
||||
tags: {datacenter: 'Africa', server: 'server2'},
|
||||
columns: ["time", "datacenter", "hostname", "source", "value"],
|
||||
values: [
|
||||
[1481549440372, "America", "10.1.100.10", "backend", 215.7432653659507],
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
it('should multiple tags', function() {
|
||||
var series = new InfluxSeries(options);
|
||||
var annotations = series.getAnnotations();
|
||||
|
||||
expect(annotations[0].tags.length).to.be(2);
|
||||
expect(annotations[0].tags[0]).to.be('America');
|
||||
expect(annotations[0].tags[1]).to.be('backend');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -34,31 +34,31 @@ describe('InfluxQueryBuilder', function() {
|
||||
it('should have no conditions in measurement query for query with no tags', function() {
|
||||
var builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
|
||||
var query = builder.buildExploreQuery('MEASUREMENTS');
|
||||
expect(query).to.be('SHOW MEASUREMENTS');
|
||||
expect(query).to.be('SHOW MEASUREMENTS LIMIT 100');
|
||||
});
|
||||
|
||||
it('should have no conditions in measurement query for query with no tags and empty query', function() {
|
||||
var builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
|
||||
var query = builder.buildExploreQuery('MEASUREMENTS', undefined, '');
|
||||
expect(query).to.be('SHOW MEASUREMENTS');
|
||||
expect(query).to.be('SHOW MEASUREMENTS LIMIT 100');
|
||||
});
|
||||
|
||||
it('should have WITH MEASUREMENT in measurement query for non-empty query with no tags', function() {
|
||||
var builder = new InfluxQueryBuilder({ measurement: '', tags: [] });
|
||||
var query = builder.buildExploreQuery('MEASUREMENTS', undefined, 'something');
|
||||
expect(query).to.be('SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/');
|
||||
expect(query).to.be('SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/ LIMIT 100');
|
||||
});
|
||||
|
||||
it('should have WITH MEASUREMENT WHERE in measurement query for non-empty query with tags', function() {
|
||||
var builder = new InfluxQueryBuilder({ measurement: '', tags: [{key: 'app', value: 'email'}] });
|
||||
var query = builder.buildExploreQuery('MEASUREMENTS', undefined, 'something');
|
||||
expect(query).to.be("SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/ WHERE \"app\" = 'email'");
|
||||
expect(query).to.be("SHOW MEASUREMENTS WITH MEASUREMENT =~ /something/ WHERE \"app\" = 'email' LIMIT 100");
|
||||
});
|
||||
|
||||
it('should have where condition in measurement query for query with tags', function() {
|
||||
var builder = new InfluxQueryBuilder({measurement: '', tags: [{key: 'app', value: 'email'}]});
|
||||
var query = builder.buildExploreQuery('MEASUREMENTS');
|
||||
expect(query).to.be("SHOW MEASUREMENTS WHERE \"app\" = 'email'");
|
||||
expect(query).to.be("SHOW MEASUREMENTS WHERE \"app\" = 'email' LIMIT 100");
|
||||
});
|
||||
|
||||
it('should have where tag name IN filter in tag values query for query with one tag', function() {
|
||||
@@ -88,6 +88,12 @@ describe('InfluxQueryBuilder', function() {
|
||||
expect(query).to.be('SHOW FIELD KEYS FROM "cpu"');
|
||||
});
|
||||
|
||||
it('should build show field query with regexp', function() {
|
||||
var builder = new InfluxQueryBuilder({measurement: '/$var/', tags: [{key: 'app', value: 'email'}]});
|
||||
var query = builder.buildExploreQuery('FIELDS');
|
||||
expect(query).to.be('SHOW FIELD KEYS FROM /$var/');
|
||||
});
|
||||
|
||||
it('should build show retention policies query', function() {
|
||||
var builder = new InfluxQueryBuilder({measurement: 'cpu', tags: []}, 'site');
|
||||
var query = builder.buildExploreQuery('RETENTION POLICIES');
|
||||
|
||||
@@ -10,6 +10,9 @@ describe('InfluxDBQueryCtrl', function() {
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.controllers'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(angularMocks.module(function($compileProvider) {
|
||||
$compileProvider.preAssignBindingsEnabled(true);
|
||||
}));
|
||||
beforeEach(ctx.providePhase());
|
||||
|
||||
beforeEach(angularMocks.inject(($rootScope, $controller, $q) => {
|
||||
|
||||
@@ -30,4 +30,4 @@ class MixedDatasource {
|
||||
}
|
||||
}
|
||||
|
||||
export {MixedDatasource, MixedDatasource as Datasource}
|
||||
export {MixedDatasource, MixedDatasource as Datasource};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "Mixed datasource",
|
||||
"name": "-- Mixed --",
|
||||
"id": "mixed",
|
||||
|
||||
"builtIn": true,
|
||||
|
||||
3
public/app/plugins/datasource/mysql/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Grafana Fake Data Datasource - Native Plugin
|
||||
|
||||
This is the built in Fake Data Datasource that is used before any datasources are set up in your Grafana installation. It means you can create a graph without any data and still get an idea of what it would look like.
|
||||
179
public/app/plugins/datasource/mysql/datasource.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
export class MysqlDatasource {
|
||||
id: any;
|
||||
name: any;
|
||||
|
||||
/** @ngInject **/
|
||||
constructor(instanceSettings, private backendSrv, private $q, private templateSrv) {
|
||||
this.name = instanceSettings.name;
|
||||
this.id = instanceSettings.id;
|
||||
}
|
||||
|
||||
interpolateVariable(value) {
|
||||
if (typeof value === 'string') {
|
||||
return '\"' + value + '\"';
|
||||
}
|
||||
|
||||
var quotedValues = _.map(value, function(val) {
|
||||
return '\"' + val + '\"';
|
||||
});
|
||||
return quotedValues.join(',');
|
||||
}
|
||||
|
||||
query(options) {
|
||||
var queries = _.filter(options.targets, item => {
|
||||
return item.hide !== true;
|
||||
}).map(item => {
|
||||
return {
|
||||
refId: item.refId,
|
||||
intervalMs: options.intervalMs,
|
||||
maxDataPoints: options.maxDataPoints,
|
||||
datasourceId: this.id,
|
||||
rawSql: this.templateSrv.replace(item.rawSql, options.scopedVars, this.interpolateVariable),
|
||||
format: item.format,
|
||||
};
|
||||
});
|
||||
|
||||
if (queries.length === 0) {
|
||||
return this.$q.when({data: []});
|
||||
}
|
||||
|
||||
return this.backendSrv.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
data: {
|
||||
from: options.range.from.valueOf().toString(),
|
||||
to: options.range.to.valueOf().toString(),
|
||||
queries: queries,
|
||||
}
|
||||
}).then(this.processQueryResult.bind(this));
|
||||
}
|
||||
|
||||
annotationQuery(options) {
|
||||
if (!options.annotation.rawQuery) {
|
||||
return this.$q.reject({message: 'Query missing in annotation definition'});
|
||||
}
|
||||
|
||||
const query = {
|
||||
refId: options.annotation.name,
|
||||
datasourceId: this.id,
|
||||
rawSql: this.templateSrv.replace(options.annotation.rawQuery, options.scopedVars, this.interpolateVariable),
|
||||
format: 'table',
|
||||
};
|
||||
|
||||
return this.backendSrv.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
data: {
|
||||
from: options.range.from.valueOf().toString(),
|
||||
to: options.range.to.valueOf().toString(),
|
||||
queries: [query],
|
||||
}
|
||||
}).then(this.transformAnnotationResponse.bind(this, options));
|
||||
}
|
||||
|
||||
transformAnnotationResponse(options, data) {
|
||||
const table = data.data.results[options.annotation.name].tables[0];
|
||||
|
||||
let timeColumnIndex = -1;
|
||||
let titleColumnIndex = -1;
|
||||
let textColumnIndex = -1;
|
||||
let tagsColumnIndex = -1;
|
||||
|
||||
for (let i = 0; i < table.columns.length; i++) {
|
||||
if (table.columns[i].text === 'time_sec') {
|
||||
timeColumnIndex = i;
|
||||
} else if (table.columns[i].text === 'title') {
|
||||
titleColumnIndex = i;
|
||||
} else if (table.columns[i].text === 'text') {
|
||||
textColumnIndex = i;
|
||||
} else if (table.columns[i].text === 'tags') {
|
||||
tagsColumnIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeColumnIndex === -1) {
|
||||
return this.$q.reject({message: 'Missing mandatory time column (with time_sec column alias) in annotation query.'});
|
||||
}
|
||||
|
||||
const list = [];
|
||||
for (let i = 0; i < table.rows.length; i++) {
|
||||
const row = table.rows[i];
|
||||
list.push({
|
||||
annotation: options.annotation,
|
||||
time: Math.floor(row[timeColumnIndex]) * 1000,
|
||||
title: row[titleColumnIndex],
|
||||
text: row[textColumnIndex],
|
||||
tags: row[tagsColumnIndex] ? row[tagsColumnIndex].trim().split(/\s*,\s*/) : []
|
||||
});
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
testDatasource() {
|
||||
return this.backendSrv.datasourceRequest({
|
||||
url: '/api/tsdb/query',
|
||||
method: 'POST',
|
||||
data: {
|
||||
from: '5m',
|
||||
to: 'now',
|
||||
queries: [{
|
||||
refId: 'A',
|
||||
intervalMs: 1,
|
||||
maxDataPoints: 1,
|
||||
datasourceId: this.id,
|
||||
rawSql: "SELECT 1",
|
||||
format: 'table',
|
||||
}],
|
||||
}
|
||||
}).then(res => {
|
||||
return { status: "success", message: "Database Connection OK", title: "Success" };
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
if (err.data && err.data.message) {
|
||||
return { status: "error", message: err.data.message, title: "Error" };
|
||||
} else {
|
||||
return { status: "error", message: err.status, title: "Error" };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
processQueryResult(res) {
|
||||
var data = [];
|
||||
|
||||
if (!res.data.results) {
|
||||
return {data: data};
|
||||
}
|
||||
|
||||
for (let key in res.data.results) {
|
||||
let queryRes = res.data.results[key];
|
||||
|
||||
if (queryRes.series) {
|
||||
for (let series of queryRes.series) {
|
||||
data.push({
|
||||
target: series.name,
|
||||
datapoints: series.points,
|
||||
refId: queryRes.refId,
|
||||
meta: queryRes.meta,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (queryRes.tables) {
|
||||
for (let table of queryRes.tables) {
|
||||
table.type = 'table';
|
||||
table.refId = queryRes.refId;
|
||||
table.meta = queryRes.meta;
|
||||
data.push(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {data: data};
|
||||
}
|
||||
}
|
||||
|
||||
27
public/app/plugins/datasource/mysql/img/mysql_logo.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="391pt" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" height="202pt" viewBox="0 0 391 202" >
|
||||
<g transform="translate(0.738281, 121.593)" style="fill:#00618a; fill-rule:nonzero; stroke:none; stroke-width:0.87088; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;" >
|
||||
<path d="M0 62.235 C0 62.235 12.8418 62.235 12.8418 62.235 C12.8418 62.235 12.8418 11.524 12.8418 11.524 C12.8418 11.524 32.7226 55.732 32.7226 55.732 C35.0683 61.083 38.2793 62.976 44.5762 62.976 C50.874 62.976 53.9609 61.083 56.3076 55.732 C56.3076 55.732 76.1875 11.524 76.1875 11.524 C76.1875 11.524 76.1875 62.235 76.1875 62.235 C76.1875 62.235 89.0303 62.235 89.0303 62.235 C89.0303 62.235 89.0303 11.607 89.0303 11.607 C89.0303 6.668 87.0537 4.28 82.9795 3.046 C73.2246 0 66.6797 2.634 63.7158 9.22 C63.7158 9.22 44.206 52.85 44.206 52.85 C44.206 52.85 25.3135 9.22 25.3135 9.22 C22.4736 2.634 15.8056 0 6.05078 3.046 C1.97559 4.28 0 6.668 0 11.607 C0 11.607 0 62.235 0 62.235 Z" />
|
||||
</g>
|
||||
<g transform="translate(100.444, 142.416)" style="fill:#00618a; fill-rule:nonzero; stroke:none; stroke-width:0.87088; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;" >
|
||||
<path d="M0.002 0.136 C0.002 0.136 12.84 0.136 12.84 0.136 C12.84 0.136 12.84 28.074 12.84 28.074 C12.72 29.592 13.327 33.154 20.362 33.265 C23.951 33.321 48.062 33.265 48.287 33.265 C48.287 33.265 48.287 0 48.287 0 C48.287 0 61.155 0 61.155 0 C61.214 0 61.142 45.364 61.143 45.557 C61.214 56.745 47.261 59.175 40.83 59.363 C40.83 59.363 0.281 59.363 0.281 59.363 C0.281 59.363 0.281 50.724 0.281 50.724 C0.352 50.724 40.8 50.732 40.902 50.722 C49.167 49.849 48.191 45.741 48.19 44.358 C48.19 44.358 48.19 40.99 48.19 40.99 C48.19 40.99 20.895 40.99 20.895 40.99 C8.196 40.873 0.109 35.331 0.009 28.955 C0 28.364 0.283 0.415 0.002 0.136 Z" />
|
||||
</g>
|
||||
<g transform="translate(171.496, 124.557)" style="fill:#e48e00; fill-rule:nonzero; stroke:none; stroke-width:0.87088; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;" >
|
||||
<path d="M0 59.271 C0 59.271 36.92 59.271 36.92 59.271 C41.242 59.271 45.441 58.366 48.775 56.801 C54.331 54.25 57.048 50.793 57.048 46.264 C57.048 46.264 57.048 36.88 57.048 36.88 C57.048 33.175 53.961 29.717 47.91 27.413 C44.699 26.177 40.748 25.519 36.92 25.519 C36.92 25.519 21.361 25.519 21.361 25.519 C16.176 25.519 13.706 23.955 13.089 20.498 C12.965 20.086 12.965 19.756 12.965 19.345 C12.965 19.345 12.965 13.501 12.965 13.501 C12.965 13.171 12.965 12.841 13.089 12.43 C13.706 9.796 15.065 9.054 19.633 8.643 C20.004 8.643 20.497 8.56 20.868 8.56 C20.868 8.56 57.542 8.56 57.542 8.56 C57.542 8.56 57.542 0 57.542 0 C57.542 0 21.485 0 21.485 0 C16.299 0 13.582 0.329 11.113 1.069 C3.457 3.457 0.123 7.244 0.123 13.83 C0.123 13.83 0.123 21.321 0.123 21.321 C0.123 27.084 6.668 32.023 17.657 33.175 C18.893 33.257 20.127 33.339 21.361 33.339 C21.361 33.339 34.697 33.339 34.697 33.339 C35.192 33.339 35.686 33.339 36.056 33.422 C40.131 33.752 41.859 34.492 43.095 35.974 C43.835 36.714 44.082 37.456 44.082 38.279 C44.082 38.279 44.082 45.77 44.082 45.77 C44.082 46.676 43.465 47.829 42.231 48.816 C41.119 49.804 39.267 50.463 36.797 50.628 C36.303 50.628 35.933 50.71 35.439 50.71 C35.439 50.71 0 50.71 0 50.71 C0 50.71 0 59.271 0 59.271 Z" />
|
||||
</g>
|
||||
<g transform="translate(308.656, 124.557)" style="fill:#e48e00; fill-rule:nonzero; stroke:none; stroke-width:0.87088; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;" >
|
||||
<path d="M0 44.371 C0 53.179 6.544 58.119 19.757 59.107 C20.991 59.189 22.227 59.271 23.461 59.271 C23.461 59.271 56.925 59.271 56.925 59.271 C56.925 59.271 56.925 50.71 56.925 50.71 C56.925 50.71 23.214 50.71 23.214 50.71 C15.682 50.71 12.842 48.816 12.842 44.289 C12.842 44.289 12.842 0 12.842 0 C12.842 0 0 0 0 0 C0 0 0 44.371 0 44.371 Z" />
|
||||
</g>
|
||||
<g transform="translate(236.74, 124.666)" style="fill:#e48e00; fill-rule:nonzero; stroke:none; stroke-width:0.87088; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;" >
|
||||
<path d="M0 44.711 C0 44.711 0 14.189 0 14.189 C0 6.435 5.445 1.732 16.209 0.247 C17.323 0.083 18.561 0 19.674 0 C19.674 0 44.05 0 44.05 0 C45.287 0 46.402 0.083 47.639 0.247 C58.404 1.732 63.848 6.435 63.848 14.189 C63.848 14.189 63.848 44.711 63.848 44.711 C63.848 51.002 61.536 54.369 56.207 56.563 C56.207 56.563 68.857 67.981 68.857 67.981 C68.857 67.981 53.946 67.981 53.946 67.981 C53.946 67.981 43.712 58.743 43.712 58.743 C43.712 58.743 33.409 59.396 33.409 59.396 C33.409 59.396 19.674 59.396 19.674 59.396 C17.323 59.396 14.849 59.065 12.126 58.323 C3.96 56.096 0 51.806 0 44.711 Z M13.861 43.969 C13.861 44.382 13.984 44.794 14.108 45.289 C14.85 48.836 18.191 50.816 23.264 50.816 C23.264 50.816 34.931 50.816 34.931 50.816 C34.931 50.816 24.214 41.141 24.214 41.141 C24.214 41.141 39.125 41.141 39.125 41.141 C39.125 41.141 48.472 49.579 48.472 49.579 C50.194 48.661 51.328 47.257 51.724 45.454 C51.847 45.042 51.847 44.629 51.847 44.217 C51.847 44.217 51.847 14.932 51.847 14.932 C51.847 14.602 51.847 14.189 51.724 13.776 C50.981 10.477 47.64 8.58 42.691 8.58 C42.691 8.58 23.264 8.58 23.264 8.58 C17.573 8.58 13.861 11.055 13.861 14.932 C13.861 14.932 13.861 43.969 13.861 43.969 Z" />
|
||||
</g>
|
||||
<g transform="translate(241.59, -4.64258)" style="fill:#00618a; fill-rule:nonzero; stroke:none; stroke-width:0.87088; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;" >
|
||||
<path d="M135.289 114.997 C127.395 114.783 121.365 115.517 116.21 117.691 C114.745 118.309 112.409 118.325 112.17 120.161 C112.975 121.005 113.1 122.265 113.74 123.303 C114.971 125.295 117.049 127.964 118.903 129.364 C120.929 130.893 123.017 132.529 125.189 133.853 C129.052 136.209 133.365 137.554 137.085 139.914 C139.277 141.305 141.455 143.057 143.594 144.627 C144.651 145.403 145.362 146.61 146.736 147.096 C146.736 147.022 146.736 146.947 146.736 146.872 C146.014 145.953 145.828 144.689 145.165 143.729 C144.193 142.757 143.22 141.784 142.247 140.811 C139.394 137.024 135.772 133.698 131.922 130.935 C128.852 128.731 121.98 125.754 120.699 122.181 C120.624 122.106 120.55 122.031 120.474 121.956 C122.651 121.711 125.2 120.923 127.209 120.385 C130.584 119.48 133.6 119.714 137.085 118.814 C138.656 118.365 140.227 117.915 141.799 117.467 C141.799 117.167 141.799 116.869 141.799 116.569 C140.037 114.761 138.781 112.369 136.86 110.733 C131.834 106.453 126.348 102.177 120.699 98.6114 C117.566 96.6338 113.694 95.3487 110.374 93.6729 C109.257 93.1094 107.295 92.8164 106.557 91.877 C104.813 89.6533 103.863 86.835 102.518 84.2451 C99.7 78.8194 96.933 72.8936 94.437 67.1856 C92.734 63.293 91.622 59.4541 89.499 55.9619 C79.307 39.2051 68.336 29.0908 51.341 19.1494 C47.725 17.0352 43.371 16.2002 38.77 15.1094 C36.302 14.96 33.832 14.8096 31.363 14.6602 C29.856 14.0303 28.288 12.1865 26.874 11.293 C21.244 7.73633 6.803 0 2.633 10.1709 C0 16.5908 6.568 22.8555 8.917 26.1084 C10.566 28.3906 12.677 30.9492 13.855 33.5156 C14.63 35.2022 14.764 36.8936 15.427 38.6787 C17.059 43.0752 18.478 47.8584 20.589 51.9219 C21.656 53.9776 22.832 56.1436 24.181 57.9824 C25.008 59.1104 26.425 59.6074 26.649 61.3496 C25.263 63.2891 25.184 66.2998 24.405 68.7569 C20.897 79.8184 22.22 93.5674 27.323 101.754 C28.889 104.267 32.577 109.657 37.648 107.59 C42.084 105.783 41.093 100.184 42.362 95.2442 C42.649 94.124 42.473 93.3008 43.035 92.5508 C43.035 92.625 43.035 92.7002 43.035 92.7754 C44.382 95.4688 45.729 98.1621 47.076 100.855 C50.066 105.671 55.373 110.704 59.87 114.1 C62.202 115.861 64.038 118.906 67.053 119.936 C67.053 119.861 67.053 119.787 67.053 119.711 C66.977 119.711 66.903 119.711 66.828 119.711 C66.244 118.8 65.33 118.423 64.584 117.691 C62.827 115.969 60.874 113.828 59.421 111.855 C55.331 106.302 51.716 100.224 48.423 93.8975 C46.85 90.877 45.483 87.544 44.157 84.4697 C43.646 83.2842 43.652 81.4922 42.587 80.878 C41.135 83.1309 38.996 84.9522 37.873 87.6123 C36.077 91.8643 35.845 97.0498 35.18 102.427 C34.786 102.568 34.961 102.471 34.73 102.651 C31.604 101.897 30.505 98.6787 29.344 95.918 C26.405 88.9356 25.859 77.6924 28.445 69.6553 C29.114 67.5762 32.138 61.0264 30.914 59.1055 C30.33 57.1885 28.402 56.0801 27.323 54.6153 C25.988 52.8047 24.655 50.4209 23.731 48.3301 C21.326 42.8858 20.203 36.7744 17.671 31.2715 C16.461 28.6406 14.414 25.9785 12.733 23.6397 C10.872 21.0489 8.788 19.1406 7.346 16.0069 C6.833 14.8936 6.136 13.1113 6.897 11.9668 C7.139 11.1943 7.48 10.8721 8.244 10.6201 C9.546 9.61621 13.172 10.9541 14.528 11.5176 C18.128 13.0127 21.132 14.4365 24.181 16.4561 C25.645 17.4268 27.125 19.3037 28.894 19.8233 C29.567 19.8233 30.241 19.8233 30.914 19.8233 C34.074 20.5498 37.614 20.0489 40.566 20.9453 C45.784 22.5313 50.46 24.9981 54.707 27.6797 C67.645 35.8487 78.223 47.4776 85.459 61.3496 C86.623 63.583 87.127 65.7149 88.152 68.084 C90.221 72.8614 92.827 77.7774 94.885 82.4492 C96.939 87.1104 98.941 91.8145 101.844 95.6934 C103.371 97.7324 109.266 98.8262 111.945 99.958 C113.823 100.752 116.9 101.579 118.679 102.651 C122.077 104.701 125.369 107.141 128.556 109.386 C130.149 110.508 135.045 112.969 135.289 114.997 Z" />
|
||||
</g>
|
||||
<g transform="translate(272.504, 24.8027)" style="fill:#00618a; fill-rule:evenodd; stroke:none; stroke-width:0.87088; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;" >
|
||||
<path d="M4.041 0.0303 C2.395 0 1.231 0.21 0 0.4786 C0 0.5537 0 0.6289 0 0.7032 C0.075 0.7032 0.15 0.7032 0.225 0.7032 C1.01 2.3164 2.396 3.3545 3.367 4.7442 C4.115 6.3155 4.863 7.8868 5.612 9.4571 C5.686 9.3828 5.762 9.3077 5.836 9.2334 C7.226 8.2539 7.863 6.6866 7.856 4.295 C7.299 3.709 7.217 2.9737 6.734 2.2744 C6.092 1.3428 4.85 0.8135 4.041 0.0303 Z" />
|
||||
</g>
|
||||
<g transform="translate(371.609, 165.56)" style="fill:#e48e00; fill-rule:evenodd; stroke:none; stroke-width:1; stroke-linecap:butt; stroke-linejoin:miter; stroke-dasharray:none;" >
|
||||
<path d="M0 9.463 C0 15.117 4.467 18.927 9.464 18.927 C14.461 18.927 18.928 15.117 18.928 9.463 C18.928 3.81 14.461 0 9.464 0 C4.467 0 0 3.81 0 9.463 Z M16.808 9.463 C16.808 13.753 13.528 16.959 9.464 16.959 C5.351 16.959 2.12 13.753 2.12 9.463 C2.12 5.173 5.351 1.967 9.464 1.967 C13.528 1.967 16.808 5.173 16.808 9.463 Z M12.014 14.864 C12.014 14.864 14.133 14.864 14.133 14.864 C14.133 14.864 11.03 10.12 11.03 10.12 C12.695 9.943 13.956 9.135 13.956 7.167 C13.956 4.971 12.569 4.063 9.894 4.063 C9.894 4.063 5.679 4.063 5.679 4.063 C5.679 4.063 5.679 14.864 5.679 14.864 C5.679 14.864 7.496 14.864 7.496 14.864 C7.496 14.864 7.496 10.195 7.496 10.195 C7.496 10.195 9.187 10.195 9.187 10.195 C9.187 10.195 12.014 14.864 12.014 14.864 Z M7.496 8.681 C7.496 8.681 7.496 5.577 7.496 5.577 C7.496 5.577 9.59 5.577 9.59 5.577 C10.676 5.577 11.988 5.779 11.988 7.041 C11.988 8.554 10.802 8.681 9.439 8.681 C9.439 8.681 7.496 8.681 7.496 8.681 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
41
public/app/plugins/datasource/mysql/module.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import {MysqlDatasource} from './datasource';
|
||||
import {MysqlQueryCtrl} from './query_ctrl';
|
||||
|
||||
class MysqlConfigCtrl {
|
||||
static templateUrl = 'partials/config.html';
|
||||
}
|
||||
|
||||
const defaultQuery = `SELECT
|
||||
UNIX_TIMESTAMP(<time_column>) as time_sec,
|
||||
<title_column> as title,
|
||||
<text_column> as text,
|
||||
<tags_column> as tags
|
||||
FROM <table name>
|
||||
WHERE $__timeFilter(time_column)
|
||||
ORDER BY <time_column> ASC
|
||||
LIMIT 100
|
||||
`;
|
||||
|
||||
class MysqlAnnotationsQueryCtrl {
|
||||
static templateUrl = 'partials/annotations.editor.html';
|
||||
|
||||
annotation: any;
|
||||
|
||||
/** @ngInject **/
|
||||
constructor() {
|
||||
this.annotation.rawQuery = this.annotation.rawQuery || defaultQuery;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
MysqlDatasource,
|
||||
MysqlDatasource as Datasource,
|
||||
MysqlQueryCtrl as QueryCtrl,
|
||||
MysqlConfigCtrl as ConfigCtrl,
|
||||
MysqlAnnotationsQueryCtrl as AnnotationsQueryCtrl,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<textarea rows="10" class="gf-form-input" ng-model="ctrl.annotation.rawQuery" spellcheck="false" placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.panelCtrl.refresh()"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword" ng-click="ctrl.showHelp = !ctrl.showHelp">
|
||||
Show Help
|
||||
<i class="fa fa-caret-down" ng-show="ctrl.showHelp"></i>
|
||||
<i class="fa fa-caret-right" ng-hide="ctrl.showHelp"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.showHelp">
|
||||
<pre class="gf-form-pre alert alert-info"><h6>Annotation Query Format</h6>
|
||||
An annotation is an event that is overlayed on top of graphs. The query can have up to four columns per row, the time_sec column is mandatory. Annotation rendering is expensive so it is important to limit the number of rows returned.
|
||||
|
||||
- column with alias: <b>time_sec</b> for the annotation event. Format is UTC in seconds, use UNIX_TIMESTAMP(column)
|
||||
- column with alias <b>title</b> for the annotation title
|
||||
- column with alias: <b>text</b> for the annotation text
|
||||
- column with alias: <b>tags</b> for annotation tags. This is a comma separated string of tags e.g. 'tag1,tag2'
|
||||
|
||||
|
||||
Macros:
|
||||
- $__time(column) -> UNIX_TIMESTAMP(column) as time_sec
|
||||
- $__timeFilter(column) -> UNIX_TIMESTAMP(time_date_time) > from AND UNIX_TIMESTAMP(time_date_time) < 1492750877
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
40
public/app/plugins/datasource/mysql/partials/config.html
Normal file
@@ -0,0 +1,40 @@
|
||||
|
||||
<h3 class="page-heading">MySQL Connection</h3>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-7">Host</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.current.url' placeholder="" required></input>
|
||||
</div>
|
||||
|
||||
<div class="gf-form max-width-30">
|
||||
<span class="gf-form-label width-7">Database</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.current.database' placeholder="" required></input>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-15">
|
||||
<span class="gf-form-label width-7">User</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.current.user' placeholder=""></input>
|
||||
</div>
|
||||
<div class="gf-form max-width-15">
|
||||
<span class="gf-form-label width-7">Password</span>
|
||||
<input type="password" class="gf-form-input" ng-model='ctrl.current.password' placeholder=""></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
<div class="grafana-info-box">
|
||||
<h5>User Permission</h5>
|
||||
<p>
|
||||
The database user should only be granted SELECT permissions on the specified database & tables you want to query.
|
||||
Grafana does not validate that queries are safe so queries can contain any SQL statement. For example, statements
|
||||
like <code>USE otherdb;</code> and <code>DROP TABLE user;</code> would be executed. To protect against this we
|
||||
<strong>Highly</strong> recommmend you create a specific MySQL user with restricted permissions.
|
||||
|
||||
Checkout the <a class="external-link" target="_blank" href="http://docs.grafana.org/features/datasources/mysql/">MySQL Data Source Docs</a> for more information.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<query-editor-row query-ctrl="ctrl" can-collapse="false">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<textarea rows="10" class="gf-form-input" ng-model="ctrl.target.rawSql" spellcheck="false" placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.panelCtrl.refresh()"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword">Format as</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.format" ng-options="f.value as f.text for f in ctrl.formats" ng-change="ctrl.refresh()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword" ng-click="ctrl.showHelp = !ctrl.showHelp">
|
||||
Show Help
|
||||
<i class="fa fa-caret-down" ng-show="ctrl.showHelp"></i>
|
||||
<i class="fa fa-caret-right" ng-hide="ctrl.showHelp"></i>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.lastQueryMeta">
|
||||
<label class="gf-form-label query-keyword" ng-click="ctrl.showLastQuerySQL = !ctrl.showLastQuerySQL">
|
||||
Generated SQL
|
||||
<i class="fa fa-caret-down" ng-show="ctrl.showLastQuerySQL"></i>
|
||||
<i class="fa fa-caret-right" ng-hide="ctrl.showLastQuerySQL"></i>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.showLastQuerySQL">
|
||||
<pre class="gf-form-pre">{{ctrl.lastQueryMeta.sql}}</pre>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.showHelp">
|
||||
<pre class="gf-form-pre alert alert-info">Time series:
|
||||
- return column named time_sec (UTC in seconds), use UNIX_TIMESTAMP(column)
|
||||
- return column named value for the time point value
|
||||
- return column named metric to represent the series name
|
||||
|
||||
Table:
|
||||
- return any set of columns
|
||||
|
||||
Macros:
|
||||
- $__time(column) -> UNIX_TIMESTAMP(column) as time_sec
|
||||
- $__timeFilter(column) -> UNIX_TIMESTAMP(time_date_time) > from AND UNIX_TIMESTAMP(time_date_time) < 1492750877
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-show="ctrl.lastQueryError">
|
||||
<pre class="gf-form-pre alert alert-error">{{ctrl.lastQueryError}}</pre>
|
||||
</div>
|
||||
|
||||
</query-editor-row>
|
||||
21
public/app/plugins/datasource/mysql/plugin.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"type": "datasource",
|
||||
"name": "MySQL",
|
||||
"id": "mysql",
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/mysql_logo.svg",
|
||||
"large": "img/mysql_logo.svg"
|
||||
}
|
||||
},
|
||||
|
||||
"state": "alpha",
|
||||
"alerting": true,
|
||||
"annotations": true,
|
||||
"metrics": true
|
||||
}
|
||||
86
public/app/plugins/datasource/mysql/query_ctrl.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import {MysqlDatasource} from './datasource';
|
||||
import {QueryCtrl} from 'app/plugins/sdk';
|
||||
|
||||
export interface MysqlQuery {
|
||||
refId: string;
|
||||
format: string;
|
||||
alias: string;
|
||||
rawSql: string;
|
||||
}
|
||||
|
||||
export interface QueryMeta {
|
||||
sql: string;
|
||||
}
|
||||
|
||||
|
||||
const defaultQuery = `SELECT
|
||||
UNIX_TIMESTAMP(<time_column>) as time_sec,
|
||||
<value column> as value,
|
||||
<series name column> as metric
|
||||
FROM <table name>
|
||||
WHERE $__timeFilter(time_column)
|
||||
ORDER BY <time_column> ASC
|
||||
`;
|
||||
|
||||
export class MysqlQueryCtrl extends QueryCtrl {
|
||||
static templateUrl = 'partials/query.editor.html';
|
||||
|
||||
showLastQuerySQL: boolean;
|
||||
formats: any[];
|
||||
target: MysqlQuery;
|
||||
lastQueryMeta: QueryMeta;
|
||||
lastQueryError: string;
|
||||
showHelp: boolean;
|
||||
|
||||
/** @ngInject **/
|
||||
constructor($scope, $injector) {
|
||||
super($scope, $injector);
|
||||
|
||||
this.target.format = this.target.format || 'time_series';
|
||||
this.target.alias = "";
|
||||
this.formats = [
|
||||
{text: 'Time series', value: 'time_series'},
|
||||
{text: 'Table', value: 'table'},
|
||||
];
|
||||
|
||||
if (!this.target.rawSql) {
|
||||
|
||||
// special handling when in table panel
|
||||
if (this.panelCtrl.panel.type === 'table') {
|
||||
this.target.format = 'table';
|
||||
this.target.rawSql = "SELECT 1";
|
||||
} else {
|
||||
this.target.rawSql = defaultQuery;
|
||||
}
|
||||
}
|
||||
|
||||
this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope);
|
||||
this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope);
|
||||
}
|
||||
|
||||
onDataReceived(dataList) {
|
||||
this.lastQueryMeta = null;
|
||||
this.lastQueryError = null;
|
||||
|
||||
let anySeriesFromQuery = _.find(dataList, {refId: this.target.refId});
|
||||
if (anySeriesFromQuery) {
|
||||
this.lastQueryMeta = anySeriesFromQuery.meta;
|
||||
}
|
||||
}
|
||||
|
||||
onDataError(err) {
|
||||
if (err.data && err.data.results) {
|
||||
let queryRes = err.data.results[this.target.refId];
|
||||
if (queryRes) {
|
||||
this.lastQueryMeta = queryRes.meta;
|
||||
this.lastQueryError = queryRes.error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common';
|
||||
import moment from 'moment';
|
||||
import helpers from 'test/specs/helpers';
|
||||
import {MysqlDatasource} from '../datasource';
|
||||
|
||||
describe('MySQLDatasource', function() {
|
||||
var ctx = new helpers.ServiceTestContext();
|
||||
var instanceSettings = {name: 'mysql'};
|
||||
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['backendSrv']));
|
||||
|
||||
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
|
||||
ctx.$q = $q;
|
||||
ctx.$httpBackend = $httpBackend;
|
||||
ctx.$rootScope = $rootScope;
|
||||
ctx.ds = $injector.instantiate(MysqlDatasource, {instanceSettings: instanceSettings});
|
||||
$httpBackend.when('GET', /\.html$/).respond('');
|
||||
}));
|
||||
|
||||
describe('When performing annotationQuery', function() {
|
||||
let results;
|
||||
|
||||
const annotationName = 'MyAnno';
|
||||
|
||||
const options = {
|
||||
annotation: {
|
||||
name: annotationName,
|
||||
rawQuery: 'select time_sec, title, text, tags from table;'
|
||||
},
|
||||
range: {
|
||||
from: moment(1432288354),
|
||||
to: moment(1432288401)
|
||||
}
|
||||
};
|
||||
|
||||
const response = {
|
||||
results: {
|
||||
MyAnno: {
|
||||
refId: annotationName,
|
||||
tables: [
|
||||
{
|
||||
columns: [{text: 'time_sec'}, {text: 'title'}, {text: 'text'}, {text: 'tags'}],
|
||||
rows: [
|
||||
[1432288355, 'aTitle', 'some text', 'TagA,TagB'],
|
||||
[1432288390, 'aTitle2', 'some text2', ' TagB , TagC'],
|
||||
[1432288400, 'aTitle3', 'some text3']
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
ctx.backendSrv.datasourceRequest = function(options) {
|
||||
return ctx.$q.when({data: response, status: 200});
|
||||
};
|
||||
ctx.ds.annotationQuery(options).then(function(data) { results = data; });
|
||||
ctx.$rootScope.$apply();
|
||||
});
|
||||
|
||||
it('should return annotation list', function() {
|
||||
expect(results.length).to.be(3);
|
||||
|
||||
expect(results[0].title).to.be('aTitle');
|
||||
expect(results[0].text).to.be('some text');
|
||||
expect(results[0].tags[0]).to.be('TagA');
|
||||
expect(results[0].tags[1]).to.be('TagB');
|
||||
|
||||
expect(results[1].tags[0]).to.be('TagB');
|
||||
expect(results[1].tags[1]).to.be('TagC');
|
||||
|
||||
expect(results[2].tags.length).to.be(0);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -28,8 +28,8 @@ function (angular, _, dateMath) {
|
||||
|
||||
_.each(options.targets, function(target) {
|
||||
if (!target.metric) { return; }
|
||||
qs.push(convertTargetToQuery(target, options));
|
||||
});
|
||||
qs.push(convertTargetToQuery(target, options, this.tsdbVersion));
|
||||
}.bind(this));
|
||||
|
||||
var queries = _.compact(qs);
|
||||
|
||||
@@ -102,6 +102,26 @@ function (angular, _, dateMath) {
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
this.targetContainsTemplate = function(target) {
|
||||
if (target.filters && target.filters.length > 0) {
|
||||
for (var i = 0; i < target.filters.length; i++) {
|
||||
if (templateSrv.variableExists(target.filters[i].filter)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target.tags && Object.keys(target.tags).length > 0) {
|
||||
for (var tagKey in target.tags) {
|
||||
if (templateSrv.variableExists(target.tags[tagKey])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.performTimeSeriesQuery = function(queries, start, end) {
|
||||
var msResolution = false;
|
||||
if (this.tsdbResolution === 2) {
|
||||
@@ -224,7 +244,7 @@ function (angular, _, dateMath) {
|
||||
|
||||
var interpolated;
|
||||
try {
|
||||
interpolated = templateSrv.replace(query);
|
||||
interpolated = templateSrv.replace(query, {}, 'distributed');
|
||||
}
|
||||
catch (err) {
|
||||
return $q.reject(err);
|
||||
@@ -332,7 +352,7 @@ function (angular, _, dateMath) {
|
||||
var tagData = [];
|
||||
|
||||
if (!_.isEmpty(md.tags)) {
|
||||
_.each(_.pairs(md.tags), function(tag) {
|
||||
_.each(_.toPairs(md.tags), function(tag) {
|
||||
if (_.has(groupByTags, tag[0])) {
|
||||
tagData.push(tag[0] + "=" + tag[1]);
|
||||
}
|
||||
@@ -346,13 +366,13 @@ function (angular, _, dateMath) {
|
||||
return label;
|
||||
}
|
||||
|
||||
function convertTargetToQuery(target, options) {
|
||||
function convertTargetToQuery(target, options, tsdbVersion) {
|
||||
if (!target.metric || target.hide) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var query = {
|
||||
metric: templateSrv.replace(target.metric, options.scopedVars),
|
||||
metric: templateSrv.replace(target.metric, options.scopedVars, 'pipe'),
|
||||
aggregator: "avg"
|
||||
};
|
||||
|
||||
@@ -373,6 +393,11 @@ function (angular, _, dateMath) {
|
||||
if (target.counterResetValue && target.counterResetValue.length) {
|
||||
query.rateOptions.resetValue = parseInt(target.counterResetValue);
|
||||
}
|
||||
|
||||
if(tsdbVersion >= 2) {
|
||||
query.rateOptions.dropResets = !query.rateOptions.counterMax &&
|
||||
(!query.rateOptions.ResetValue || query.rateOptions.ResetValue === 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!target.disableDownsampling) {
|
||||
@@ -391,20 +416,24 @@ function (angular, _, dateMath) {
|
||||
|
||||
if (target.filters && target.filters.length > 0) {
|
||||
query.filters = angular.copy(target.filters);
|
||||
if(query.filters){
|
||||
for(var filter_key in query.filters){
|
||||
if (query.filters){
|
||||
for (var filter_key in query.filters) {
|
||||
query.filters[filter_key].filter = templateSrv.replace(query.filters[filter_key].filter, options.scopedVars, 'pipe');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query.tags = angular.copy(target.tags);
|
||||
if(query.tags){
|
||||
for(var tag_key in query.tags){
|
||||
if (query.tags){
|
||||
for (var tag_key in query.tags) {
|
||||
query.tags[tag_key] = templateSrv.replace(query.tags[tag_key], options.scopedVars, 'pipe');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target.explicitTags) {
|
||||
query.explicitTags = true;
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
@@ -249,6 +249,11 @@
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class="gf-form" ng-if="ctrl.tsdbVersion > 2">
|
||||
<gf-form-switch class="gf-form" label="Explicit tags" label-class="width-8 query-keyword" checked="ctrl.target.explicitTags" on-change="ctrl.targetBlur()">
|
||||
</gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
"metrics": true,
|
||||
"defaultMatchFormat": "pipe",
|
||||
"annotations": true,
|
||||
"alerting": true,
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/opentsdb_logo.png",
|
||||
|
||||
@@ -7,6 +7,10 @@ describe('OpenTsQueryCtrl', function() {
|
||||
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(angularMocks.module(function($compileProvider) {
|
||||
$compileProvider.preAssignBindingsEnabled(true);
|
||||
}));
|
||||
|
||||
beforeEach(ctx.providePhase(['backendSrv','templateSrv']));
|
||||
|
||||
beforeEach(ctx.providePhase());
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
}
|
||||
],
|
||||
"thresholds": "0,500,4000",
|
||||
"title": "Interal Storage Queue Length",
|
||||
"title": "Internal Storage Queue Length",
|
||||
"type": "singlestat",
|
||||
"valueFontSize": "70%",
|
||||
"valueMaps": [
|
||||
@@ -167,7 +167,7 @@
|
||||
"valueName": "current"
|
||||
},
|
||||
{
|
||||
"content": "<img src=\"public/app/plugins/datasource/prometheus/img/prometheus_logo.svg\" alt=\"Prometheus logo\" style=\"height: 40px;\">\n<span style=\"font-family: 'Open Sans', 'Helvetica Neue', Helvetica; font-size: 25px;vertical-align: text-top;color: #bbbfc2;margin-left: 10px;\">Prometheus</span>\n\n<p style=\"margin-top: 10px;\">You're using Prometheus, an open-source systems monitoring and alerting toolkit originally built at SoundCloud. For more information, check out the <a href=\"http://www.grafana.org/\">Grafana</a> and <a href=\"http://prometheus.io/\">Prometheus</a> projects.</p>",
|
||||
"content": "<img src=\"public/app/plugins/datasource/prometheus/img/prometheus_logo.svg\" alt=\"Prometheus logo\" style=\"height: 40px;\">\n<span style=\"font-family: 'Open Sans', 'Helvetica Neue', Helvetica; font-size: 25px;vertical-align: text-top;color: #bbbfc2;margin-left: 10px;\">Prometheus</span>\n\n<p style=\"margin-top: 10px;\">You're using Prometheus, an open-source systems monitoring and alerting toolkit originally built at SoundCloud. For more information, check out the <a href=\"https://grafana.com/\">Grafana</a> and <a href=\"http://prometheus.io/\">Prometheus</a> projects.</p>",
|
||||
"editable": true,
|
||||
"error": false,
|
||||
"id": 9,
|
||||
@@ -478,7 +478,7 @@
|
||||
"steppedLine": false,
|
||||
"targets": [
|
||||
{
|
||||
"expr": "prometheus_evaluator_duration_milliseconds{quantile!=\"0.01\", quantile!=\"0.05\"}",
|
||||
"expr": "prometheus_evaluator_duration_seconds{quantile!=\"0.01\", quantile!=\"0.05\"}",
|
||||
"interval": "",
|
||||
"intervalFactor": 2,
|
||||
"legendFormat": "{{quantile}}",
|
||||
@@ -563,7 +563,7 @@
|
||||
"title": "Grafana Docs",
|
||||
"tooltip": "",
|
||||
"type": "link",
|
||||
"url": "http://www.grafana.org/docs"
|
||||
"url": "http://docs.grafana.org/"
|
||||
},
|
||||
{
|
||||
"icon": "info",
|
||||
|
||||
@@ -4,8 +4,10 @@ import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import * as dateMath from 'app/core/utils/datemath';
|
||||
import PrometheusMetricFindQuery from './metric_find_query';
|
||||
import TableModel from 'app/core/table_model';
|
||||
|
||||
var durationSplitRegexp = /(\d+)(ms|s|m|h|d|w|M|y)/;
|
||||
|
||||
@@ -19,7 +21,6 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
this.directUrl = instanceSettings.directUrl;
|
||||
this.basicAuth = instanceSettings.basicAuth;
|
||||
this.withCredentials = instanceSettings.withCredentials;
|
||||
this.lastErrors = {};
|
||||
|
||||
this._request = function(method, url, requestId) {
|
||||
var options: any = {
|
||||
@@ -58,6 +59,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
return escapedValues.join('|');
|
||||
};
|
||||
|
||||
this.targetContainsTemplate = function(target) {
|
||||
return templateSrv.variableExists(target.expr);
|
||||
};
|
||||
|
||||
// Called once per panel (graph)
|
||||
this.query = function(options) {
|
||||
var self = this;
|
||||
@@ -69,9 +74,9 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
|
||||
options = _.clone(options);
|
||||
|
||||
_.each(options.targets, target => {
|
||||
for (let target of options.targets) {
|
||||
if (!target.expr || target.hide) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
activeTargets.push(target);
|
||||
@@ -84,44 +89,50 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
var intervalFactor = target.intervalFactor || 1;
|
||||
target.step = query.step = this.calculateInterval(interval, intervalFactor);
|
||||
var range = Math.ceil(end - start);
|
||||
// Prometheus drop query if range/step > 11000
|
||||
// calibrate step if it is too big
|
||||
if (query.step !== 0 && range / query.step > 11000) {
|
||||
target.step = query.step = Math.ceil(range / 11000);
|
||||
}
|
||||
|
||||
target.step = query.step = this.adjustStep(query.step, this.intervalSeconds(options.interval), range);
|
||||
queries.push(query);
|
||||
});
|
||||
}
|
||||
|
||||
// No valid targets, return the empty result to save a round trip.
|
||||
if (_.isEmpty(queries)) {
|
||||
var d = $q.defer();
|
||||
d.resolve({ data: [] });
|
||||
return d.promise;
|
||||
return $q.when({ data: [] });
|
||||
}
|
||||
|
||||
var allQueryPromise = _.map(queries, query => {
|
||||
return this.performTimeSeriesQuery(query, start, end);
|
||||
});
|
||||
|
||||
return $q.all(allQueryPromise).then(function(allResponse) {
|
||||
return $q.all(allQueryPromise).then(responseList => {
|
||||
var result = [];
|
||||
var index = 0;
|
||||
|
||||
_.each(allResponse, function(response, index) {
|
||||
_.each(responseList, (response, index) => {
|
||||
if (response.status === 'error') {
|
||||
self.lastErrors.query = response.error;
|
||||
throw response.error;
|
||||
}
|
||||
delete self.lastErrors.query;
|
||||
_.each(response.data.data.result, function(metricData) {
|
||||
result.push(self.transformMetricData(metricData, activeTargets[index], start, end));
|
||||
});
|
||||
|
||||
if (activeTargets[index].format === "table") {
|
||||
result.push(self.transformMetricDataToTable(response.data.data.result));
|
||||
} else {
|
||||
for (let metricData of response.data.data.result) {
|
||||
result.push(self.transformMetricData(metricData, activeTargets[index], start, end));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { data: result };
|
||||
});
|
||||
};
|
||||
|
||||
this.adjustStep = function(step, autoStep, range) {
|
||||
// Prometheus drop query if range/step > 11000
|
||||
// calibrate step if it is too big
|
||||
if (step !== 0 && range / step > 11000) {
|
||||
return Math.ceil(range / 11000);
|
||||
}
|
||||
return Math.max(step, autoStep);
|
||||
};
|
||||
|
||||
this.performTimeSeriesQuery = function(query, start, end) {
|
||||
if (start > end) {
|
||||
throw { message: 'Invalid time range' };
|
||||
@@ -171,15 +182,19 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
return $q.reject(err);
|
||||
}
|
||||
|
||||
var query = {
|
||||
expr: interpolated,
|
||||
step: '60s'
|
||||
};
|
||||
var step = '60s';
|
||||
if (annotation.step) {
|
||||
step = templateSrv.replace(annotation.step);
|
||||
}
|
||||
|
||||
var start = this.getPrometheusTime(options.range.from, false);
|
||||
var end = this.getPrometheusTime(options.range.to, true);
|
||||
var self = this;
|
||||
var query = {
|
||||
expr: interpolated,
|
||||
step: this.adjustStep(kbn.interval_to_seconds(step), 0, Math.ceil(end - start)) + 's'
|
||||
};
|
||||
|
||||
var self = this;
|
||||
return this.performTimeSeriesQuery(query, start, end).then(function(results) {
|
||||
var eventList = [];
|
||||
tagKeys = tagKeys.split(',');
|
||||
@@ -216,6 +231,10 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
};
|
||||
|
||||
this.calculateInterval = function(interval, intervalFactor) {
|
||||
return Math.ceil(this.intervalSeconds(interval) * intervalFactor);
|
||||
};
|
||||
|
||||
this.intervalSeconds = function(interval) {
|
||||
var m = interval.match(durationSplitRegexp);
|
||||
var dur = moment.duration(parseInt(m[1]), m[2]);
|
||||
var sec = dur.asSeconds();
|
||||
@@ -223,7 +242,7 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
sec = 1;
|
||||
}
|
||||
|
||||
return Math.ceil(sec * intervalFactor);
|
||||
return sec;
|
||||
};
|
||||
|
||||
this.transformMetricData = function(md, options, start, end) {
|
||||
@@ -256,6 +275,44 @@ export function PrometheusDatasource(instanceSettings, $q, backendSrv, templateS
|
||||
return { target: metricLabel, datapoints: dps };
|
||||
};
|
||||
|
||||
this.transformMetricDataToTable = function(series) {
|
||||
var table = new TableModel();
|
||||
var self = this;
|
||||
var i, j;
|
||||
|
||||
if (series.length === 0) {
|
||||
return table;
|
||||
}
|
||||
|
||||
_.each(series, function(series, seriesIndex) {
|
||||
if (seriesIndex === 0) {
|
||||
table.columns.push({text: 'Time', type: 'time'});
|
||||
_.each(_.keys(series.metric), function(key) {
|
||||
table.columns.push({text: key});
|
||||
});
|
||||
table.columns.push({text: 'Value'});
|
||||
}
|
||||
|
||||
if (series.values) {
|
||||
for (i = 0; i < series.values.length; i++) {
|
||||
var values = series.values[i];
|
||||
var reordered = [values[0] * 1000];
|
||||
if (series.metric) {
|
||||
for (var key in series.metric) {
|
||||
if (series.metric.hasOwnProperty(key)) {
|
||||
reordered.push(series.metric[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
reordered.push(parseFloat(values[1]));
|
||||
table.rows.push(reordered);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return table;
|
||||
};
|
||||
|
||||
this.createMetricLabel = function(labelData, options) {
|
||||
if (_.isUndefined(options) || _.isEmpty(options.legendFormat)) {
|
||||
return this.getOriginalMetricName(labelData);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
|
||||
<h5 class="section-heading">Search expression</h6>
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Search expression</span>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.annotation.expr' placeholder="ALERTS"></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">step</span>
|
||||
<input type="text" class="gf-form-input max-width-6" ng-model='ctrl.annotation.step' placeholder="60s"></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group">
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
<query-editor-row query-ctrl="ctrl" can-collapse="false">
|
||||
<query-editor-row query-ctrl="ctrl" can-collapse="true" has-text-edit-mode="true">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form gf-form--grow">
|
||||
<label class="gf-form-label width-8">Query</label>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.expr" spellcheck='false' placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.refreshMetricData()">
|
||||
</div>
|
||||
<div class="gf-form max-width-22">
|
||||
<label class="gf-form-label">Metric lookup</label>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.metric" spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100>
|
||||
<textarea rows="3" class="gf-form-input" ng-model="ctrl.target.expr" spellcheck="false" placeholder="query expression" data-min-length=0 data-items=100 ng-model-onblur ng-change="ctrl.refreshMetricData()"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,9 +13,10 @@
|
||||
ng-model-onblur ng-change="ctrl.refreshMetricData()">
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-5">Step</label>
|
||||
<input type="text" class="gf-form-input max-width-5" ng-model="ctrl.target.interval"
|
||||
<label class="gf-form-label width-6">Min step</label>
|
||||
<input type="text" class="gf-form-input width-8" ng-model="ctrl.target.interval"
|
||||
data-placement="right"
|
||||
spellcheck='false'
|
||||
placeholder="{{ctrl.panelCtrl.interval}}"
|
||||
@@ -31,6 +27,7 @@
|
||||
Leave blank for auto handling based on time range and panel width
|
||||
</info-popover>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label">Resolution</label>
|
||||
<div class="gf-form-select-wrapper max-width-15">
|
||||
@@ -39,6 +36,24 @@
|
||||
ng-change="ctrl.refreshMetricData()">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-26">
|
||||
<label class="gf-form-label width-8">Metric lookup</label>
|
||||
<input type="text" class="gf-form-input" ng-model="ctrl.target.metric" spellcheck='false' bs-typeahead="ctrl.suggestMetrics" placeholder="metric name" data-min-length=0 data-items=100>
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-6">Format as</label>
|
||||
<div class="gf-form-select-wrapper width-8">
|
||||
<select class="gf-form-input gf-size-auto" ng-model="ctrl.target.format" ng-options="f.value as f.text for f in ctrl.formats" ng-change="ctrl.refresh()"></select>
|
||||
</div>
|
||||
<label class="gf-form-label">
|
||||
<a href="{{ctrl.linkToPrometheus}}" target="_blank" bs-tooltip="'Link to Graph in Prometheus'">
|
||||
<i class="fa fa-share-square-o"></i>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/prometheus_logo.svg",
|
||||
|
||||
@@ -12,6 +12,7 @@ class PrometheusQueryCtrl extends QueryCtrl {
|
||||
|
||||
metric: any;
|
||||
resolutions: any;
|
||||
formats: any;
|
||||
oldTarget: any;
|
||||
suggestMetrics: any;
|
||||
linkToPrometheus: any;
|
||||
@@ -23,15 +24,20 @@ class PrometheusQueryCtrl extends QueryCtrl {
|
||||
var target = this.target;
|
||||
target.expr = target.expr || '';
|
||||
target.intervalFactor = target.intervalFactor || 2;
|
||||
target.format = target.format || this.getDefaultFormat();
|
||||
|
||||
this.metric = '';
|
||||
this.resolutions = _.map([1,2,3,4,5,10], function(f) {
|
||||
return {factor: f, label: '1/' + f};
|
||||
});
|
||||
|
||||
this.formats = [
|
||||
{text: 'Time series', value: 'time_series'},
|
||||
{text: 'Table', value: 'table'},
|
||||
];
|
||||
|
||||
$scope.$on('typeahead-updated', () => {
|
||||
this.$scope.$apply(() => {
|
||||
|
||||
this.target.expr += this.target.metric;
|
||||
this.metric = '';
|
||||
this.refreshMetricData();
|
||||
@@ -48,6 +54,13 @@ class PrometheusQueryCtrl extends QueryCtrl {
|
||||
this.updateLink();
|
||||
}
|
||||
|
||||
getDefaultFormat() {
|
||||
if (this.panelCtrl.panel.type === 'table') {
|
||||
return 'table';
|
||||
}
|
||||
return 'time_series';
|
||||
}
|
||||
|
||||
refreshMetricData() {
|
||||
if (!_.isEqual(this.oldTarget, this.target)) {
|
||||
this.oldTarget = angular.copy(this.target);
|
||||
@@ -65,15 +78,19 @@ class PrometheusQueryCtrl extends QueryCtrl {
|
||||
var rangeDiff = Math.ceil((range.to.valueOf() - range.from.valueOf()) / 1000);
|
||||
var endTime = range.to.utc().format('YYYY-MM-DD HH:mm');
|
||||
var expr = {
|
||||
expr: this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars, this.datasource.interpolateQueryExpr),
|
||||
range_input: rangeDiff + 's',
|
||||
end_input: endTime,
|
||||
step_input: this.target.step,
|
||||
stacked: this.panelCtrl.panel.stack,
|
||||
tab: 0
|
||||
'g0.expr': this.templateSrv.replace(this.target.expr, this.panelCtrl.panel.scopedVars, this.datasource.interpolateQueryExpr),
|
||||
'g0.range_input': rangeDiff + 's',
|
||||
'g0.end_input': endTime,
|
||||
'g0.step_input': this.target.step,
|
||||
'g0.stacked': this.panelCtrl.panel.stack ? 1 : 0,
|
||||
'g0.tab': 0
|
||||
};
|
||||
var hash = encodeURIComponent(JSON.stringify([expr]));
|
||||
this.linkToPrometheus = this.datasource.directUrl + '/graph#' + hash;
|
||||
var args = _.map(expr, (v, k) => { return k + '=' + encodeURIComponent(v); }).join('&');
|
||||
this.linkToPrometheus = this.datasource.directUrl + '/graph?' + args;
|
||||
}
|
||||
|
||||
getCollapsedText() {
|
||||
return this.target.expr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ describe('PrometheusDatasource', function() {
|
||||
|
||||
beforeEach(angularMocks.module('grafana.core'));
|
||||
beforeEach(angularMocks.module('grafana.services'));
|
||||
beforeEach(ctx.providePhase(['timeSrv']));
|
||||
|
||||
beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
|
||||
ctx.$q = $q;
|
||||
ctx.$httpBackend = $httpBackend;
|
||||
@@ -158,4 +160,28 @@ describe('PrometheusDatasource', function() {
|
||||
expect(results[0].time).to.be(1443454528 * 1000);
|
||||
});
|
||||
});
|
||||
describe('When resultFormat is table', function() {
|
||||
var response = {
|
||||
status: "success",
|
||||
data: {
|
||||
resultType: "matrix",
|
||||
result: [{
|
||||
metric: {"__name__": "test", job: "testjob"},
|
||||
values: [[1443454528, "3846"]]
|
||||
}]
|
||||
}
|
||||
};
|
||||
it('should return table model', function() {
|
||||
var table = ctx.ds.transformMetricDataToTable(response.data.result);
|
||||
expect(table.type).to.be('table');
|
||||
expect(table.rows).to.eql([ [ 1443454528000, 'test', 'testjob', 3846 ] ]);
|
||||
expect(table.columns).to.eql(
|
||||
[ { text: 'Time', type: 'time' },
|
||||
{ text: '__name__' },
|
||||
{ text: 'job' },
|
||||
{ text: 'Value' }
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
<span class="gf-form-label width-8">Max items</span>
|
||||
<input type="text" class="gf-form-input max-width-15" ng-model="ctrl.panel.limit" ng-change="ctrl.onRender()" />
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.show === 'current'">
|
||||
<span class="gf-form-label width-8">Sort order</span>
|
||||
<div class="gf-form-select-wrapper max-width-15">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.sortOrder" ng-options="f.value as f.text for f in ctrl.sortOrderOptions" ng-change="ctrl.onRender()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<gf-form-switch class="gf-form" label="Alerts from this dashboard" label-class="width-18" checked="ctrl.panel.onlyAlertsOnDashboard" on-change="ctrl.updateStateFilter()"></gf-form-switch>
|
||||
</div>
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">State filter</h5>
|
||||
@@ -20,12 +27,4 @@
|
||||
<gf-form-switch class="gf-form" label="Execution error" label-class="width-10" checked="ctrl.stateFilter['execution_error']" on-change="ctrl.updateStateFilter()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label="Alerting" label-class="width-10" checked="ctrl.stateFilter['alerting']" on-change="ctrl.updateStateFilter()"></gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group" ng-if="ctrl.panel.show == 'changes'">
|
||||
<!-- <h5 class="section-heading">Current state</h5> -->
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group" ng-if="ctrl.panel.show == 'current'">
|
||||
<!-- <h5 class="section-heading">Current state</h5> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,33 +1,75 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<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">
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="32.3342" y1="95.7019" x2="32.3342" y2="5.2695">
|
||||
<stop offset="0" style="stop-color:#FFDE17"/>
|
||||
<stop offset="0.0803" style="stop-color:#FFD210"/>
|
||||
<stop offset="0.1774" style="stop-color:#FEC90D"/>
|
||||
<stop offset="0.2809" style="stop-color:#FDC70C"/>
|
||||
<stop offset="0.6685" style="stop-color:#F3903F"/>
|
||||
<stop offset="0.8876" style="stop-color:#ED683C"/>
|
||||
<stop offset="1" style="stop-color:#E93E3A"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_1_);" d="M48.173,57.757V39.825c0-1.302,1.055-2.357,2.357-2.357h9.691
|
||||
c0.897,0,1.346-1.084,0.712-1.718L34.112,0.737c-0.982-0.982-2.574-0.982-3.556,0L3.735,35.75
|
||||
c-0.634,0.634-0.185,1.718,0.712,1.718h9.691c1.302,0,2.357,1.055,2.357,2.357v17.932c0,0.958,0.776,1.734,1.734,1.734h28.21
|
||||
C47.397,59.491,48.173,58.715,48.173,57.757z"/>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="67.6658" y1="94.1706" x2="67.6658" y2="3.7383">
|
||||
<stop offset="0" style="stop-color:#FFDE17"/>
|
||||
<stop offset="0.0803" style="stop-color:#FFD210"/>
|
||||
<stop offset="0.1774" style="stop-color:#FEC90D"/>
|
||||
<stop offset="0.2809" style="stop-color:#FDC70C"/>
|
||||
<stop offset="0.6685" style="stop-color:#F3903F"/>
|
||||
<stop offset="0.8876" style="stop-color:#ED683C"/>
|
||||
<stop offset="1" style="stop-color:#E93E3A"/>
|
||||
</linearGradient>
|
||||
<path style="fill:url(#SVGID_2_);" d="M95.553,62.532h-9.691c-1.302,0-2.357-1.055-2.357-2.357V42.243
|
||||
c0-0.958-0.776-1.734-1.734-1.734h-28.21c-0.958,0-1.734,0.776-1.734,1.734v17.932c0,1.302-1.055,2.357-2.357,2.357h-9.691
|
||||
c-0.897,0-1.346,1.084-0.712,1.718l26.821,35.013c0.982,0.982,2.574,0.982,3.556,0L96.265,64.25
|
||||
C96.898,63.616,96.45,62.532,95.553,62.532z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 20.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<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" enable-background="new 0 0 100 100" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#1F1F1F" d="M18.2,4.2c-1.5,0-2.9,0.6-3.9,1.6l-0.5,0.5l-0.5-0.5c-1-1.1-2.4-1.6-3.9-1.6S6.6,4.8,5.5,5.8
|
||||
c-1,1-1.6,2.4-1.6,3.9s0.6,2.9,1.6,3.9l8.3,8.3l8.3-8.3c1-1,1.6-2.4,1.6-3.9s-0.6-2.9-1.6-3.9C21.1,4.8,19.7,4.2,18.2,4.2z
|
||||
M21,12.5l-7.2,7.2l-7.1-7.1c-0.7-0.7-1.1-1.7-1.1-2.8c0-1,0.4-2,1.1-2.8c0.7-0.7,1.7-1.1,2.8-1.1c1,0,2,0.4,2.8,1.1l0.5,0.5
|
||||
l0.6,0.6l0.6,0.6l0.6-0.6L15,7.5L15.5,7c0.7-0.7,1.7-1.1,2.8-1.1c1,0,2,0.4,2.8,1.1c0.7,0.7,1.1,1.7,1.1,2.8
|
||||
C22.1,10.8,21.7,11.8,21,12.5z"/>
|
||||
<path fill="#1F1F1F" d="M18.2,77.3c-1.5,0-2.9,0.6-3.9,1.6l-0.5,0.5L13.4,79c-1-1.1-2.4-1.6-3.9-1.6S6.6,77.9,5.5,79
|
||||
c-1,1-1.6,2.4-1.6,3.9s0.6,2.9,1.6,3.9l8.3,8.3l8.3-8.3c1-1,1.6-2.4,1.6-3.9S23.2,80,22.1,79C21.1,77.9,19.7,77.3,18.2,77.3z
|
||||
M21,85.6l-7.2,7.2l-7.1-7.1c-0.7-0.7-1.1-1.7-1.1-2.8c0-1,0.4-2,1.1-2.8C7.4,79.4,8.4,79,9.4,79c1,0,2,0.4,2.8,1.1l0.5,0.5
|
||||
l0.6,0.6l0.6,0.6l0.6-0.6l0.6-0.6l0.5-0.5c0.7-0.7,1.7-1.1,2.8-1.1c1,0,2,0.4,2.8,1.1c0.7,0.7,1.1,1.7,1.1,2.8
|
||||
C22.1,83.9,21.7,84.9,21,85.6z"/>
|
||||
<path fill="#1F1F1F" d="M0,0v100h100V0H0z M22.7,87.4l-8.9,8.9l-8.9-8.9C3.7,86.2,3,84.6,3,82.9s0.7-3.3,1.9-4.5
|
||||
c1.2-1.2,2.8-1.9,4.5-1.9c1.6,0,3.2,0.6,4.4,1.7c1.2-1.1,2.7-1.7,4.4-1.7c1.7,0,3.3,0.7,4.5,1.9c1.2,1.2,1.9,2.8,1.9,4.5
|
||||
S23.9,86.2,22.7,87.4z M22.7,63l-8.9,8.9L4.9,63C3.7,61.8,3,60.2,3,58.5c0-1.7,0.7-3.3,1.9-4.5c1.2-1.2,2.8-1.9,4.5-1.9
|
||||
c1.1,0,2.1,0.3,3,0.7l0,0.1l0,0l-1.3,6.6l0,0l0,0.1l2.3-0.1l-0.6,6.1l0-0.1l0,0l3.4-8.3l-2,0.3l1.2-3.7l0-0.1l0,0l0.3-1.1
|
||||
c0.8-0.3,1.6-0.5,2.4-0.5c1.7,0,3.3,0.7,4.5,1.9c1.2,1.2,1.9,2.8,1.9,4.5C24.6,60.2,23.9,61.8,22.7,63z M22.7,38.7l-8.9,8.9
|
||||
l-8.9-8.9C3.7,37.5,3,35.9,3,34.1s0.7-3.3,1.9-4.5c1.2-1.2,2.8-1.9,4.5-1.9c1.1,0,2.1,0.3,3,0.7l0,0.1l0,0l-1.3,6.6l0,0l0,0.1
|
||||
l2.3-0.1l-0.6,6.1l0-0.1l0,0l3.4-8.3l-2,0.3l1.2-3.7l0-0.1l0,0l0.3-1.1c0.8-0.3,1.6-0.5,2.4-0.5c1.7,0,3.3,0.7,4.5,1.9
|
||||
c1.2,1.2,1.9,2.8,1.9,4.5S23.9,37.5,22.7,38.7z M22.7,14.3l-8.9,8.9l-8.9-8.9C3.7,13.1,3,11.5,3,9.8s0.7-3.3,1.9-4.5
|
||||
c1.2-1.2,2.8-1.9,4.5-1.9c1.6,0,3.2,0.6,4.4,1.7C15,4,16.6,3.4,18.2,3.4c1.7,0,3.3,0.7,4.5,1.9c1.2,1.2,1.9,2.8,1.9,4.5
|
||||
S23.9,13.1,22.7,14.3z M96.6,94.4c0,1-0.8,1.8-1.8,1.8H33.4c-1,0-1.8-0.8-1.8-1.8V78.6c0-1,0.8-1.8,1.8-1.8h61.4
|
||||
c1,0,1.8,0.8,1.8,1.8V94.4z M96.6,69.9c0,1-0.8,1.8-1.8,1.8H33.4c-1,0-1.8-0.8-1.8-1.8V54.1c0-1,0.8-1.8,1.8-1.8h61.4
|
||||
c1,0,1.8,0.8,1.8,1.8V69.9z M96.6,45.4c0,1-0.8,1.8-1.8,1.8H33.4c-1,0-1.8-0.8-1.8-1.8V29.6c0-1,0.8-1.8,1.8-1.8h61.4
|
||||
c1,0,1.8,0.8,1.8,1.8V45.4z M96.6,20.9c0,1-0.8,1.8-1.8,1.8H33.4c-1,0-1.8-0.8-1.8-1.8V5.1c0-1,0.8-1.8,1.8-1.8h61.4
|
||||
c1,0,1.8,0.8,1.8,1.8V20.9z"/>
|
||||
<path fill="#1F1F1F" d="M18.2,53c-0.5,0-1,0.1-1.4,0.2l-0.7,2.1c0.6-0.4,1.3-0.6,2.1-0.6c1,0,2,0.4,2.8,1.1
|
||||
c0.7,0.7,1.1,1.7,1.1,2.8c0,1-0.4,2-1.1,2.8l-7.2,7.2l-7.1-7.1c-0.7-0.7-1.1-1.7-1.1-2.8c0-1,0.4-2,1.1-2.8
|
||||
c0.7-0.7,1.7-1.1,2.8-1.1c0.5,0,1,0.1,1.5,0.3l0.3-1.6c-0.6-0.2-1.2-0.3-1.8-0.3c-1.5,0-2.9,0.6-3.9,1.6c-1,1-1.6,2.4-1.6,3.9
|
||||
c0,1.5,0.6,2.9,1.6,3.9l8.3,8.3l8.3-8.3c1-1,1.6-2.4,1.6-3.9c0-1.5-0.6-2.9-1.6-3.9C21.1,53.5,19.7,53,18.2,53z"/>
|
||||
<path fill="#1F1F1F" d="M18.2,28.6c-0.5,0-1,0.1-1.4,0.2l-0.7,2.1c0.6-0.4,1.3-0.6,2.1-0.6c1,0,2,0.4,2.8,1.1
|
||||
c0.7,0.7,1.1,1.7,1.1,2.8c0,1-0.4,2-1.1,2.8l-7.2,7.2l-7.1-7.1c-0.7-0.7-1.1-1.7-1.1-2.8c0-1,0.4-2,1.1-2.8
|
||||
c0.7-0.7,1.7-1.1,2.8-1.1c0.5,0,1,0.1,1.5,0.3l0.3-1.6c-0.6-0.2-1.2-0.3-1.8-0.3c-1.5,0-2.9,0.6-3.9,1.6c-1,1-1.6,2.4-1.6,3.9
|
||||
s0.6,2.9,1.6,3.9l8.3,8.3l8.3-8.3c1-1,1.6-2.4,1.6-3.9s-0.6-2.9-1.6-3.9C21.1,29.2,19.7,28.6,18.2,28.6z"/>
|
||||
<path fill="#898989" d="M94.7,3.3H33.4c-1,0-1.8,0.8-1.8,1.8v15.8c0,1,0.8,1.8,1.8,1.8h61.4c1,0,1.8-0.8,1.8-1.8V5.1
|
||||
C96.6,4.1,95.7,3.3,94.7,3.3z"/>
|
||||
<path fill="#898989" d="M94.7,27.8H33.4c-1,0-1.8,0.8-1.8,1.8v15.8c0,1,0.8,1.8,1.8,1.8h61.4c1,0,1.8-0.8,1.8-1.8V29.6
|
||||
C96.6,28.6,95.7,27.8,94.7,27.8z"/>
|
||||
<path fill="#898989" d="M94.7,52.3H33.4c-1,0-1.8,0.8-1.8,1.8v15.8c0,1,0.8,1.8,1.8,1.8h61.4c1,0,1.8-0.8,1.8-1.8V54.1
|
||||
C96.6,53.1,95.7,52.3,94.7,52.3z"/>
|
||||
<path fill="#898989" d="M94.7,76.8H33.4c-1,0-1.8,0.8-1.8,1.8v15.8c0,1,0.8,1.8,1.8,1.8h61.4c1,0,1.8-0.8,1.8-1.8V78.6
|
||||
C96.6,77.6,95.7,76.8,94.7,76.8z"/>
|
||||
<path fill="#04A64D" d="M18.2,3.4c-1.6,0-3.2,0.6-4.4,1.7C12.6,4,11.1,3.4,9.4,3.4c-1.7,0-3.3,0.7-4.5,1.9C3.7,6.5,3,8.1,3,9.8
|
||||
s0.7,3.3,1.9,4.5l8.9,8.9l8.9-8.9c1.2-1.2,1.9-2.8,1.9-4.5s-0.7-3.3-1.9-4.5C21.5,4.1,19.9,3.4,18.2,3.4z M22.1,13.7L13.8,22
|
||||
l-8.3-8.3c-1-1-1.6-2.4-1.6-3.9s0.6-2.9,1.6-3.9c1-1,2.4-1.6,3.9-1.6s2.9,0.6,3.9,1.6l0.5,0.5l0.5-0.5c1-1,2.4-1.6,3.9-1.6
|
||||
c1.5,0,2.9,0.6,3.9,1.6c1,1,1.6,2.4,1.6,3.9S23.2,12.7,22.1,13.7z"/>
|
||||
<path fill="#04A64D" d="M18.2,5.9c-1,0-2,0.4-2.8,1.1L15,7.5l-0.6,0.6l-0.6,0.6l-0.6-0.6l-0.6-0.6L12.2,7c-0.7-0.7-1.7-1.1-2.8-1.1
|
||||
c-1,0-2,0.4-2.8,1.1C5.9,7.8,5.5,8.7,5.5,9.8c0,1,0.4,2,1.1,2.8l7.1,7.1l7.2-7.2c0.7-0.7,1.1-1.7,1.1-2.8c0-1-0.4-2-1.1-2.8
|
||||
C20.2,6.3,19.3,5.9,18.2,5.9z"/>
|
||||
<path fill="#04A64D" d="M18.2,76.5c-1.6,0-3.2,0.6-4.4,1.7c-1.2-1.1-2.7-1.7-4.4-1.7c-1.7,0-3.3,0.7-4.5,1.9
|
||||
C3.7,79.6,3,81.2,3,82.9s0.7,3.3,1.9,4.5l8.9,8.9l8.9-8.9c1.2-1.2,1.9-2.8,1.9-4.5s-0.7-3.3-1.9-4.5C21.5,77.2,19.9,76.5,18.2,76.5
|
||||
z M22.1,86.8l-8.3,8.3l-8.3-8.3c-1-1-1.6-2.4-1.6-3.9S4.5,80,5.5,79c1-1,2.4-1.6,3.9-1.6s2.9,0.6,3.9,1.6l0.5,0.5l0.5-0.5
|
||||
c1-1,2.4-1.6,3.9-1.6c1.5,0,2.9,0.6,3.9,1.6c1,1,1.6,2.4,1.6,3.9S23.2,85.8,22.1,86.8z"/>
|
||||
<path fill="#04A64D" d="M18.2,79c-1,0-2,0.4-2.8,1.1L15,80.6l-0.6,0.6l-0.6,0.6l-0.6-0.6l-0.6-0.6l-0.5-0.5
|
||||
c-0.7-0.7-1.7-1.1-2.8-1.1c-1,0-2,0.4-2.8,1.1c-0.7,0.7-1.1,1.7-1.1,2.8c0,1,0.4,2,1.1,2.8l7.1,7.1l7.2-7.2
|
||||
c0.7-0.7,1.1-1.7,1.1-2.8c0-1-0.4-2-1.1-2.8C20.2,79.4,19.3,79,18.2,79z"/>
|
||||
<path fill="#EB242A" d="M18.2,27.8c-0.8,0-1.7,0.2-2.4,0.5l-0.3,1.1l0,0l0,0.1l-1.2,3.7l2-0.3l-3.4,8.3l0,0l0,0.1l0.6-6.1l-2.3,0.1
|
||||
l0-0.1l0,0l1.3-6.6l0,0l0-0.1c-0.9-0.5-1.9-0.7-3-0.7c-1.7,0-3.3,0.7-4.5,1.9C3.7,30.8,3,32.4,3,34.1s0.7,3.3,1.9,4.5l8.9,8.9
|
||||
l8.9-8.9c1.2-1.2,1.9-2.8,1.9-4.5s-0.7-3.3-1.9-4.5C21.5,28.4,19.9,27.8,18.2,27.8z M22.1,38.1l-8.3,8.3l-8.3-8.3
|
||||
c-1-1-1.6-2.4-1.6-3.9s0.6-2.9,1.6-3.9c1-1,2.4-1.6,3.9-1.6c0.6,0,1.2,0.1,1.8,0.3l-0.3,1.6c-0.5-0.2-1-0.3-1.5-0.3
|
||||
c-1,0-2,0.4-2.8,1.1c-0.7,0.7-1.1,1.7-1.1,2.8c0,1,0.4,2,1.1,2.8l7.1,7.1l7.2-7.2c0.7-0.7,1.1-1.7,1.1-2.8c0-1-0.4-2-1.1-2.8
|
||||
c-0.7-0.7-1.7-1.1-2.8-1.1c-0.8,0-1.5,0.2-2.1,0.6l0.7-2.1c0.5-0.1,0.9-0.2,1.4-0.2c1.5,0,2.9,0.6,3.9,1.6c1,1,1.6,2.4,1.6,3.9
|
||||
S23.2,37,22.1,38.1z"/>
|
||||
<path fill="#EB242A" d="M18.2,52.1c-0.8,0-1.7,0.2-2.4,0.5l-0.3,1.1l0,0l0,0.1l-1.2,3.7l2-0.3l-3.4,8.3l0,0l0,0.1l0.6-6.1l-2.3,0.1
|
||||
l0-0.1l0,0l1.3-6.6l0,0l0-0.1c-0.9-0.5-1.9-0.7-3-0.7c-1.7,0-3.3,0.7-4.5,1.9C3.7,55.2,3,56.8,3,58.5c0,1.7,0.7,3.3,1.9,4.5
|
||||
l8.9,8.9l8.9-8.9c1.2-1.2,1.9-2.8,1.9-4.5c0-1.7-0.7-3.3-1.9-4.5C21.5,52.8,19.9,52.1,18.2,52.1z M22.1,62.4l-8.3,8.3l-8.3-8.3
|
||||
c-1-1-1.6-2.4-1.6-3.9c0-1.5,0.6-2.9,1.6-3.9c1-1,2.4-1.6,3.9-1.6c0.6,0,1.2,0.1,1.8,0.3l-0.3,1.6c-0.5-0.2-1-0.3-1.5-0.3
|
||||
c-1,0-2,0.4-2.8,1.1c-0.7,0.7-1.1,1.7-1.1,2.8c0,1,0.4,2,1.1,2.8l7.1,7.1l7.2-7.2c0.7-0.7,1.1-1.7,1.1-2.8c0-1-0.4-2-1.1-2.8
|
||||
c-0.7-0.7-1.7-1.1-2.8-1.1c-0.8,0-1.5,0.2-2.1,0.6l0.7-2.1c0.5-0.1,0.9-0.2,1.4-0.2c1.5,0,2.9,0.6,3.9,1.6c1,1,1.6,2.4,1.6,3.9
|
||||
C23.8,60,23.2,61.4,22.1,62.4z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 7.3 KiB |
@@ -1,4 +1,4 @@
|
||||
<div class="panel-alert-list">
|
||||
<div class="panel-alert-list" style="{{ctrl.contentHeight}}">
|
||||
<section class="card-section card-list-layout-list" ng-if="ctrl.panel.show === 'current'">
|
||||
<ol class="card-list">
|
||||
<li class="card-item-wrapper" ng-repeat="alert in ctrl.currentAlerts">
|
||||
@@ -40,7 +40,7 @@
|
||||
<span class="alert-list-item-state {{al.stateModel.stateClass}}">
|
||||
<i class="{{al.stateModel.iconClass}}"></i>
|
||||
{{al.stateModel.text}}
|
||||
</span> {{al.metrics}}
|
||||
</span> {{al.info}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,6 +17,13 @@ class AlertListPanel extends PanelCtrl {
|
||||
{text: 'Recent state changes', value: 'changes'}
|
||||
];
|
||||
|
||||
sortOrderOptions = [
|
||||
{text: 'Alphabetical (asc)', value: 1},
|
||||
{text: 'Alphabetical (desc)', value: 2},
|
||||
{text: 'Importance', value: 3},
|
||||
];
|
||||
|
||||
contentHeight: string;
|
||||
stateFilter: any = {};
|
||||
currentAlerts: any = [];
|
||||
alertHistory: any = [];
|
||||
@@ -24,7 +31,9 @@ class AlertListPanel extends PanelCtrl {
|
||||
panelDefaults = {
|
||||
show: 'current',
|
||||
limit: 10,
|
||||
stateFilter: []
|
||||
stateFilter: [],
|
||||
onlyAlertsOnDashboard: false,
|
||||
sortOrder: 1
|
||||
};
|
||||
|
||||
/** @ngInject */
|
||||
@@ -41,6 +50,19 @@ class AlertListPanel extends PanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
sortResult(alerts) {
|
||||
if (this.panel.sortOrder === 3) {
|
||||
return _.sortBy(alerts, a => { return alertDef.alertStateSortScore[a.state]; });
|
||||
}
|
||||
|
||||
var result = _.sortBy(alerts, a => { return a.name.toLowerCase();});
|
||||
if (this.panel.sortOrder === 2) {
|
||||
result.reverse();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
updateStateFilter() {
|
||||
var result = [];
|
||||
|
||||
@@ -55,6 +77,7 @@ class AlertListPanel extends PanelCtrl {
|
||||
}
|
||||
|
||||
onRender() {
|
||||
this.contentHeight = "max-height: " + this.height + "px;";
|
||||
if (this.panel.show === 'current') {
|
||||
this.getCurrentAlertState();
|
||||
}
|
||||
@@ -68,9 +91,13 @@ class AlertListPanel extends PanelCtrl {
|
||||
var params: any = {
|
||||
limit: this.panel.limit,
|
||||
type: 'alert',
|
||||
newState: this.panel.stateFilter
|
||||
newState: this.panel.stateFilter,
|
||||
};
|
||||
|
||||
if (this.panel.onlyAlertsOnDashboard) {
|
||||
params.dashboardId = this.dashboard.id;
|
||||
}
|
||||
|
||||
params.from = dateMath.parse(this.dashboard.time.from).unix() * 1000;
|
||||
params.to = dateMath.parse(this.dashboard.time.to).unix() * 1000;
|
||||
|
||||
@@ -79,7 +106,7 @@ class AlertListPanel extends PanelCtrl {
|
||||
this.alertHistory = _.map(res, al => {
|
||||
al.time = moment(al.time).format('MMM D, YYYY HH:mm:ss');
|
||||
al.stateModel = alertDef.getStateDisplayModel(al.newState);
|
||||
al.metrics = alertDef.joinEvalMatches(al.data, ', ');
|
||||
al.info = alertDef.getAlertAnnotationInfo(al);
|
||||
return al;
|
||||
});
|
||||
});
|
||||
@@ -90,13 +117,17 @@ class AlertListPanel extends PanelCtrl {
|
||||
state: this.panel.stateFilter
|
||||
};
|
||||
|
||||
if (this.panel.onlyAlertsOnDashboard) {
|
||||
params.dashboardId = this.dashboard.id;
|
||||
}
|
||||
|
||||
this.backendSrv.get(`/api/alerts`, params)
|
||||
.then(res => {
|
||||
this.currentAlerts = _.map(res, al => {
|
||||
this.currentAlerts = this.sortResult(_.map(res, al => {
|
||||
al.stateModel = alertDef.getStateDisplayModel(al.state);
|
||||
al.newStateDateAgo = moment(al.newStateDate).fromNow().replace(" ago", "");
|
||||
return al;
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -108,4 +139,4 @@ class AlertListPanel extends PanelCtrl {
|
||||
export {
|
||||
AlertListPanel,
|
||||
AlertListPanel as PanelCtrl
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-singlestat-panel.svg",
|
||||
|
||||
@@ -125,4 +125,4 @@ class DashListCtrl extends PanelCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
export {DashListCtrl, DashListCtrl as PanelCtrl}
|
||||
export {DashListCtrl, DashListCtrl as PanelCtrl};
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "http://grafana.org"
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-dashlist-panel.svg",
|
||||
|
||||
2
public/app/plugins/panel/gettingstarted/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Plugin List Panel - Native Plugin
|
||||
|
||||
40
public/app/plugins/panel/gettingstarted/editor.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<div class="gf-form-group">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Mode</span>
|
||||
<div class="gf-form-select-wrapper max-width-10">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.mode" ng-options="f for f in ctrl.modes" ng-change="ctrl.refresh()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.panel.mode === 'recently viewed'">
|
||||
<span class="gf-form-label">
|
||||
<i class="grafana-tip fa fa-question-circle ng-scope" bs-tooltip="'WARNING: This list will be cleared when clearing browser cache'" data-original-title="" title=""></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline" ng-if="ctrl.panel.mode === 'search'">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Search options</span>
|
||||
<span class="gf-form-label">Query</span>
|
||||
|
||||
<input type="text" class="gf-form-input" placeholder="title query"
|
||||
ng-model="ctrl.panel.query" ng-change="ctrl.refresh()" ng-model-onblur>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Tags</span>
|
||||
|
||||
<bootstrap-tagsinput ng-model="ctrl.panel.tags" tagclass="label label-tag" placeholder="add tags" on-tags-updated="ctrl.refresh()">
|
||||
</bootstrap-tagsinput>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Limit number to</span>
|
||||
<input class="gf-form-input" type="number" ng-model="ctrl.panel.limit" ng-model-onblur ng-change="ctrl.refresh()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<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">
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#666666;" d="M8.842,11.219h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,11.219z"/>
|
||||
<path style="fill:#666666;" d="M0.008,2.113l2.054-2.054C0.966,0.139,0.089,1.016,0.008,2.113z"/>
|
||||
<polygon style="fill:#666666;" points="0,2.998 0,5.533 5.484,0.05 2.948,0.05 "/>
|
||||
<polygon style="fill:#666666;" points="6.361,0.05 0,6.411 0,8.946 8.896,0.05 "/>
|
||||
<path style="fill:#666666;" d="M11.169,2.277c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
|
||||
l6.617-6.617V2.277z"/>
|
||||
<path style="fill:#666666;" d="M9.654,0.169L0.119,9.704c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
|
||||
C10.728,0.812,10.247,0.37,9.654,0.169z"/>
|
||||
<polygon style="fill:#666666;" points="11.169,5.479 5.429,11.219 7.964,11.219 11.169,8.014 "/>
|
||||
</g>
|
||||
<path style="fill:#898989;" d="M88.146,11.031H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
|
||||
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,10.212,89.157,11.031,88.146,11.031z"/>
|
||||
<g>
|
||||
<path style="fill:#666666;" d="M8.842,23.902h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,23.902z"/>
|
||||
<path style="fill:#666666;" d="M0.008,14.796l2.054-2.054C0.966,12.822,0.089,13.699,0.008,14.796z"/>
|
||||
<polygon style="fill:#666666;" points="0,15.681 0,18.216 5.484,12.733 2.948,12.733 "/>
|
||||
<polygon style="fill:#666666;" points="6.361,12.733 0,19.094 0,21.629 8.896,12.733 "/>
|
||||
<path style="fill:#666666;" d="M11.169,14.96c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
|
||||
l6.617-6.617V14.96z"/>
|
||||
<path style="fill:#666666;" d="M9.654,12.852l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
|
||||
C10.728,13.495,10.247,13.053,9.654,12.852z"/>
|
||||
<polygon style="fill:#666666;" points="11.169,18.162 5.429,23.902 7.964,23.902 11.169,20.697 "/>
|
||||
</g>
|
||||
<path style="fill:#898989;" d="M88.146,23.714H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
|
||||
c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,22.895,89.157,23.714,88.146,23.714z"/>
|
||||
<g>
|
||||
<path style="fill:#666666;" d="M8.842,36.585h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,36.585z"/>
|
||||
<path style="fill:#666666;" d="M0.008,27.479l2.054-2.054C0.966,25.505,0.089,26.382,0.008,27.479z"/>
|
||||
<polygon style="fill:#666666;" points="0,28.364 0,30.899 5.484,25.416 2.948,25.416 "/>
|
||||
<polygon style="fill:#666666;" points="6.361,25.416 0,31.777 0,34.312 8.896,25.416 "/>
|
||||
<path style="fill:#666666;" d="M11.169,27.643c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
|
||||
l6.617-6.617V27.643z"/>
|
||||
<path style="fill:#666666;" d="M9.654,25.535L0.119,35.07c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
|
||||
C10.728,26.178,10.247,25.736,9.654,25.535z"/>
|
||||
<polygon style="fill:#666666;" points="11.169,30.845 5.429,36.585 7.964,36.585 11.169,33.38 "/>
|
||||
</g>
|
||||
<path style="fill:#898989;" d="M88.146,36.397H14.866c-1.011,0-1.83-0.82-1.83-1.831v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
|
||||
c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,35.578,89.157,36.397,88.146,36.397z"/>
|
||||
<g>
|
||||
<path style="fill:#666666;" d="M8.842,49.268h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,49.268z"/>
|
||||
<path style="fill:#666666;" d="M0.008,40.162l2.054-2.054C0.966,38.188,0.089,39.065,0.008,40.162z"/>
|
||||
<polygon style="fill:#666666;" points="0,41.047 0,43.582 5.484,38.099 2.948,38.099 "/>
|
||||
<polygon style="fill:#666666;" points="6.361,38.099 0,44.46 0,46.995 8.896,38.099 "/>
|
||||
<path style="fill:#666666;" d="M11.169,40.326c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
|
||||
l6.617-6.617V40.326z"/>
|
||||
<path style="fill:#666666;" d="M9.654,38.218l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
|
||||
C10.728,38.861,10.247,38.419,9.654,38.218z"/>
|
||||
<polygon style="fill:#666666;" points="11.169,43.528 5.429,49.268 7.964,49.268 11.169,46.063 "/>
|
||||
</g>
|
||||
<path style="fill:#898989;" d="M88.146,49.08H14.866c-1.011,0-1.83-0.82-1.83-1.831v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
|
||||
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,48.261,89.157,49.08,88.146,49.08z"/>
|
||||
<g>
|
||||
<path style="fill:#666666;" d="M8.842,61.951h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,61.951z"/>
|
||||
<path style="fill:#666666;" d="M0.008,52.845l2.054-2.054C0.966,50.871,0.089,51.748,0.008,52.845z"/>
|
||||
<polygon style="fill:#666666;" points="0,53.73 0,56.265 5.484,50.782 2.948,50.782 "/>
|
||||
<polygon style="fill:#666666;" points="6.361,50.782 0,57.143 0,59.678 8.896,50.782 "/>
|
||||
<path style="fill:#666666;" d="M11.169,53.009c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
|
||||
l6.617-6.617V53.009z"/>
|
||||
<path style="fill:#666666;" d="M9.654,50.901l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
|
||||
C10.728,51.544,10.247,51.102,9.654,50.901z"/>
|
||||
<polygon style="fill:#666666;" points="11.169,56.211 5.429,61.951 7.964,61.951 11.169,58.746 "/>
|
||||
</g>
|
||||
<path style="fill:#898989;" d="M88.146,61.763H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
|
||||
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,60.944,89.157,61.763,88.146,61.763z"/>
|
||||
<g>
|
||||
<path style="fill:#666666;" d="M8.842,74.634h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,74.634z"/>
|
||||
<path style="fill:#666666;" d="M0.008,65.528l2.054-2.054C0.966,63.554,0.089,64.431,0.008,65.528z"/>
|
||||
<polygon style="fill:#666666;" points="0,66.413 0,68.948 5.484,63.465 2.948,63.465 "/>
|
||||
<polygon style="fill:#666666;" points="6.361,63.465 0,69.826 0,72.361 8.896,63.465 "/>
|
||||
<path style="fill:#666666;" d="M11.169,65.692c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
|
||||
l6.617-6.617V65.692z"/>
|
||||
<path style="fill:#666666;" d="M9.654,63.584l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
|
||||
C10.728,64.227,10.247,63.785,9.654,63.584z"/>
|
||||
<polygon style="fill:#666666;" points="11.169,68.894 5.429,74.634 7.964,74.634 11.169,71.429 "/>
|
||||
</g>
|
||||
<path style="fill:#898989;" d="M88.146,74.446H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
|
||||
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,73.627,89.157,74.446,88.146,74.446z"/>
|
||||
<g>
|
||||
<path style="fill:#666666;" d="M8.842,87.317h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,87.317z"/>
|
||||
<path style="fill:#666666;" d="M0.008,78.211l2.054-2.054C0.966,76.237,0.089,77.114,0.008,78.211z"/>
|
||||
<polygon style="fill:#666666;" points="0,79.096 0,81.631 5.484,76.148 2.948,76.148 "/>
|
||||
<polygon style="fill:#666666;" points="6.361,76.148 0,82.509 0,85.044 8.896,76.148 "/>
|
||||
<path style="fill:#666666;" d="M11.169,78.375c0-0.068-0.004-0.134-0.01-0.2l-9.132,9.132c0.066,0.006,0.133,0.01,0.2,0.01h2.325
|
||||
l6.617-6.617V78.375z"/>
|
||||
<path style="fill:#666666;" d="M9.654,76.267l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
|
||||
C10.728,76.91,10.247,76.468,9.654,76.267z"/>
|
||||
<polygon style="fill:#666666;" points="11.169,81.577 5.429,87.317 7.964,87.317 11.169,84.112 "/>
|
||||
</g>
|
||||
<path style="fill:#898989;" d="M88.146,87.129H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.831,1.83-1.831h73.281
|
||||
c1.011,0,1.83,0.82,1.83,1.831v7.37C89.977,86.31,89.157,87.129,88.146,87.129z"/>
|
||||
<g>
|
||||
<path style="fill:#666666;" d="M8.842,100h0.1c1.228,0,2.227-0.999,2.227-2.227v-0.1L8.842,100z"/>
|
||||
<path style="fill:#666666;" d="M0.008,90.894l2.054-2.054C0.966,88.92,0.089,89.797,0.008,90.894z"/>
|
||||
<polygon style="fill:#666666;" points="0,91.779 0,94.314 5.484,88.831 2.948,88.831 "/>
|
||||
<polygon style="fill:#666666;" points="6.361,88.831 0,95.192 0,97.727 8.896,88.831 "/>
|
||||
<path style="fill:#666666;" d="M11.169,91.058c0-0.068-0.004-0.134-0.01-0.2L2.027,99.99c0.066,0.006,0.133,0.01,0.2,0.01h2.325
|
||||
l6.617-6.617V91.058z"/>
|
||||
<path style="fill:#666666;" d="M9.654,88.95l-9.536,9.536c0.201,0.592,0.643,1.073,1.211,1.324l9.649-9.649
|
||||
C10.728,89.593,10.247,89.151,9.654,88.95z"/>
|
||||
<polygon style="fill:#666666;" points="11.169,94.26 5.429,100 7.964,100 11.169,96.795 "/>
|
||||
</g>
|
||||
<path style="fill:#898989;" d="M88.146,99.812H14.866c-1.011,0-1.83-0.82-1.83-1.83v-7.37c0-1.011,0.82-1.83,1.83-1.83h73.281
|
||||
c1.011,0,1.83,0.82,1.83,1.83v7.37C89.977,98.993,89.157,99.812,88.146,99.812z"/>
|
||||
<circle style="fill:#F7941E;" cx="96.125" cy="5.637" r="3.875"/>
|
||||
<circle style="fill:#898989;" cx="96.125" cy="18.37" r="3.875"/>
|
||||
<circle style="fill:#898989;" cx="96.125" cy="31.104" r="3.875"/>
|
||||
<circle style="fill:#F7941E;" cx="96.125" cy="43.837" r="3.875"/>
|
||||
<circle style="fill:#F7941E;" cx="96.125" cy="56.57" r="3.875"/>
|
||||
<circle style="fill:#898989;" cx="96.125" cy="69.304" r="3.875"/>
|
||||
<circle style="fill:#F7941E;" cx="96.125" cy="82.037" r="3.875"/>
|
||||
<circle style="fill:#898989;" cx="96.125" cy="94.77" r="3.875"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
19
public/app/plugins/panel/gettingstarted/module.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<div class="dashlist" ng-if="ctrl.checksDone">
|
||||
<div class="dashlist-section">
|
||||
<h6 class="dashlist-section-header">
|
||||
Getting Started with Grafana
|
||||
<button class="dashlist-cta-close-btn" ng-click="ctrl.dismiss()">
|
||||
<i class="fa fa-remove"></i>
|
||||
</button>
|
||||
</h6>
|
||||
<ul class="progress-tracker">
|
||||
<li class="progress-step" ng-repeat="step in ctrl.steps" ng-class="step.cssClass">
|
||||
<a class="progress-link" ng-href="{{step.href}}" target="{{step.target}}" title="{{step.note}}">
|
||||
<span class="progress-marker" ng-class="step.cssClass"><i class="{{step.icon}}"></i></span>
|
||||
<span class="progress-text" ng-href="{{step.href}}" target="{{step.target}}">{{step.title}}</span>
|
||||
</a>
|
||||
<a class="btn progress-step-cta" ng-href="{{step.href}}" target="{{step.target}}">{{step.cta}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
119
public/app/plugins/panel/gettingstarted/module.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
///<reference path="../../../headers/common.d.ts" />
|
||||
|
||||
import {PanelCtrl} from 'app/plugins/sdk';
|
||||
|
||||
import {contextSrv} from 'app/core/core';
|
||||
|
||||
class GettingStartedPanelCtrl extends PanelCtrl {
|
||||
static templateUrl = 'public/app/plugins/panel/gettingstarted/module.html';
|
||||
checksDone: boolean;
|
||||
stepIndex: number;
|
||||
steps: any;
|
||||
|
||||
/** @ngInject **/
|
||||
constructor($scope, $injector, private backendSrv, private datasourceSrv, private $q) {
|
||||
super($scope, $injector);
|
||||
|
||||
this.stepIndex = 0;
|
||||
this.steps = [];
|
||||
|
||||
this.steps.push({
|
||||
title: 'Install Grafana',
|
||||
icon: 'icon-gf icon-gf-check',
|
||||
href: 'http://docs.grafana.org/',
|
||||
target: '_blank',
|
||||
note: 'Review the installation docs',
|
||||
check: () => $q.when(true),
|
||||
});
|
||||
|
||||
this.steps.push({
|
||||
title: 'Create your first data source',
|
||||
cta: 'Add data source',
|
||||
icon: 'icon-gf icon-gf-datasources',
|
||||
href: 'datasources/new?gettingstarted',
|
||||
check: () => {
|
||||
return $q.when(
|
||||
datasourceSrv.getMetricSources().filter(item => {
|
||||
return item.meta.builtIn === false;
|
||||
}).length > 0
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
this.steps.push({
|
||||
title: 'Create your first dashboard',
|
||||
cta: 'New dashboard',
|
||||
icon: 'icon-gf icon-gf-dashboard',
|
||||
href: 'dashboard/new?gettingstarted',
|
||||
check: () => {
|
||||
return this.backendSrv.search({limit: 1}).then(result => {
|
||||
return result.length > 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.steps.push({
|
||||
title: 'Invite your team',
|
||||
cta: 'Add Users',
|
||||
icon: 'icon-gf icon-gf-users',
|
||||
href: 'org/users?gettingstarted',
|
||||
check: () => {
|
||||
return this.backendSrv.get('/api/org/users').then(res => {
|
||||
return res.length > 1;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.steps.push({
|
||||
title: 'Install apps & plugins',
|
||||
cta: 'Explore plugin repository',
|
||||
icon: 'icon-gf icon-gf-apps',
|
||||
href: 'https://grafana.com/plugins?utm_source=grafana_getting_started',
|
||||
check: () => {
|
||||
return this.backendSrv.get('/api/plugins', {embedded: 0, core: 0}).then(plugins => {
|
||||
return plugins.length > 0;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
this.stepIndex = -1;
|
||||
return this.nextStep().then(res => {
|
||||
this.checksDone = true;
|
||||
});
|
||||
}
|
||||
|
||||
nextStep() {
|
||||
if (this.stepIndex === this.steps.length - 1) {
|
||||
return this.$q.when();
|
||||
}
|
||||
|
||||
this.stepIndex += 1;
|
||||
var currentStep = this.steps[this.stepIndex];
|
||||
return currentStep.check().then(passed => {
|
||||
if (passed) {
|
||||
currentStep.cssClass = 'completed';
|
||||
return this.nextStep();
|
||||
}
|
||||
|
||||
currentStep.cssClass = 'active';
|
||||
return this.$q.when();
|
||||
});
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
this.row.removePanel(this.panel, false);
|
||||
|
||||
this.backendSrv.request({
|
||||
method: 'PUT',
|
||||
url: '/api/user/helpflags/1',
|
||||
showSuccessAlert: false,
|
||||
}).then(res => {
|
||||
contextSrv.user.helpFlags1 = res.helpFlags1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export {GettingStartedPanelCtrl, GettingStartedPanelCtrl as PanelCtrl};
|
||||
18
public/app/plugins/panel/gettingstarted/plugin.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "Getting Started",
|
||||
"id": "gettingstarted",
|
||||
|
||||
"hideFromList": true,
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-dashlist-panel.svg",
|
||||
"large": "img/icn-dashlist-panel.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,13 +20,13 @@
|
||||
</div>
|
||||
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-10">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-5">Y-Min</label>
|
||||
<input type="text" class="gf-form-input" placeholder="auto" empty-to-null ng-model="yaxis.min" ng-change="ctrl.render()" ng-model-onblur>
|
||||
<input type="text" class="gf-form-input width-5" placeholder="auto" empty-to-null ng-model="yaxis.min" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
<div class="gf-form max-width-10">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-5">Y-Max</label>
|
||||
<input type="text" class="gf-form-input" placeholder="auto" empty-to-null ng-model="yaxis.max" ng-change="ctrl.render()" ng-model-onblur>
|
||||
<input type="text" class="gf-form-input width-5" placeholder="auto" empty-to-null ng-model="yaxis.max" ng-change="ctrl.render()" ng-model-onblur>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
|
||||
<div class="section gf-form-group">
|
||||
<h5 class="section-heading">X-Axis</h5>
|
||||
<gf-form-switch class="gf-form" label="Show" label-class="width-5" checked="ctrl.panel.xaxis.show" on-change="ctrl.render()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label="Show" label-class="width-6" checked="ctrl.panel.xaxis.show" on-change="ctrl.render()"></gf-form-switch>
|
||||
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-5">Mode</label>
|
||||
<label class="gf-form-label width-6">Mode</label>
|
||||
<div class="gf-form-select-wrapper max-width-15">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.xaxis.mode" ng-options="v as k for (k, v) in ctrl.xAxisModes" ng-change="ctrl.xAxisOptionChanged()"> </select>
|
||||
</div>
|
||||
@@ -50,22 +50,28 @@
|
||||
|
||||
<!-- Table mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'field'">
|
||||
<label class="gf-form-label width-5">Name</label>
|
||||
<label class="gf-form-label width-6">Name</label>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.name" get-options="ctrl.getDataFieldNames(false)" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<!-- Series mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'field'">
|
||||
<label class="gf-form-label width-5">Value</label>
|
||||
<label class="gf-form-label width-6">Value</label>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.values[0]" get-options="ctrl.getDataFieldNames(true)" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<!-- Series mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'series'">
|
||||
<label class="gf-form-label width-5">Value</label>
|
||||
<label class="gf-form-label width-6">Value</label>
|
||||
<metric-segment-model property="ctrl.panel.xaxis.values[0]" options="ctrl.xAxisStatOptions" on-change="ctrl.xAxisOptionChanged()" custom="false" css-class="width-10" select-mode="true"></metric-segment-model>
|
||||
</div>
|
||||
|
||||
<!-- Histogram mode -->
|
||||
<div class="gf-form" ng-if="ctrl.panel.xaxis.mode === 'histogram'">
|
||||
<label class="gf-form-label width-6">Buckets</label>
|
||||
<input type="number" class="gf-form-input max-width-8" ng-model="ctrl.panel.xaxis.buckets" placeholder="auto" ng-change="ctrl.render()" ng-model-onblur bs-tooltip="'Number of buckets'" data-placement="right">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -30,15 +30,17 @@ export class AxesEditorCtrl {
|
||||
this.xAxisModes = {
|
||||
'Time': 'time',
|
||||
'Series': 'series',
|
||||
'Histogram': 'histogram'
|
||||
// 'Data field': 'field',
|
||||
};
|
||||
|
||||
this.xAxisStatOptions = [
|
||||
{text: 'Avg', value: 'avg'},
|
||||
{text: 'Min', value: 'min'},
|
||||
{text: 'Max', value: 'min'},
|
||||
{text: 'Max', value: 'max'},
|
||||
{text: 'Total', value: 'total'},
|
||||
{text: 'Count', value: 'count'},
|
||||
{text: 'Current', value: 'current'},
|
||||
];
|
||||
|
||||
if (this.panel.xaxis.mode === 'custom') {
|
||||
|
||||
@@ -29,6 +29,7 @@ export class DataProcessor {
|
||||
|
||||
switch (this.panel.xaxis.mode) {
|
||||
case 'series':
|
||||
case 'histogram':
|
||||
case 'time': {
|
||||
return options.dataList.map((item, index) => {
|
||||
return this.timeSeriesHandler(item, index, options);
|
||||
@@ -48,6 +49,9 @@ export class DataProcessor {
|
||||
if (this.panel.xaxis.mode === 'series') {
|
||||
return 'series';
|
||||
}
|
||||
if (this.panel.xaxis.mode === 'histogram') {
|
||||
return 'histogram';
|
||||
}
|
||||
return 'time';
|
||||
}
|
||||
}
|
||||
@@ -74,6 +78,15 @@ export class DataProcessor {
|
||||
this.panel.xaxis.values = ['total'];
|
||||
break;
|
||||
}
|
||||
case 'histogram': {
|
||||
this.panel.bars = true;
|
||||
this.panel.lines = false;
|
||||
this.panel.points = false;
|
||||
this.panel.stack = false;
|
||||
this.panel.legend.show = false;
|
||||
this.panel.tooltip.shared = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,14 +143,8 @@ export class DataProcessor {
|
||||
|
||||
let fields = [];
|
||||
var firstItem = dataList[0];
|
||||
if (firstItem.type === 'docs'){
|
||||
if (firstItem.datapoints.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let fieldParts = [];
|
||||
|
||||
function getPropertiesRecursive(obj) {
|
||||
let fieldParts = [];
|
||||
function getPropertiesRecursive(obj) {
|
||||
_.forEach(obj, (value, key) => {
|
||||
if (_.isObject(value)) {
|
||||
fieldParts.push(key);
|
||||
@@ -150,8 +157,11 @@ export class DataProcessor {
|
||||
}
|
||||
});
|
||||
fieldParts.pop();
|
||||
}
|
||||
if (firstItem.type === 'docs'){
|
||||
if (firstItem.datapoints.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
getPropertiesRecursive(firstItem.datapoints[0]);
|
||||
return fields;
|
||||
}
|
||||
@@ -166,7 +176,7 @@ export class DataProcessor {
|
||||
return [
|
||||
{text: 'Avg', value: 'avg'},
|
||||
{text: 'Min', value: 'min'},
|
||||
{text: 'Max', value: 'min'},
|
||||
{text: 'Max', value: 'max'},
|
||||
{text: 'Total', value: 'total'},
|
||||
{text: 'Count', value: 'count'},
|
||||
];
|
||||
|
||||
@@ -7,20 +7,21 @@ import 'jquery.flot.stack';
|
||||
import 'jquery.flot.stackpercent';
|
||||
import 'jquery.flot.fillbelow';
|
||||
import 'jquery.flot.crosshair';
|
||||
import 'jquery.flot.dashes';
|
||||
import './jquery.flot.events';
|
||||
|
||||
import angular from 'angular';
|
||||
import $ from 'jquery';
|
||||
import moment from 'moment';
|
||||
import _ from 'lodash';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import moment from 'moment';
|
||||
import kbn from 'app/core/utils/kbn';
|
||||
import {tickStep} from 'app/core/utils/ticks';
|
||||
import {appEvents, coreModule} from 'app/core/core';
|
||||
import GraphTooltip from './graph_tooltip';
|
||||
import {ThresholdManager} from './threshold_manager';
|
||||
import {EventManager} from 'app/features/annotations/all';
|
||||
import {convertValuesToHistogram, getSeriesValues} from './histogram';
|
||||
|
||||
var module = angular.module('grafana.directives');
|
||||
var labelWidthCache = {};
|
||||
|
||||
module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
coreModule.directive('grafanaGraph', function($rootScope, timeSrv, popoverSrv) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
template: '',
|
||||
@@ -28,44 +29,59 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
var ctrl = scope.ctrl;
|
||||
var dashboard = ctrl.dashboard;
|
||||
var panel = ctrl.panel;
|
||||
var data, annotations;
|
||||
var annotations = [];
|
||||
var data;
|
||||
var plot;
|
||||
var sortedSeries;
|
||||
var legendSideLastValue = null;
|
||||
var rootScope = scope.$root;
|
||||
var panelWidth = 0;
|
||||
var eventManager = new EventManager(ctrl, elem, popoverSrv);
|
||||
var thresholdManager = new ThresholdManager(ctrl);
|
||||
var tooltip = new GraphTooltip(elem, dashboard, scope, function() {
|
||||
return sortedSeries;
|
||||
});
|
||||
|
||||
rootScope.onAppEvent('setCrosshair', function(event, info) {
|
||||
// do not need to to this if event is from this panel
|
||||
if (info.scope === scope) {
|
||||
return;
|
||||
}
|
||||
// panel events
|
||||
ctrl.events.on('panel-teardown', () => {
|
||||
thresholdManager = null;
|
||||
|
||||
if (dashboard.sharedCrosshair) {
|
||||
var plot = elem.data().plot;
|
||||
if (plot) {
|
||||
plot.setCrosshair({ x: info.pos.x, y: info.pos.y });
|
||||
}
|
||||
}
|
||||
}, scope);
|
||||
|
||||
rootScope.onAppEvent('clearCrosshair', function() {
|
||||
var plot = elem.data().plot;
|
||||
if (plot) {
|
||||
plot.clearCrosshair();
|
||||
plot.destroy();
|
||||
plot = null;
|
||||
}
|
||||
}, scope);
|
||||
});
|
||||
|
||||
// Receive render events
|
||||
ctrl.events.on('render', function(renderData) {
|
||||
data = renderData || data;
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
annotations = ctrl.annotations;
|
||||
annotations = ctrl.annotations || [];
|
||||
render_panel();
|
||||
});
|
||||
|
||||
// global events
|
||||
appEvents.on('graph-hover', function(evt) {
|
||||
// ignore other graph hover events if shared tooltip is disabled
|
||||
if (!dashboard.sharedTooltipModeEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore if we are the emitter
|
||||
if (!plot || evt.panel.id === panel.id || ctrl.otherPanelInFullscreenMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
tooltip.show(evt.pos);
|
||||
}, scope);
|
||||
|
||||
appEvents.on('graph-hover-clear', function(event, info) {
|
||||
if (plot) {
|
||||
tooltip.clear(plot);
|
||||
}
|
||||
}, scope);
|
||||
|
||||
function getLegendHeight(panelHeight) {
|
||||
if (!panel.legend.show || panel.legend.rightSide) {
|
||||
return 0;
|
||||
@@ -106,16 +122,6 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
}
|
||||
}
|
||||
|
||||
function getLabelWidth(text, elem) {
|
||||
var labelWidth = labelWidthCache[text];
|
||||
|
||||
if (!labelWidth) {
|
||||
labelWidth = labelWidthCache[text] = elem.width();
|
||||
}
|
||||
|
||||
return labelWidth;
|
||||
}
|
||||
|
||||
function drawHook(plot) {
|
||||
// Update legend values
|
||||
var yaxis = plot.getYAxes();
|
||||
@@ -143,8 +149,6 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
var yaxisLabel = $("<div class='axisLabel left-yaxis-label flot-temp-elem'></div>")
|
||||
.text(panel.yaxes[0].label)
|
||||
.appendTo(elem);
|
||||
|
||||
yaxisLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[0].label, yaxisLabel) / 2) + 'px';
|
||||
}
|
||||
|
||||
// add right axis labels
|
||||
@@ -152,8 +156,6 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
var rightLabel = $("<div class='axisLabel right-yaxis-label flot-temp-elem'></div>")
|
||||
.text(panel.yaxes[1].label)
|
||||
.appendTo(elem);
|
||||
|
||||
rightLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[1].label, rightLabel) / 2) + 'px';
|
||||
}
|
||||
|
||||
thresholdManager.draw(plot);
|
||||
@@ -170,11 +172,39 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
for (var i = 0; i < yaxis.length; i++) {
|
||||
var axis = yaxis[i];
|
||||
var panelOptions = panel.yaxes[i];
|
||||
axis.options.max = panelOptions.max;
|
||||
axis.options.min = panelOptions.min;
|
||||
axis.options.max = axis.options.max !== null ? axis.options.max : panelOptions.max;
|
||||
axis.options.min = axis.options.min !== null ? axis.options.min : panelOptions.min;
|
||||
}
|
||||
}
|
||||
|
||||
// Series could have different timeSteps,
|
||||
// let's find the smallest one so that bars are correctly rendered.
|
||||
// In addition, only take series which are rendered as bars for this.
|
||||
function getMinTimeStepOfSeries(data) {
|
||||
var min = Number.MAX_VALUE;
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (!data[i].stats.timeStep) {
|
||||
continue;
|
||||
}
|
||||
if (panel.bars) {
|
||||
if (data[i].bars && data[i].bars.show === false) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (typeof data[i].bars === 'undefined' || typeof data[i].bars.show === 'undefined' || !data[i].bars.show) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (data[i].stats.timeStep < min) {
|
||||
min = data[i].stats.timeStep;
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
// Function for rendering panel
|
||||
function render_panel() {
|
||||
panelWidth = elem.width();
|
||||
@@ -186,6 +216,9 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
// give space to alert editing
|
||||
thresholdManager.prepare(elem, data);
|
||||
|
||||
// un-check dashes if lines are unchecked
|
||||
panel.dashes = panel.lines ? panel.dashes : false;
|
||||
|
||||
var stack = panel.stack ? true : null;
|
||||
|
||||
// Populate element
|
||||
@@ -202,9 +235,14 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
show: panel.lines,
|
||||
zero: false,
|
||||
fill: translateFillOption(panel.fill),
|
||||
lineWidth: panel.linewidth,
|
||||
lineWidth: panel.dashes ? 0 : panel.linewidth,
|
||||
steps: panel.steppedLine
|
||||
},
|
||||
dashes: {
|
||||
show: panel.dashes,
|
||||
lineWidth: panel.linewidth,
|
||||
dashLength: [panel.dashLength, panel.spaceLength]
|
||||
},
|
||||
bars: {
|
||||
show: panel.bars,
|
||||
fill: 1,
|
||||
@@ -228,6 +266,7 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
backgroundColor: null,
|
||||
borderWidth: 0,
|
||||
hoverable: true,
|
||||
clickable: true,
|
||||
color: '#c8c8c8',
|
||||
margin: { left: 0, right: 0 },
|
||||
},
|
||||
@@ -236,12 +275,12 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
color: '#666'
|
||||
},
|
||||
crosshair: {
|
||||
mode: panel.tooltip.shared || dashboard.sharedCrosshair ? "x" : null
|
||||
mode: 'x'
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
var series = data[i];
|
||||
let series = data[i];
|
||||
series.data = series.getFlotPairs(series.nullPointMode || panel.nullPointMode);
|
||||
|
||||
// if hidden remove points and disable stack
|
||||
@@ -257,13 +296,36 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
options.series.bars.align = 'center';
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
var series = data[i];
|
||||
let series = data[i];
|
||||
series.data = [[i + 1, series.stats[panel.xaxis.values[0]]]];
|
||||
}
|
||||
|
||||
addXSeriesAxis(options);
|
||||
break;
|
||||
}
|
||||
case 'histogram': {
|
||||
let bucketSize: number;
|
||||
let values = getSeriesValues(data);
|
||||
|
||||
if (data.length && values.length) {
|
||||
let histMin = _.min(_.map(data, s => s.stats.min));
|
||||
let histMax = _.max(_.map(data, s => s.stats.max));
|
||||
let ticks = panel.xaxis.buckets || panelWidth / 50;
|
||||
bucketSize = tickStep(histMin, histMax, ticks);
|
||||
let histogram = convertValuesToHistogram(values, bucketSize);
|
||||
|
||||
data[0].data = histogram;
|
||||
data[0].alias = data[0].label = data[0].id = "count";
|
||||
data = [data[0]];
|
||||
|
||||
options.series.bars.barWidth = bucketSize * 0.8;
|
||||
} else {
|
||||
bucketSize = 0;
|
||||
}
|
||||
|
||||
addXHistogramAxis(options, bucketSize);
|
||||
break;
|
||||
}
|
||||
case 'table': {
|
||||
options.series.bars.barWidth = 0.7;
|
||||
options.series.bars.align = 'center';
|
||||
@@ -271,23 +333,21 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (data.length && data[0].stats.timeStep) {
|
||||
options.series.bars.barWidth = data[0].stats.timeStep / 1.5;
|
||||
}
|
||||
options.series.bars.barWidth = getMinTimeStepOfSeries(data) / 1.5;
|
||||
addTimeAxis(options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
thresholdManager.addPlotOptions(options, panel);
|
||||
addAnnotations(options);
|
||||
thresholdManager.addFlotOptions(options, panel);
|
||||
eventManager.addFlotEvents(annotations, options);
|
||||
configureAxisOptions(data, options);
|
||||
|
||||
sortedSeries = _.sortBy(data, function(series) { return series.zindex; });
|
||||
|
||||
function callPlot(incrementRenderCounter) {
|
||||
try {
|
||||
$.plot(elem, sortedSeries, options);
|
||||
plot = $.plot(elem, sortedSeries, options);
|
||||
if (ctrl.renderError) {
|
||||
delete ctrl.error;
|
||||
delete ctrl.inspector;
|
||||
@@ -360,6 +420,38 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
};
|
||||
}
|
||||
|
||||
function addXHistogramAxis(options, bucketSize) {
|
||||
let ticks, min, max;
|
||||
|
||||
if (data.length && bucketSize) {
|
||||
ticks = _.map(data[0].data, point => point[0]);
|
||||
|
||||
// Expand ticks for pretty view
|
||||
min = Math.max(0, _.min(ticks) - bucketSize);
|
||||
max = _.max(ticks) + bucketSize;
|
||||
|
||||
ticks = [];
|
||||
for (let i = min; i <= max; i += bucketSize) {
|
||||
ticks.push(i);
|
||||
}
|
||||
} else {
|
||||
// Set defaults if no data
|
||||
ticks = panelWidth / 100;
|
||||
min = 0;
|
||||
max = 1;
|
||||
}
|
||||
|
||||
options.xaxis = {
|
||||
timezone: dashboard.getTimezone(),
|
||||
show: panel.xaxis.show,
|
||||
mode: null,
|
||||
min: min,
|
||||
max: max,
|
||||
label: "Histogram",
|
||||
ticks: ticks
|
||||
};
|
||||
}
|
||||
|
||||
function addXTableAxis(options) {
|
||||
var ticks = _.map(data, function(series, seriesIndex) {
|
||||
return _.map(series.datapoints, function(point, pointIndex) {
|
||||
@@ -380,38 +472,14 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
};
|
||||
}
|
||||
|
||||
function addAnnotations(options) {
|
||||
if (!annotations || annotations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var types = {};
|
||||
for (var i = 0; i < annotations.length; i++) {
|
||||
var item = annotations[i];
|
||||
|
||||
if (!types[item.source.name]) {
|
||||
types[item.source.name] = {
|
||||
color: item.source.iconColor,
|
||||
position: 'BOTTOM',
|
||||
markerSize: 5,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
options.events = {
|
||||
levels: _.keys(types).length + 1,
|
||||
data: annotations,
|
||||
types: types,
|
||||
};
|
||||
}
|
||||
|
||||
function configureAxisOptions(data, options) {
|
||||
var defaults = {
|
||||
position: 'left',
|
||||
show: panel.yaxes[0].show,
|
||||
index: 1,
|
||||
logBase: panel.yaxes[0].logBase || 1,
|
||||
max: 100, // correct later
|
||||
min: panel.yaxes[0].min ? _.toNumber(panel.yaxes[0].min) : null,
|
||||
max: panel.yaxes[0].max ? _.toNumber(panel.yaxes[0].max) : null,
|
||||
};
|
||||
|
||||
options.yaxes.push(defaults);
|
||||
@@ -422,10 +490,13 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
secondY.show = panel.yaxes[1].show;
|
||||
secondY.logBase = panel.yaxes[1].logBase || 1;
|
||||
secondY.position = 'right';
|
||||
secondY.min = panel.yaxes[1].min ? _.toNumber(panel.yaxes[1].min) : null;
|
||||
secondY.max = panel.yaxes[1].max ? _.toNumber(panel.yaxes[1].max) : null;
|
||||
options.yaxes.push(secondY);
|
||||
|
||||
applyLogScale(options.yaxes[1], data);
|
||||
configureAxisMode(options.yaxes[1], panel.percentage && panel.stack ? "percent" : panel.yaxes[1].format);
|
||||
}
|
||||
|
||||
applyLogScale(options.yaxes[0], data);
|
||||
configureAxisMode(options.yaxes[0], panel.percentage && panel.stack ? "percent" : panel.yaxes[0].format);
|
||||
}
|
||||
@@ -435,42 +506,104 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
return;
|
||||
}
|
||||
|
||||
var series, i;
|
||||
var max = axis.max;
|
||||
const minSetToZero = axis.min === 0;
|
||||
|
||||
if (max === null) {
|
||||
for (i = 0; i < data.length; i++) {
|
||||
series = data[i];
|
||||
if (series.yaxis === axis.index) {
|
||||
if (max < series.stats.max) {
|
||||
max = series.stats.max;
|
||||
}
|
||||
if (axis.min < Number.MIN_VALUE) {
|
||||
axis.min = null;
|
||||
}
|
||||
if (axis.max < Number.MIN_VALUE) {
|
||||
axis.max = null;
|
||||
}
|
||||
|
||||
var series, i;
|
||||
var max = axis.max, min = axis.min;
|
||||
|
||||
for (i = 0; i < data.length; i++) {
|
||||
series = data[i];
|
||||
if (series.yaxis === axis.index) {
|
||||
if (!max || max < series.stats.max) {
|
||||
max = series.stats.max;
|
||||
}
|
||||
if (!min || min > series.stats.logmin) {
|
||||
min = series.stats.logmin;
|
||||
}
|
||||
}
|
||||
if (max === void 0) {
|
||||
max = Number.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
axis.min = axis.min !== null ? axis.min : 0;
|
||||
axis.ticks = [0, 1];
|
||||
var nextTick = 1;
|
||||
axis.transform = function(v) { return (v < Number.MIN_VALUE) ? null : Math.log(v) / Math.log(axis.logBase); };
|
||||
axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); };
|
||||
|
||||
while (true) {
|
||||
nextTick = nextTick * axis.logBase;
|
||||
axis.ticks.push(nextTick);
|
||||
if (nextTick > max) {
|
||||
break;
|
||||
}
|
||||
if (!max && !min) {
|
||||
max = axis.inverseTransform(+2);
|
||||
min = axis.inverseTransform(-2);
|
||||
} else if (!max) {
|
||||
max = min*axis.inverseTransform(+4);
|
||||
} else if (!min) {
|
||||
min = max*axis.inverseTransform(-4);
|
||||
}
|
||||
|
||||
if (axis.logBase === 10) {
|
||||
axis.transform = function(v) { return Math.log(v+0.1); };
|
||||
axis.inverseTransform = function (v) { return Math.pow(10,v); };
|
||||
if (axis.min) {
|
||||
min = axis.inverseTransform(Math.ceil(axis.transform(axis.min)));
|
||||
} else {
|
||||
axis.transform = function(v) { return Math.log(v+0.1) / Math.log(axis.logBase); };
|
||||
axis.inverseTransform = function (v) { return Math.pow(axis.logBase,v); };
|
||||
min = axis.min = axis.inverseTransform(Math.floor(axis.transform(min)));
|
||||
}
|
||||
if (axis.max) {
|
||||
max = axis.inverseTransform(Math.floor(axis.transform(axis.max)));
|
||||
} else {
|
||||
max = axis.max = axis.inverseTransform(Math.ceil(axis.transform(max)));
|
||||
}
|
||||
|
||||
if (!min || min < Number.MIN_VALUE || !max || max < Number.MIN_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Number.isFinite(min) && Number.isFinite(max)) {
|
||||
if (minSetToZero) {
|
||||
axis.min = 0.1;
|
||||
min = 1;
|
||||
}
|
||||
|
||||
axis.ticks = generateTicksForLogScaleYAxis(min, max, axis.logBase);
|
||||
if (minSetToZero) {
|
||||
axis.ticks.unshift(0.1);
|
||||
}
|
||||
if (axis.ticks[axis.ticks.length - 1] > axis.max) {
|
||||
axis.max = axis.ticks[axis.ticks.length - 1];
|
||||
}
|
||||
axis.tickDecimals = decimalPlaces(min);
|
||||
} else {
|
||||
axis.ticks = [1, 2];
|
||||
delete axis.min;
|
||||
delete axis.max;
|
||||
}
|
||||
}
|
||||
|
||||
function generateTicksForLogScaleYAxis(min, max, logBase) {
|
||||
let ticks = [];
|
||||
|
||||
var nextTick;
|
||||
for (nextTick = min; nextTick <= max; nextTick *= logBase) {
|
||||
ticks.push(nextTick);
|
||||
}
|
||||
|
||||
const maxNumTicks = Math.ceil(ctrl.height/25);
|
||||
const numTicks = ticks.length;
|
||||
if (numTicks > maxNumTicks) {
|
||||
const factor = Math.ceil(numTicks/maxNumTicks) * logBase;
|
||||
ticks = [];
|
||||
|
||||
for (nextTick = min; nextTick <= (max * factor); nextTick *= factor) {
|
||||
ticks.push(nextTick);
|
||||
}
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
function decimalPlaces(num) {
|
||||
if (!num) { return 0; }
|
||||
|
||||
return (num.toString().split('.')[1] || []).length;
|
||||
}
|
||||
|
||||
function configureAxisMode(axis, format) {
|
||||
@@ -504,17 +637,37 @@ module.directive('grafanaGraph', function($rootScope, timeSrv) {
|
||||
return "%H:%M";
|
||||
}
|
||||
|
||||
new GraphTooltip(elem, dashboard, scope, function() {
|
||||
return sortedSeries;
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
if (ranges.ctrlKey || ranges.metaKey) {
|
||||
// scope.$apply(() => {
|
||||
// eventManager.updateTime(ranges.xaxis);
|
||||
// });
|
||||
} else {
|
||||
scope.$apply(function() {
|
||||
timeSrv.setTime({
|
||||
from : moment.utc(ranges.xaxis.from),
|
||||
to : moment.utc(ranges.xaxis.to),
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
elem.bind("plotselected", function (event, ranges) {
|
||||
scope.$apply(function() {
|
||||
timeSrv.setTime({
|
||||
from : moment.utc(ranges.xaxis.from),
|
||||
to : moment.utc(ranges.xaxis.to),
|
||||
});
|
||||
});
|
||||
elem.bind("plotclick", function (event, pos, item) {
|
||||
if (pos.ctrlKey || pos.metaKey || eventManager.event) {
|
||||
// Skip if range selected (added in "plotselected" event handler)
|
||||
let isRangeSelection = pos.x !== pos.x1;
|
||||
if (!isRangeSelection) {
|
||||
// scope.$apply(() => {
|
||||
// eventManager.updateTime({from: pos.x, to: null});
|
||||
// });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
tooltip.destroy();
|
||||
elem.off();
|
||||
elem.remove();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,23 +1,32 @@
|
||||
define([
|
||||
'jquery',
|
||||
'lodash'
|
||||
'app/core/core',
|
||||
],
|
||||
function ($, _) {
|
||||
function ($, core) {
|
||||
'use strict';
|
||||
|
||||
var appEvents = core.appEvents;
|
||||
|
||||
function GraphTooltip(elem, dashboard, scope, getSeriesFn) {
|
||||
var self = this;
|
||||
var ctrl = scope.ctrl;
|
||||
var panel = ctrl.panel;
|
||||
|
||||
var $tooltip = $('<div id="tooltip" class="graph-tooltip">');
|
||||
var $tooltip = $('<div class="graph-tooltip">');
|
||||
|
||||
this.destroy = function() {
|
||||
$tooltip.remove();
|
||||
};
|
||||
|
||||
this.findHoverIndexFromDataPoints = function(posX, series, last) {
|
||||
var ps = series.datapoints.pointsize;
|
||||
var initial = last*ps;
|
||||
var len = series.datapoints.points.length;
|
||||
for (var j = initial; j < len; j += ps) {
|
||||
if (series.datapoints.points[j] > posX) {
|
||||
// Special case of a non stepped line, highlight the very last point just before a null point
|
||||
if ((!series.lines.steps && series.datapoints.points[initial] != null && series.datapoints.points[j] == null)
|
||||
//normal case
|
||||
|| series.datapoints.points[j] > posX) {
|
||||
return Math.max(j - ps, 0)/ps;
|
||||
}
|
||||
}
|
||||
@@ -25,45 +34,68 @@ function ($, _) {
|
||||
};
|
||||
|
||||
this.findHoverIndexFromData = function(posX, series) {
|
||||
var len = series.data.length;
|
||||
for (var j = 0; j < len; j++) {
|
||||
if (series.data[j][0] > posX) {
|
||||
return Math.max(j - 1, 0);
|
||||
var lower = 0;
|
||||
var upper = series.data.length - 1;
|
||||
var middle;
|
||||
while (true) {
|
||||
if (lower > upper) {
|
||||
return Math.max(upper, 0);
|
||||
}
|
||||
middle = Math.floor((lower + upper) / 2);
|
||||
if (series.data[middle][0] === posX) {
|
||||
return middle;
|
||||
} else if (series.data[middle][0] < posX) {
|
||||
lower = middle + 1;
|
||||
} else {
|
||||
upper = middle - 1;
|
||||
}
|
||||
}
|
||||
return j - 1;
|
||||
};
|
||||
|
||||
this.showTooltip = function(absoluteTime, innerHtml, pos) {
|
||||
var body = '<div class="graph-tooltip-time">'+ absoluteTime + '</div>';
|
||||
body += innerHtml + '</div>';
|
||||
$tooltip.html(body).place_tt(pos.pageX + 20, pos.pageY);
|
||||
this.renderAndShow = function(absoluteTime, innerHtml, pos, xMode) {
|
||||
if (xMode === 'time') {
|
||||
innerHtml = '<div class="graph-tooltip-time">'+ absoluteTime + '</div>' + innerHtml;
|
||||
}
|
||||
$tooltip.html(innerHtml).place_tt(pos.pageX + 20, pos.pageY);
|
||||
};
|
||||
|
||||
this.getMultiSeriesPlotHoverInfo = function(seriesList, pos) {
|
||||
var value, i, series, hoverIndex, hoverDistance, pointTime;
|
||||
var results = [];
|
||||
var value, i, series, hoverIndex, hoverDistance, pointTime, yaxis;
|
||||
// 3 sub-arrays, 1st for hidden series, 2nd for left yaxis, 3rd for right yaxis.
|
||||
var results = [[],[],[]];
|
||||
|
||||
//now we know the current X (j) position for X and Y values
|
||||
var last_value = 0; //needed for stacked values
|
||||
|
||||
var minDistance, minTime;
|
||||
|
||||
for (i = 0; i < seriesList.length; i++) {
|
||||
series = seriesList[i];
|
||||
|
||||
if (!series.data.length || (panel.legend.hideEmpty && series.allIsNull)) {
|
||||
results.push({ hidden: true });
|
||||
// Init value so that it does not brake series sorting
|
||||
results[0].push({ hidden: true, value: 0 });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!series.data.length || (panel.legend.hideZero && series.allIsZero)) {
|
||||
results.push({ hidden: true });
|
||||
// Init value so that it does not brake series sorting
|
||||
results[0].push({ hidden: true, value: 0 });
|
||||
continue;
|
||||
}
|
||||
|
||||
hoverIndex = this.findHoverIndexFromData(pos.x, series);
|
||||
hoverDistance = Math.abs(pos.x - series.data[hoverIndex][0]);
|
||||
hoverDistance = pos.x - series.data[hoverIndex][0];
|
||||
pointTime = series.data[hoverIndex][0];
|
||||
|
||||
// Take the closest point before the cursor, or if it does not exist, the closest after
|
||||
if (! minDistance
|
||||
|| (hoverDistance >=0 && (hoverDistance < minDistance || minDistance < 0))
|
||||
|| (hoverDistance < 0 && hoverDistance > minDistance)) {
|
||||
minDistance = hoverDistance;
|
||||
minTime = pointTime;
|
||||
}
|
||||
|
||||
if (series.stack) {
|
||||
if (panel.tooltip.value_type === 'individual') {
|
||||
value = series.data[hoverIndex][1];
|
||||
@@ -85,18 +117,28 @@ function ($, _) {
|
||||
hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex);
|
||||
}
|
||||
|
||||
results.push({
|
||||
// Be sure we have a yaxis so that it does not brake series sorting
|
||||
yaxis = 0;
|
||||
if (series.yaxis) {
|
||||
yaxis = series.yaxis.n;
|
||||
}
|
||||
|
||||
results[yaxis].push({
|
||||
value: value,
|
||||
hoverIndex: hoverIndex,
|
||||
color: series.color,
|
||||
label: series.label,
|
||||
time: pointTime,
|
||||
distance: hoverDistance
|
||||
distance: hoverDistance,
|
||||
index: i
|
||||
});
|
||||
}
|
||||
|
||||
// Find point which closer to pointer
|
||||
results.time = _.min(results, 'distance').time;
|
||||
// Contat the 3 sub-arrays
|
||||
results = results[0].concat(results[1],results[2]);
|
||||
|
||||
// Time of the point closer to pointer
|
||||
results.time = minTime;
|
||||
|
||||
return results;
|
||||
};
|
||||
@@ -109,20 +151,58 @@ function ($, _) {
|
||||
plot.unhighlight();
|
||||
}
|
||||
}
|
||||
|
||||
if (dashboard.sharedCrosshair) {
|
||||
ctrl.publishAppEvent('clearCrosshair');
|
||||
}
|
||||
appEvents.emit('graph-hover-clear');
|
||||
});
|
||||
|
||||
elem.bind("plothover", function (event, pos, item) {
|
||||
self.show(pos, item);
|
||||
|
||||
// broadcast to other graph panels that we are hovering!
|
||||
pos.panelRelY = (pos.pageY - elem.offset().top) / elem.height();
|
||||
appEvents.emit('graph-hover', {pos: pos, panel: panel});
|
||||
});
|
||||
|
||||
elem.bind("plotclick", function (event, pos, item) {
|
||||
appEvents.emit('graph-click', {pos: pos, panel: panel, item: item});
|
||||
});
|
||||
|
||||
this.clear = function(plot) {
|
||||
$tooltip.detach();
|
||||
plot.clearCrosshair();
|
||||
plot.unhighlight();
|
||||
};
|
||||
|
||||
this.show = function(pos, item) {
|
||||
var plot = elem.data().plot;
|
||||
var plotData = plot.getData();
|
||||
var xAxes = plot.getXAxes();
|
||||
var xMode = xAxes[0].options.mode;
|
||||
var seriesList = getSeriesFn();
|
||||
var allSeriesMode = panel.tooltip.shared;
|
||||
var group, value, absoluteTime, hoverInfo, i, series, seriesHtml, tooltipFormat;
|
||||
|
||||
if (dashboard.sharedCrosshair) {
|
||||
ctrl.publishAppEvent('setCrosshair', {pos: pos, scope: scope});
|
||||
// if panelRelY is defined another panel wants us to show a tooltip
|
||||
// get pageX from position on x axis and pageY from relative position in original panel
|
||||
if (pos.panelRelY) {
|
||||
var pointOffset = plot.pointOffset({x: pos.x});
|
||||
if (Number.isNaN(pointOffset.left) || pointOffset.left < 0 || pointOffset.left > elem.width()) {
|
||||
self.clear(plot);
|
||||
return;
|
||||
}
|
||||
pos.pageX = elem.offset().left + pointOffset.left;
|
||||
pos.pageY = elem.offset().top + elem.height() * pos.panelRelY;
|
||||
var isVisible = pos.pageY >= $(window).scrollTop() && pos.pageY <= $(window).innerHeight() + $(window).scrollTop();
|
||||
if (!isVisible) {
|
||||
self.clear(plot);
|
||||
return;
|
||||
}
|
||||
plot.setCrosshair(pos);
|
||||
allSeriesMode = true;
|
||||
|
||||
if (dashboard.sharedCrosshairModeOnly()) {
|
||||
// if only crosshair mode we are done
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (seriesList.length === 0) {
|
||||
@@ -135,7 +215,7 @@ function ($, _) {
|
||||
tooltipFormat = 'YYYY-MM-DD HH:mm:ss';
|
||||
}
|
||||
|
||||
if (panel.tooltip.shared) {
|
||||
if (allSeriesMode) {
|
||||
plot.unhighlight();
|
||||
|
||||
var seriesHoverInfo = self.getMultiSeriesPlotHoverInfo(plotData, pos);
|
||||
@@ -164,21 +244,21 @@ function ($, _) {
|
||||
}
|
||||
|
||||
var highlightClass = '';
|
||||
if (item && i === item.seriesIndex) {
|
||||
if (item && hoverInfo.index === item.seriesIndex) {
|
||||
highlightClass = 'graph-tooltip-list-item--highlight';
|
||||
}
|
||||
|
||||
series = seriesList[i];
|
||||
series = seriesList[hoverInfo.index];
|
||||
|
||||
value = series.formatValue(hoverInfo.value);
|
||||
|
||||
seriesHtml += '<div class="graph-tooltip-list-item ' + highlightClass + '"><div class="graph-tooltip-series-name">';
|
||||
seriesHtml += '<i class="fa fa-minus" style="color:' + hoverInfo.color +';"></i> ' + hoverInfo.label + ':</div>';
|
||||
seriesHtml += '<div class="graph-tooltip-value">' + value + '</div></div>';
|
||||
plot.highlight(i, hoverInfo.hoverIndex);
|
||||
plot.highlight(hoverInfo.index, hoverInfo.hoverIndex);
|
||||
}
|
||||
|
||||
self.showTooltip(absoluteTime, seriesHtml, pos);
|
||||
self.renderAndShow(absoluteTime, seriesHtml, pos, xMode);
|
||||
}
|
||||
// single series tooltip
|
||||
else if (item) {
|
||||
@@ -199,13 +279,13 @@ function ($, _) {
|
||||
|
||||
group += '<div class="graph-tooltip-value">' + value + '</div>';
|
||||
|
||||
self.showTooltip(absoluteTime, group, pos);
|
||||
self.renderAndShow(absoluteTime, group, pos, xMode);
|
||||
}
|
||||
// no hit
|
||||
else {
|
||||
$tooltip.detach();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
return GraphTooltip;
|
||||
|
||||
48
public/app/plugins/panel/graph/histogram.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* Convert series into array of series values.
|
||||
* @param data Array of series
|
||||
*/
|
||||
export function getSeriesValues(data: any): number[] {
|
||||
let values = [];
|
||||
|
||||
// Count histogam stats
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let series = data[i];
|
||||
for (let j = 0; j < series.data.length; j++) {
|
||||
if (series.data[j][1] !== null) {
|
||||
values.push(series.data[j][1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert array of values into timeseries-like histogram:
|
||||
* [[val_1, count_1], [val_2, count_2], ..., [val_n, count_n]]
|
||||
* @param values
|
||||
* @param bucketSize
|
||||
*/
|
||||
export function convertValuesToHistogram(values: number[], bucketSize: number): any[] {
|
||||
let histogram = {};
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
let bound = getBucketBound(values[i], bucketSize);
|
||||
if (histogram[bound]) {
|
||||
histogram[bound] = histogram[bound] + 1;
|
||||
} else {
|
||||
histogram[bound] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return _.map(histogram, (count, bound) => {
|
||||
return [Number(bound), count];
|
||||
});
|
||||
}
|
||||
|
||||
function getBucketBound(value: number, bucketSize: number): number {
|
||||
return Math.floor(value / bucketSize) * bucketSize;
|
||||
}
|
||||