mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
TestData: Support for csv files & csv content (#34674)
* initial implementation of csv support for test data source * CSV file & content scenarios working * Removing categorical data * fixing handler names * Update pkg/tsdb/testdatasource/csv_data.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/tsdb/testdatasource/csv_data.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/tsdb/testdatasource/csv_data.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/tsdb/testdatasource/csv_data.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/tsdb/testdatasource/csv_data.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/tsdb/testdatasource/csv_data.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Update pkg/tsdb/testdatasource/csv_data.go Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> * Fixed lint issues * updated so it uses the same parsing * more CSV tests * lint fixes * more lint * lint * support time field * migrate manual entry to csv * more test output * more test output * missing file Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
parent
b4ce068f0e
commit
987bffe482
@ -15,7 +15,6 @@
|
|||||||
"editable": true,
|
"editable": true,
|
||||||
"gnetId": null,
|
"gnetId": null,
|
||||||
"graphTooltip": 0,
|
"graphTooltip": 0,
|
||||||
"id": 441,
|
|
||||||
"links": [],
|
"links": [],
|
||||||
"panels": [
|
"panels": [
|
||||||
{
|
{
|
||||||
@ -56,19 +55,21 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 13,
|
"h": 10,
|
||||||
"w": 24,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0
|
"y": 0
|
||||||
},
|
},
|
||||||
"id": 9,
|
"id": 9,
|
||||||
"options": {
|
"options": {
|
||||||
"barWidth": 1,
|
"barWidth": 1,
|
||||||
"groupWidth": 1,
|
"groupWidth": 0.82,
|
||||||
"legend": {
|
"legend": {
|
||||||
"calcs": [],
|
"calcs": [
|
||||||
|
"max"
|
||||||
|
],
|
||||||
"displayMode": "list",
|
"displayMode": "list",
|
||||||
"placement": "bottom"
|
"placement": "right"
|
||||||
},
|
},
|
||||||
"orientation": "auto",
|
"orientation": "auto",
|
||||||
"showValue": "auto",
|
"showValue": "auto",
|
||||||
@ -79,11 +80,85 @@
|
|||||||
},
|
},
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
|
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nNegative, 15, -5\nLong value, 15,10",
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"scenarioId": "categorical_data"
|
"scenarioId": "csv_content"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Plenty od data, automatic value sizing",
|
"title": "Auto sizing & auto show values",
|
||||||
|
"type": "barchart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": null,
|
||||||
|
"description": "Should be smaller given the longer value",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"axisSoftMin": 0,
|
||||||
|
"fillOpacity": 80,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"graph": false,
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false
|
||||||
|
},
|
||||||
|
"lineWidth": 0
|
||||||
|
},
|
||||||
|
"decimals": 2,
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 10,
|
||||||
|
"w": 12,
|
||||||
|
"x": 12,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"id": 15,
|
||||||
|
"options": {
|
||||||
|
"barWidth": 1,
|
||||||
|
"groupWidth": 0.82,
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "right"
|
||||||
|
},
|
||||||
|
"orientation": "auto",
|
||||||
|
"showValue": "auto",
|
||||||
|
"text": {},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nNegative, 15, -5\nLong value, 15,10",
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "csv_content"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Auto sizing & auto show values",
|
||||||
"type": "barchart"
|
"type": "barchart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -124,10 +199,152 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 12,
|
"h": 11,
|
||||||
"w": 24,
|
"w": 8,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 13
|
"y": 10
|
||||||
|
},
|
||||||
|
"id": 16,
|
||||||
|
"options": {
|
||||||
|
"barWidth": 1,
|
||||||
|
"groupWidth": 0.89,
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "right"
|
||||||
|
},
|
||||||
|
"orientation": "auto",
|
||||||
|
"showValue": "auto",
|
||||||
|
"text": {},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"csvContent": "Name,Stat1,Stat2,Stat3,Stat4,Stat5,Stat6,Stat7,Stat8,Stat9,Stat10\nA, 10, 15,8,3,4,12,14,1,5,10\nB, 19, 5,8,3,4,12,14,6,7,2\nC, 15, 5,8,3,4,10,4,6,7,2\n",
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "csv_content"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "auto show values & No room for value",
|
||||||
|
"type": "barchart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": null,
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"axisSoftMin": 0,
|
||||||
|
"fillOpacity": 80,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"graph": false,
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false
|
||||||
|
},
|
||||||
|
"lineWidth": 0
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 11,
|
||||||
|
"w": 8,
|
||||||
|
"x": 8,
|
||||||
|
"y": 10
|
||||||
|
},
|
||||||
|
"id": 17,
|
||||||
|
"options": {
|
||||||
|
"barWidth": 1,
|
||||||
|
"groupWidth": 0.89,
|
||||||
|
"legend": {
|
||||||
|
"calcs": [
|
||||||
|
"max"
|
||||||
|
],
|
||||||
|
"displayMode": "list",
|
||||||
|
"placement": "right"
|
||||||
|
},
|
||||||
|
"orientation": "auto",
|
||||||
|
"showValue": "always",
|
||||||
|
"text": {},
|
||||||
|
"tooltip": {
|
||||||
|
"mode": "single"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"csvContent": "Name,Stat1,Stat2,Stat3,Stat4,Stat5,Stat6,Stat7,Stat8,Stat9,Stat10\nA, 10, 15,8,3,4,12,14,1,5,10\nB, 19, 5,8,3,4,12,14,6,7,2\nC, 15, 5,8,3,4,10,4,6,7,2\n",
|
||||||
|
"refId": "A",
|
||||||
|
"scenarioId": "csv_content"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "auto show values & Always show value",
|
||||||
|
"type": "barchart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datasource": "-- Dashboard --",
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": {
|
||||||
|
"color": {
|
||||||
|
"mode": "palette-classic"
|
||||||
|
},
|
||||||
|
"custom": {
|
||||||
|
"axisLabel": "",
|
||||||
|
"axisPlacement": "auto",
|
||||||
|
"axisSoftMin": 0,
|
||||||
|
"fillOpacity": 80,
|
||||||
|
"gradientMode": "none",
|
||||||
|
"hideFrom": {
|
||||||
|
"graph": false,
|
||||||
|
"legend": false,
|
||||||
|
"tooltip": false
|
||||||
|
},
|
||||||
|
"lineWidth": 0
|
||||||
|
},
|
||||||
|
"mappings": [],
|
||||||
|
"thresholds": {
|
||||||
|
"mode": "absolute",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"color": "green",
|
||||||
|
"value": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"color": "red",
|
||||||
|
"value": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"overrides": []
|
||||||
|
},
|
||||||
|
"gridPos": {
|
||||||
|
"h": 11,
|
||||||
|
"w": 8,
|
||||||
|
"x": 16,
|
||||||
|
"y": 10
|
||||||
},
|
},
|
||||||
"id": 10,
|
"id": 10,
|
||||||
"options": {
|
"options": {
|
||||||
@ -142,7 +359,7 @@
|
|||||||
"showValue": "auto",
|
"showValue": "auto",
|
||||||
"text": {
|
"text": {
|
||||||
"size": 10,
|
"size": 10,
|
||||||
"valueSize": 10
|
"valueSize": 25
|
||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"mode": "single"
|
"mode": "single"
|
||||||
@ -150,11 +367,11 @@
|
|||||||
},
|
},
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"refId": "A",
|
"panelId": 9,
|
||||||
"scenarioId": "categorical_data"
|
"refId": "A"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"title": "Plenty od data, fixed value sizing",
|
"title": "Fixed value sizing",
|
||||||
"type": "barchart"
|
"type": "barchart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -195,267 +412,42 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 12,
|
"h": 11,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 25
|
"y": 21
|
||||||
},
|
},
|
||||||
"id": 11,
|
"id": 18,
|
||||||
"options": {
|
"options": {
|
||||||
"barWidth": 1,
|
"barWidth": 1,
|
||||||
"groupWidth": 1,
|
"groupWidth": 0.82,
|
||||||
"legend": {
|
"legend": {
|
||||||
"calcs": [],
|
"calcs": [
|
||||||
|
"max"
|
||||||
|
],
|
||||||
"displayMode": "list",
|
"displayMode": "list",
|
||||||
"placement": "bottom"
|
"placement": "right"
|
||||||
},
|
|
||||||
"orientation": "auto",
|
|
||||||
"showValue": "auto",
|
|
||||||
"text": {
|
|
||||||
"size": 10
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "single"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"refId": "A",
|
|
||||||
"scenarioId": "categorical_data"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Auto font size, value auto visible",
|
|
||||||
"transformations": [
|
|
||||||
{
|
|
||||||
"id": "filterByValue",
|
|
||||||
"options": {
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"config": {
|
|
||||||
"id": "equal",
|
|
||||||
"options": {
|
|
||||||
"value": "Bedroom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fieldName": "location"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"config": {
|
|
||||||
"id": "equal",
|
|
||||||
"options": {
|
|
||||||
"value": "Cellar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fieldName": "location"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": "any",
|
|
||||||
"type": "include"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "barchart"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": null,
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"axisSoftMin": 0,
|
|
||||||
"fillOpacity": 80,
|
|
||||||
"gradientMode": "none",
|
|
||||||
"hideFrom": {
|
|
||||||
"graph": false,
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false
|
|
||||||
},
|
|
||||||
"lineWidth": 0
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 12,
|
|
||||||
"w": 12,
|
|
||||||
"x": 12,
|
|
||||||
"y": 25
|
|
||||||
},
|
|
||||||
"id": 12,
|
|
||||||
"options": {
|
|
||||||
"barWidth": 1,
|
|
||||||
"groupWidth": 1,
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "bottom"
|
|
||||||
},
|
|
||||||
"orientation": "auto",
|
|
||||||
"showValue": "always",
|
|
||||||
"text": {
|
|
||||||
"size": 10
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "single"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"refId": "A",
|
|
||||||
"scenarioId": "categorical_data"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Auto font size, value always visible",
|
|
||||||
"transformations": [
|
|
||||||
{
|
|
||||||
"id": "filterByValue",
|
|
||||||
"options": {
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"config": {
|
|
||||||
"id": "equal",
|
|
||||||
"options": {
|
|
||||||
"value": "Bedroom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fieldName": "location"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"config": {
|
|
||||||
"id": "equal",
|
|
||||||
"options": {
|
|
||||||
"value": "Cellar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fieldName": "location"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": "any",
|
|
||||||
"type": "include"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"type": "barchart"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": null,
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"axisSoftMin": 0,
|
|
||||||
"fillOpacity": 80,
|
|
||||||
"gradientMode": "none",
|
|
||||||
"hideFrom": {
|
|
||||||
"graph": false,
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false
|
|
||||||
},
|
|
||||||
"lineWidth": 0
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"color": "red",
|
|
||||||
"value": 80
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
},
|
|
||||||
"gridPos": {
|
|
||||||
"h": 12,
|
|
||||||
"w": 12,
|
|
||||||
"x": 0,
|
|
||||||
"y": 37
|
|
||||||
},
|
|
||||||
"id": 13,
|
|
||||||
"options": {
|
|
||||||
"barWidth": 1,
|
|
||||||
"groupWidth": 1,
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "bottom"
|
|
||||||
},
|
},
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
"showValue": "auto",
|
"showValue": "auto",
|
||||||
"text": {
|
"text": {},
|
||||||
"size": 10
|
|
||||||
},
|
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"mode": "single"
|
"mode": "single"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
|
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nLong value, 15,10",
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"scenarioId": "categorical_data"
|
"scenarioId": "csv_content"
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Auto font size, value auto visible",
|
|
||||||
"transformations": [
|
|
||||||
{
|
|
||||||
"id": "filterByValue",
|
|
||||||
"options": {
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"config": {
|
|
||||||
"id": "equal",
|
|
||||||
"options": {
|
|
||||||
"value": "Bedroom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fieldName": "location"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"config": {
|
|
||||||
"id": "equal",
|
|
||||||
"options": {
|
|
||||||
"value": "Cellar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fieldName": "location"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": "any",
|
|
||||||
"type": "include"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"title": "Auto sizing & auto show values",
|
||||||
"type": "barchart"
|
"type": "barchart"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"datasource": null,
|
"datasource": null,
|
||||||
|
"description": "",
|
||||||
"fieldConfig": {
|
"fieldConfig": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"color": {
|
"color": {
|
||||||
@ -492,65 +484,37 @@
|
|||||||
"overrides": []
|
"overrides": []
|
||||||
},
|
},
|
||||||
"gridPos": {
|
"gridPos": {
|
||||||
"h": 12,
|
"h": 11,
|
||||||
"w": 12,
|
"w": 12,
|
||||||
"x": 12,
|
"x": 12,
|
||||||
"y": 37
|
"y": 21
|
||||||
},
|
},
|
||||||
"id": 14,
|
"id": 19,
|
||||||
"options": {
|
"options": {
|
||||||
"barWidth": 1,
|
"barWidth": 1,
|
||||||
"groupWidth": 1,
|
"groupWidth": 0.89,
|
||||||
"legend": {
|
"legend": {
|
||||||
"calcs": [],
|
"calcs": [
|
||||||
|
"max"
|
||||||
|
],
|
||||||
"displayMode": "list",
|
"displayMode": "list",
|
||||||
"placement": "bottom"
|
"placement": "right"
|
||||||
},
|
},
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
"showValue": "always",
|
"showValue": "auto",
|
||||||
"text": {
|
"text": {},
|
||||||
"size": 10
|
|
||||||
},
|
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"mode": "single"
|
"mode": "single"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
|
"csvContent": "Name,Stat1,Stat2,Stat3,Stat4,Stat5,Stat6,Stat7,Stat8,Stat9,Stat10\nA, 10, 15,8,3,4,12,14,1,5,10\nB, 19, 5,8,3,4,12,14,6,7,2\nC, 15, 5,8,3,4,10,4,6,7,2\n",
|
||||||
"refId": "A",
|
"refId": "A",
|
||||||
"scenarioId": "categorical_data"
|
"scenarioId": "csv_content"
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Auto font size, value always visible",
|
|
||||||
"transformations": [
|
|
||||||
{
|
|
||||||
"id": "filterByValue",
|
|
||||||
"options": {
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"config": {
|
|
||||||
"id": "equal",
|
|
||||||
"options": {
|
|
||||||
"value": "Bedroom"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fieldName": "location"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"config": {
|
|
||||||
"id": "equal",
|
|
||||||
"options": {
|
|
||||||
"value": "Cellar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fieldName": "location"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"match": "any",
|
|
||||||
"type": "include"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"title": "auto show values & little room",
|
||||||
"type": "barchart"
|
"type": "barchart"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -571,7 +535,7 @@
|
|||||||
},
|
},
|
||||||
"timepicker": {},
|
"timepicker": {},
|
||||||
"timezone": "",
|
"timezone": "",
|
||||||
"title": "BarChart - value text sizing",
|
"title": "BarChart - Panel Tests - Value sizing",
|
||||||
"uid": "WFlOM-jM1",
|
"uid": "WFlOM-jM1",
|
||||||
"version": 9
|
"version": 3
|
||||||
}
|
}
|
58
pkg/tsdb/influxdb/flux/testdata/boolean_tag.golden.txt
vendored
Normal file
58
pkg/tsdb/influxdb/flux/testdata/boolean_tag.golden.txt
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
🌟 This was machine generated. Do not edit. 🌟
|
||||||
|
|
||||||
|
Frame[0] {}
|
||||||
|
Name: ingress
|
||||||
|
Dimensions: 2 Fields by 1 Rows
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
| Name: _time | Name: file_size |
|
||||||
|
| Labels: | Labels: dead=true, train=350117 |
|
||||||
|
| Type: []*time.Time | Type: []*int64 |
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
| 2020-11-09 17:27:00 +0000 UTC | 3339 |
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Frame[1]
|
||||||
|
Name: ingress
|
||||||
|
Dimensions: 2 Fields by 1 Rows
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
| Name: _time | Name: file_size |
|
||||||
|
| Labels: | Labels: dead=true, train=350125 |
|
||||||
|
| Type: []*time.Time | Type: []*int64 |
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
| 2020-11-10 12:29:00 +0000 UTC | 3666 |
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Frame[2]
|
||||||
|
Name: ingress
|
||||||
|
Dimensions: 2 Fields by 1 Rows
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
| Name: _time | Name: file_size |
|
||||||
|
| Labels: | Labels: dead=true, train=350236 |
|
||||||
|
| Type: []*time.Time | Type: []*int64 |
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
| 2020-11-09 19:01:00 +0000 UTC | 3570 |
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Frame[3]
|
||||||
|
Name: ingress
|
||||||
|
Dimensions: 2 Fields by 1 Rows
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
| Name: _time | Name: file_size |
|
||||||
|
| Labels: | Labels: dead=true, train=350410 |
|
||||||
|
| Type: []*time.Time | Type: []*int64 |
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
| 2020-11-09 07:13:00 +0000 UTC | 2772 |
|
||||||
|
+-------------------------------+---------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
====== TEST DATA RESPONSE (arrow base64) ======
|
||||||
|
FRAME=QVJST1cxAAD/////6AEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAHgAAAADAAAAUAAAACgAAAAEAAAArP7//wgAAAAMAAAAAAAAAAAAAAAFAAAAcmVmSWQAAADM/v//CAAAABAAAAAHAAAAaW5ncmVzcwAEAAAAbmFtZQAAAADw/v//CAAAAAwAAAACAAAAe30AAAQAAABtZXRhAAAAAAIAAADMAAAABAAAAE7///8UAAAAhAAAAIwAAAAAAAIBkAAAAAIAAAAwAAAABAAAAED///8IAAAAFAAAAAkAAABmaWxlX3NpemUAAAAEAAAAbmFtZQAAAABo////CAAAACwAAAAgAAAAeyJkZWFkIjoidHJ1ZSIsInRyYWluIjoiMzUwMTE3In0AAAAABgAAAGxhYmVscwAAAAAAAAgADAAIAAcACAAAAAAAAAFAAAAACQAAAGZpbGVfc2l6ZQASABgAFAATABIADAAAAAgABAASAAAAFAAAAEQAAABMAAAAAAAKAUwAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAQAAAABQAAAF90aW1lAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAFAAAAX3RpbWUAAAAAAAAA/////7gAAAAUAAAAAAAAAAwAFgAUABMADAAEAAwAAAAQAAAAAAAAABQAAAAAAAADAwAKABgADAAIAAQACgAAABQAAABYAAAAAQAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAACAAAAAAAAAAAAAAAAgAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAOjpzv3mRRYLDQAAAAAAABAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA4AAAAAAADAAEAAAD4AQAAAAAAAMAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAeAAAAAMAAABQAAAAKAAAAAQAAACs/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAMz+//8IAAAAEAAAAAcAAABpbmdyZXNzAAQAAABuYW1lAAAAAPD+//8IAAAADAAAAAIAAAB7fQAABAAAAG1ldGEAAAAAAgAAAMwAAAAEAAAATv///xQAAACEAAAAjAAAAAAAAgGQAAAAAgAAADAAAAAEAAAAQP///wgAAAAUAAAACQAAAGZpbGVfc2l6ZQAAAAQAAABuYW1lAAAAAGj///8IAAAALAAAACAAAAB7ImRlYWQiOiJ0cnVlIiwidHJhaW4iOiIzNTAxMTcifQAAAAAGAAAAbGFiZWxzAAAAAAAACAAMAAgABwAIAAAAAAAAAUAAAAAJAAAAZmlsZV9zaXplABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAARAAAAEwAAAAAAAoBTAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAABAAAAAFAAAAX3RpbWUAAAAEAAAAbmFtZQAAAAAAAAAAAAAGAAgABgAGAAAAAAADAAUAAABfdGltZQAAABACAABBUlJPVzE=
|
||||||
|
FRAME=QVJST1cxAAD/////wAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFQAAAACAAAAKAAAAAQAAADM/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAOz+//8IAAAAEAAAAAcAAABpbmdyZXNzAAQAAABuYW1lAAAAAAIAAADMAAAABAAAAE7///8UAAAAhAAAAIwAAAAAAAIBkAAAAAIAAAAwAAAABAAAAED///8IAAAAFAAAAAkAAABmaWxlX3NpemUAAAAEAAAAbmFtZQAAAABo////CAAAACwAAAAgAAAAeyJkZWFkIjoidHJ1ZSIsInRyYWluIjoiMzUwMTI1In0AAAAABgAAAGxhYmVscwAAAAAAAAgADAAIAAcACAAAAAAAAAFAAAAACQAAAGZpbGVfc2l6ZQASABgAFAATABIADAAAAAgABAASAAAAFAAAAEQAAABMAAAAAAAKAUwAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAQAAAABQAAAF90aW1lAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAFAAAAX3RpbWUAAAD/////uAAAABQAAAAAAAAADAAWABQAEwAMAAQADAAAABAAAAAAAAAAFAAAAAAAAAMDAAoAGAAMAAgABAAKAAAAFAAAAFgAAAABAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAAAAAACAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAeCxdTyVGFlIOAAAAAAAAEAAAAAwAFAASAAwACAAEAAwAAAAQAAAALAAAADwAAAAAAAMAAQAAANABAAAAAAAAwAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAVAAAAAIAAAAoAAAABAAAAMz+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAA7P7//wgAAAAQAAAABwAAAGluZ3Jlc3MABAAAAG5hbWUAAAAAAgAAAMwAAAAEAAAATv///xQAAACEAAAAjAAAAAAAAgGQAAAAAgAAADAAAAAEAAAAQP///wgAAAAUAAAACQAAAGZpbGVfc2l6ZQAAAAQAAABuYW1lAAAAAGj///8IAAAALAAAACAAAAB7ImRlYWQiOiJ0cnVlIiwidHJhaW4iOiIzNTAxMjUifQAAAAAGAAAAbGFiZWxzAAAAAAAACAAMAAgABwAIAAAAAAAAAUAAAAAJAAAAZmlsZV9zaXplABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAARAAAAEwAAAAAAAoBTAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAABAAAAAFAAAAX3RpbWUAAAAEAAAAbmFtZQAAAAAAAAAAAAAGAAgABgAGAAAAAAADAAUAAABfdGltZQAAAPABAABBUlJPVzE=
|
||||||
|
FRAME=QVJST1cxAAD/////wAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFQAAAACAAAAKAAAAAQAAADM/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAOz+//8IAAAAEAAAAAcAAABpbmdyZXNzAAQAAABuYW1lAAAAAAIAAADMAAAABAAAAE7///8UAAAAhAAAAIwAAAAAAAIBkAAAAAIAAAAwAAAABAAAAED///8IAAAAFAAAAAkAAABmaWxlX3NpemUAAAAEAAAAbmFtZQAAAABo////CAAAACwAAAAgAAAAeyJkZWFkIjoidHJ1ZSIsInRyYWluIjoiMzUwMjM2In0AAAAABgAAAGxhYmVscwAAAAAAAAgADAAIAAcACAAAAAAAAAFAAAAACQAAAGZpbGVfc2l6ZQASABgAFAATABIADAAAAAgABAASAAAAFAAAAEQAAABMAAAAAAAKAUwAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAQAAAABQAAAF90aW1lAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAFAAAAX3RpbWUAAAD/////uAAAABQAAAAAAAAADAAWABQAEwAMAAQADAAAABAAAAAAAAAAFAAAAAAAAAMDAAoAGAAMAAgABAAKAAAAFAAAAFgAAAABAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAAAAAACAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAOBz5HuxFFvINAAAAAAAAEAAAAAwAFAASAAwACAAEAAwAAAAQAAAALAAAADwAAAAAAAMAAQAAANABAAAAAAAAwAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAVAAAAAIAAAAoAAAABAAAAMz+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAA7P7//wgAAAAQAAAABwAAAGluZ3Jlc3MABAAAAG5hbWUAAAAAAgAAAMwAAAAEAAAATv///xQAAACEAAAAjAAAAAAAAgGQAAAAAgAAADAAAAAEAAAAQP///wgAAAAUAAAACQAAAGZpbGVfc2l6ZQAAAAQAAABuYW1lAAAAAGj///8IAAAALAAAACAAAAB7ImRlYWQiOiJ0cnVlIiwidHJhaW4iOiIzNTAyMzYifQAAAAAGAAAAbGFiZWxzAAAAAAAACAAMAAgABwAIAAAAAAAAAUAAAAAJAAAAZmlsZV9zaXplABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAARAAAAEwAAAAAAAoBTAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAABAAAAAFAAAAX3RpbWUAAAAEAAAAbmFtZQAAAAAAAAAAAAAGAAgABgAGAAAAAAADAAUAAABfdGltZQAAAPABAABBUlJPVzE=
|
||||||
|
FRAME=QVJST1cxAAD/////wAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFQAAAACAAAAKAAAAAQAAADM/v//CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAAOz+//8IAAAAEAAAAAcAAABpbmdyZXNzAAQAAABuYW1lAAAAAAIAAADMAAAABAAAAE7///8UAAAAhAAAAIwAAAAAAAIBkAAAAAIAAAAwAAAABAAAAED///8IAAAAFAAAAAkAAABmaWxlX3NpemUAAAAEAAAAbmFtZQAAAABo////CAAAACwAAAAgAAAAeyJkZWFkIjoidHJ1ZSIsInRyYWluIjoiMzUwNDEwIn0AAAAABgAAAGxhYmVscwAAAAAAAAgADAAIAAcACAAAAAAAAAFAAAAACQAAAGZpbGVfc2l6ZQASABgAFAATABIADAAAAAgABAASAAAAFAAAAEQAAABMAAAAAAAKAUwAAAABAAAADAAAAAgADAAIAAQACAAAAAgAAAAQAAAABQAAAF90aW1lAAAABAAAAG5hbWUAAAAAAAAAAAAABgAIAAYABgAAAAAAAwAFAAAAX3RpbWUAAAD/////uAAAABQAAAAAAAAADAAWABQAEwAMAAQADAAAABAAAAAAAAAAFAAAAAAAAAMDAAoAGAAMAAgABAAKAAAAFAAAAFgAAAABAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAAAAAAAAAAAAACAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAA2MxTfMVFFtQKAAAAAAAAEAAAAAwAFAASAAwACAAEAAwAAAAQAAAALAAAADwAAAAAAAMAAQAAANABAAAAAAAAwAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAMAAAACAAEAAoAAAAIAAAAVAAAAAIAAAAoAAAABAAAAMz+//8IAAAADAAAAAAAAAAAAAAABQAAAHJlZklkAAAA7P7//wgAAAAQAAAABwAAAGluZ3Jlc3MABAAAAG5hbWUAAAAAAgAAAMwAAAAEAAAATv///xQAAACEAAAAjAAAAAAAAgGQAAAAAgAAADAAAAAEAAAAQP///wgAAAAUAAAACQAAAGZpbGVfc2l6ZQAAAAQAAABuYW1lAAAAAGj///8IAAAALAAAACAAAAB7ImRlYWQiOiJ0cnVlIiwidHJhaW4iOiIzNTA0MTAifQAAAAAGAAAAbGFiZWxzAAAAAAAACAAMAAgABwAIAAAAAAAAAUAAAAAJAAAAZmlsZV9zaXplABIAGAAUABMAEgAMAAAACAAEABIAAAAUAAAARAAAAEwAAAAAAAoBTAAAAAEAAAAMAAAACAAMAAgABAAIAAAACAAAABAAAAAFAAAAX3RpbWUAAAAEAAAAbmFtZQAAAAAAAAAAAAAGAAgABgAGAAAAAAADAAUAAABfdGltZQAAAPABAABBUlJPVzE=
|
276
pkg/tsdb/testdatasource/csv_data.go
Normal file
276
pkg/tsdb/testdatasource/csv_data.go
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
package testdatasource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/csv"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *testDataPlugin) handleCsvContentScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
|
resp := backend.NewQueryDataResponse()
|
||||||
|
|
||||||
|
for _, q := range req.Queries {
|
||||||
|
model, err := simplejson.NewJson(q.JSON)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse query json: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
csvContent := model.Get("csvContent").MustString()
|
||||||
|
alias := model.Get("alias").MustString(q.RefID)
|
||||||
|
|
||||||
|
frame, err := p.loadCsvContent(strings.NewReader(csvContent), alias)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respD := resp.Responses[q.RefID]
|
||||||
|
respD.Frames = append(respD.Frames, frame)
|
||||||
|
resp.Responses[q.RefID] = respD
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *testDataPlugin) handleCsvFileScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
|
resp := backend.NewQueryDataResponse()
|
||||||
|
|
||||||
|
for _, q := range req.Queries {
|
||||||
|
model, err := simplejson.NewJson(q.JSON)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse query json %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := model.Get("csvFileName").MustString()
|
||||||
|
|
||||||
|
if len(fileName) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
frame, err := p.loadCsvFile(fileName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respD := resp.Responses[q.RefID]
|
||||||
|
respD.Frames = append(respD.Frames, frame)
|
||||||
|
resp.Responses[q.RefID] = respD
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *testDataPlugin) loadCsvFile(fileName string) (*data.Frame, error) {
|
||||||
|
validFileName := regexp.MustCompile(`([\w_]+)\.csv`)
|
||||||
|
|
||||||
|
if !validFileName.MatchString(fileName) {
|
||||||
|
return nil, fmt.Errorf("invalid csv file name: %q", fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(p.Cfg.StaticRootPath, "testdata", fileName)
|
||||||
|
|
||||||
|
// Can ignore gosec G304 here, because we check the file pattern above
|
||||||
|
// nolint:gosec
|
||||||
|
fileReader, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed open file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := fileReader.Close(); err != nil {
|
||||||
|
p.logger.Warn("Failed to close file", "err", err, "path", fileName)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return p.loadCsvContent(fileReader, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *testDataPlugin) loadCsvContent(ioReader io.Reader, name string) (*data.Frame, error) {
|
||||||
|
reader := csv.NewReader(ioReader)
|
||||||
|
|
||||||
|
// Read the header records
|
||||||
|
headerFields, err := reader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read header line: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := []*data.Field{}
|
||||||
|
fieldNames := []string{}
|
||||||
|
fieldRawValues := [][]string{}
|
||||||
|
|
||||||
|
for _, fieldName := range headerFields {
|
||||||
|
fieldNames = append(fieldNames, strings.Trim(fieldName, " "))
|
||||||
|
fieldRawValues = append(fieldRawValues, []string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
lineValues, err := reader.Read()
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break // reached end of the file
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read line: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for fieldIndex, value := range lineValues {
|
||||||
|
fieldRawValues[fieldIndex] = append(fieldRawValues[fieldIndex], strings.Trim(value, " "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
longest := 0
|
||||||
|
for fieldIndex, rawValues := range fieldRawValues {
|
||||||
|
fieldName := fieldNames[fieldIndex]
|
||||||
|
field, err := csvValuesToField(rawValues)
|
||||||
|
if err == nil {
|
||||||
|
// Check if the values are actually a time field
|
||||||
|
if strings.Contains(strings.ToLower(fieldName), "time") {
|
||||||
|
timeField := toTimeField(field)
|
||||||
|
if timeField != nil {
|
||||||
|
field = timeField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field.Name = fieldName
|
||||||
|
fields = append(fields, field)
|
||||||
|
if field.Len() > longest {
|
||||||
|
longest = field.Len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make all fields the same length
|
||||||
|
for _, field := range fields {
|
||||||
|
delta := field.Len() - longest
|
||||||
|
if delta > 0 {
|
||||||
|
field.Extend(delta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frame := data.NewFrame(name, fields...)
|
||||||
|
return frame, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func csvLineToField(stringInput string) (*data.Field, error) {
|
||||||
|
return csvValuesToField(strings.Split(strings.ReplaceAll(stringInput, " ", ""), ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
func csvValuesToField(parts []string) (*data.Field, error) {
|
||||||
|
if len(parts) < 1 {
|
||||||
|
return nil, fmt.Errorf("csv must have at least one value")
|
||||||
|
}
|
||||||
|
|
||||||
|
first := strings.ToUpper(parts[0])
|
||||||
|
if first == "T" || first == "F" || first == "TRUE" || first == "FALSE" {
|
||||||
|
field := data.NewFieldFromFieldType(data.FieldTypeNullableBool, len(parts))
|
||||||
|
for idx, strVal := range parts {
|
||||||
|
strVal = strings.ToUpper(strVal)
|
||||||
|
if strVal == "NULL" || strVal == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
field.SetConcrete(idx, strVal == "T" || strVal == "TRUE")
|
||||||
|
}
|
||||||
|
return field, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try parsing values as numbers
|
||||||
|
ok := false
|
||||||
|
field := data.NewFieldFromFieldType(data.FieldTypeNullableInt64, len(parts))
|
||||||
|
for idx, strVal := range parts {
|
||||||
|
if strVal == "null" || strVal == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := strconv.ParseInt(strVal, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
field.SetConcrete(idx, val)
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return field, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe floats
|
||||||
|
field = data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, len(parts))
|
||||||
|
for idx, strVal := range parts {
|
||||||
|
if strVal == "null" || strVal == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := strconv.ParseFloat(strVal, 64)
|
||||||
|
if err != nil {
|
||||||
|
ok = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
field.SetConcrete(idx, val)
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return field, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace empty strings with null
|
||||||
|
field = data.NewFieldFromFieldType(data.FieldTypeNullableString, len(parts))
|
||||||
|
for idx, strVal := range parts {
|
||||||
|
if strVal == "null" || strVal == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
field.SetConcrete(idx, strVal)
|
||||||
|
}
|
||||||
|
return field, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This will try to convert the values to a timestamp
|
||||||
|
func toTimeField(field *data.Field) *data.Field {
|
||||||
|
found := false
|
||||||
|
count := field.Len()
|
||||||
|
timeField := data.NewFieldFromFieldType(data.FieldTypeNullableTime, count)
|
||||||
|
timeField.Config = field.Config
|
||||||
|
timeField.Name = field.Name
|
||||||
|
timeField.Labels = field.Labels
|
||||||
|
ft := field.Type()
|
||||||
|
if ft.Numeric() {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
v, err := field.FloatAt(i)
|
||||||
|
if err == nil {
|
||||||
|
t := time.Unix(0, int64(v)*int64(time.Millisecond))
|
||||||
|
timeField.SetConcrete(i, t.UTC())
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return timeField
|
||||||
|
}
|
||||||
|
if ft == data.FieldTypeNullableString || ft == data.FieldTypeString {
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
v, ok := field.ConcreteAt(i)
|
||||||
|
if ok && v != nil {
|
||||||
|
t, err := time.Parse(time.RFC3339, v.(string))
|
||||||
|
if err == nil {
|
||||||
|
timeField.SetConcrete(i, t.UTC())
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return timeField
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
107
pkg/tsdb/testdatasource/csv_data_test.go
Normal file
107
pkg/tsdb/testdatasource/csv_data_test.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package testdatasource
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/experimental"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCSVFileScenario(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.DataPath = t.TempDir()
|
||||||
|
cfg.StaticRootPath = "../../../public"
|
||||||
|
|
||||||
|
p := &testDataPlugin{
|
||||||
|
Cfg: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("loadCsvFile", func(t *testing.T) {
|
||||||
|
files := []string{"population_by_state.csv", "city_stats.csv"}
|
||||||
|
for _, name := range files {
|
||||||
|
t.Run("Should load file and convert to DataFrame", func(t *testing.T) {
|
||||||
|
frame, err := p.loadCsvFile(name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, frame)
|
||||||
|
|
||||||
|
dr := &backend.DataResponse{
|
||||||
|
Frames: data.Frames{frame},
|
||||||
|
}
|
||||||
|
err = experimental.CheckGoldenDataResponse(
|
||||||
|
filepath.Join("testdata", name+".golden.txt"), dr, true,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
files = []string{"simple", "mixed"}
|
||||||
|
for _, name := range files {
|
||||||
|
t.Run("Should load CSV Text: "+name, func(t *testing.T) {
|
||||||
|
filePath := filepath.Join("testdata", name+".csv")
|
||||||
|
// Can ignore gosec G304 here, because this is a constant defined above
|
||||||
|
// nolint:gosec
|
||||||
|
fileReader, err := os.Open(filePath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = fileReader.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
frame, err := p.loadCsvContent(fileReader, name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, frame)
|
||||||
|
|
||||||
|
dr := &backend.DataResponse{
|
||||||
|
Frames: data.Frames{frame},
|
||||||
|
}
|
||||||
|
err = experimental.CheckGoldenDataResponse(
|
||||||
|
filepath.Join("testdata", name+".golden.txt"), dr, true,
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Should not allow non file name chars", func(t *testing.T) {
|
||||||
|
_, err := p.loadCsvFile("../population_by_state.csv")
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadCSV(t *testing.T) {
|
||||||
|
fBool, err := csvLineToField("T, F,F,T ,")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fBool2, err := csvLineToField("true,false,T,F,F")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fNum, err := csvLineToField("1,null,,4,5")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
fStr, err := csvLineToField("a,b,,,c")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
frame := data.NewFrame("", fBool, fBool2, fNum, fStr)
|
||||||
|
frameToJSON, err := data.FrameToJSON(frame)
|
||||||
|
require.NoError(t, err)
|
||||||
|
out := frameToJSON.Bytes(data.IncludeAll)
|
||||||
|
|
||||||
|
require.JSONEq(t, `{"schema":{
|
||||||
|
"fields":[
|
||||||
|
{"type":"boolean","typeInfo":{"frame":"bool","nullable":true}},
|
||||||
|
{"type":"boolean","typeInfo":{"frame":"bool","nullable":true}},
|
||||||
|
{"type":"number","typeInfo":{"frame":"int64","nullable":true}},
|
||||||
|
{"type":"string","typeInfo":{"frame":"string","nullable":true}}
|
||||||
|
]},"data":{
|
||||||
|
"values":[
|
||||||
|
[true,false,false,true,null],
|
||||||
|
[true,false,true,false,false],
|
||||||
|
[1,null,null,4,5],
|
||||||
|
["a","b",null,null,"c"]
|
||||||
|
]}}`, string(out))
|
||||||
|
}
|
@ -27,7 +27,6 @@ const (
|
|||||||
noDataPointsQuery queryType = "no_data_points"
|
noDataPointsQuery queryType = "no_data_points"
|
||||||
datapointsOutsideRangeQuery queryType = "datapoints_outside_range"
|
datapointsOutsideRangeQuery queryType = "datapoints_outside_range"
|
||||||
csvMetricValuesQuery queryType = "csv_metric_values"
|
csvMetricValuesQuery queryType = "csv_metric_values"
|
||||||
manualEntryQuery queryType = "manual_entry"
|
|
||||||
predictablePulseQuery queryType = "predictable_pulse"
|
predictablePulseQuery queryType = "predictable_pulse"
|
||||||
predictableCSVWaveQuery queryType = "predictable_csv_wave"
|
predictableCSVWaveQuery queryType = "predictable_csv_wave"
|
||||||
streamingClientQuery queryType = "streaming_client"
|
streamingClientQuery queryType = "streaming_client"
|
||||||
@ -39,7 +38,8 @@ const (
|
|||||||
serverError500Query queryType = "server_error_500"
|
serverError500Query queryType = "server_error_500"
|
||||||
logsQuery queryType = "logs"
|
logsQuery queryType = "logs"
|
||||||
nodeGraphQuery queryType = "node_graph"
|
nodeGraphQuery queryType = "node_graph"
|
||||||
categoricalDataQuery queryType = "categorical_data"
|
csvFileQueryType queryType = "csv_file"
|
||||||
|
csvContentQueryType queryType = "csv_content"
|
||||||
)
|
)
|
||||||
|
|
||||||
type queryType string
|
type queryType string
|
||||||
@ -117,12 +117,6 @@ Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means
|
|||||||
handler: p.handleDatapointsOutsideRangeScenario,
|
handler: p.handleDatapointsOutsideRangeScenario,
|
||||||
})
|
})
|
||||||
|
|
||||||
p.registerScenario(&Scenario{
|
|
||||||
ID: string(manualEntryQuery),
|
|
||||||
Name: "Manual Entry",
|
|
||||||
handler: p.handleManualEntryScenario,
|
|
||||||
})
|
|
||||||
|
|
||||||
p.registerScenario(&Scenario{
|
p.registerScenario(&Scenario{
|
||||||
ID: string(csvMetricValuesQuery),
|
ID: string(csvMetricValuesQuery),
|
||||||
Name: "CSV Metric Values",
|
Name: "CSV Metric Values",
|
||||||
@ -190,9 +184,15 @@ Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means
|
|||||||
})
|
})
|
||||||
|
|
||||||
p.registerScenario(&Scenario{
|
p.registerScenario(&Scenario{
|
||||||
ID: string(categoricalDataQuery),
|
ID: string(csvFileQueryType),
|
||||||
Name: "Categorical Data",
|
Name: "CSV File",
|
||||||
handler: p.handleCategoricalDataScenario,
|
handler: p.handleCsvFileScenario,
|
||||||
|
})
|
||||||
|
|
||||||
|
p.registerScenario(&Scenario{
|
||||||
|
ID: string(csvContentQueryType),
|
||||||
|
Name: "CSV Content",
|
||||||
|
handler: p.handleCsvContentScenario,
|
||||||
})
|
})
|
||||||
|
|
||||||
p.queryMux.HandleFunc("", p.handleFallbackScenario)
|
p.queryMux.HandleFunc("", p.handleFallbackScenario)
|
||||||
@ -286,97 +286,6 @@ func (p *testDataPlugin) handleDatapointsOutsideRangeScenario(ctx context.Contex
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *testDataPlugin) handleManualEntryScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
|
||||||
resp := backend.NewQueryDataResponse()
|
|
||||||
|
|
||||||
for _, q := range req.Queries {
|
|
||||||
model, err := simplejson.NewJson(q.JSON)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading query")
|
|
||||||
}
|
|
||||||
points := model.Get("points").MustArray()
|
|
||||||
|
|
||||||
frame := newSeriesForQuery(q, model, 0)
|
|
||||||
|
|
||||||
timeField := data.NewFieldFromFieldType(data.FieldTypeTime, 0)
|
|
||||||
valueField := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, 0)
|
|
||||||
timeField.Name = data.TimeSeriesTimeFieldName
|
|
||||||
valueField.Name = data.TimeSeriesValueFieldName
|
|
||||||
|
|
||||||
for _, val := range points {
|
|
||||||
pointValues := val.([]interface{})
|
|
||||||
|
|
||||||
var value *float64
|
|
||||||
|
|
||||||
if pointValues[0] != nil {
|
|
||||||
if valueFloat, err := strconv.ParseFloat(string(pointValues[0].(json.Number)), 64); err == nil {
|
|
||||||
value = &valueFloat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timeInt, err := strconv.ParseInt(string(pointValues[1].(json.Number)), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
t := time.Unix(timeInt/int64(1e+3), (timeInt%int64(1e+3))*int64(1e+6))
|
|
||||||
|
|
||||||
timeField.Append(t)
|
|
||||||
valueField.Append(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.Fields = data.Fields{timeField, valueField}
|
|
||||||
|
|
||||||
respD := resp.Responses[q.RefID]
|
|
||||||
respD.Frames = append(respD.Frames, frame)
|
|
||||||
resp.Responses[q.RefID] = respD
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func csvToFieldValues(stringInput string) (*data.Field, error) {
|
|
||||||
parts := strings.Split(strings.ReplaceAll(stringInput, " ", ""), ",")
|
|
||||||
if len(parts) < 1 {
|
|
||||||
return nil, fmt.Errorf("csv must have at least one value")
|
|
||||||
}
|
|
||||||
|
|
||||||
first := strings.ToUpper(parts[0])
|
|
||||||
if first == "T" || first == "F" || first == "TRUE" || first == "FALSE" {
|
|
||||||
field := data.NewFieldFromFieldType(data.FieldTypeNullableBool, len(parts))
|
|
||||||
for idx, strVal := range parts {
|
|
||||||
strVal = strings.ToUpper(strVal)
|
|
||||||
if strVal == "NULL" || strVal == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
field.SetConcrete(idx, strVal == "T" || strVal == "TRUE")
|
|
||||||
}
|
|
||||||
return field, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we can not parse the first value as a number, assume strings
|
|
||||||
_, err := strconv.ParseFloat(first, 64)
|
|
||||||
if err != nil {
|
|
||||||
field := data.NewFieldFromFieldType(data.FieldTypeNullableString, len(parts))
|
|
||||||
for idx, strVal := range parts {
|
|
||||||
if strVal == "null" || strVal == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
field.SetConcrete(idx, strVal)
|
|
||||||
}
|
|
||||||
return field, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set any valid numbers
|
|
||||||
field := data.NewFieldFromFieldType(data.FieldTypeNullableFloat64, len(parts))
|
|
||||||
for idx, strVal := range parts {
|
|
||||||
if val, err := strconv.ParseFloat(strVal, 64); err == nil {
|
|
||||||
field.SetConcrete(idx, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return field, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *testDataPlugin) handleCSVMetricValuesScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
func (p *testDataPlugin) handleCSVMetricValuesScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
resp := backend.NewQueryDataResponse()
|
resp := backend.NewQueryDataResponse()
|
||||||
|
|
||||||
@ -388,7 +297,7 @@ func (p *testDataPlugin) handleCSVMetricValuesScenario(ctx context.Context, req
|
|||||||
|
|
||||||
stringInput := model.Get("stringInput").MustString()
|
stringInput := model.Get("stringInput").MustString()
|
||||||
|
|
||||||
valueField, err := csvToFieldValues(stringInput)
|
valueField, err := csvLineToField(stringInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -695,27 +604,6 @@ func (p *testDataPlugin) handleLogsScenario(ctx context.Context, req *backend.Qu
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *testDataPlugin) handleCategoricalDataScenario(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
|
||||||
resp := backend.NewQueryDataResponse()
|
|
||||||
for _, q := range req.Queries {
|
|
||||||
frame := data.NewFrame(q.RefID,
|
|
||||||
data.NewField("location", nil, []string{}),
|
|
||||||
data.NewField("temperature", nil, []int64{}),
|
|
||||||
data.NewField("humidity", nil, []int64{}),
|
|
||||||
data.NewField("pressure", nil, []int64{}),
|
|
||||||
)
|
|
||||||
|
|
||||||
for i := 0; i < len(houseLocations); i++ {
|
|
||||||
frame.AppendRow(houseLocations[i], rand.Int63n(40+40)-40, rand.Int63n(100), rand.Int63n(1020-900)+900)
|
|
||||||
}
|
|
||||||
respD := resp.Responses[q.RefID]
|
|
||||||
respD.Frames = append(respD.Frames, frame)
|
|
||||||
resp.Responses[q.RefID] = respD
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func randomWalk(query backend.DataQuery, model *simplejson.Json, index int) *data.Frame {
|
func randomWalk(query backend.DataQuery, model *simplejson.Json, index int) *data.Frame {
|
||||||
timeWalkerMs := query.TimeRange.From.UnixNano() / int64(time.Millisecond)
|
timeWalkerMs := query.TimeRange.From.UnixNano() / int64(time.Millisecond)
|
||||||
to := query.TimeRange.To.UnixNano() / int64(time.Millisecond)
|
to := query.TimeRange.To.UnixNano() / int64(time.Millisecond)
|
||||||
|
@ -185,57 +185,6 @@ func TestTestdataScenarios(t *testing.T) {
|
|||||||
require.True(t, maxNil)
|
require.True(t, maxNil)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("manual entry ", func(t *testing.T) {
|
|
||||||
t.Run("should support nulls and return all data", func(t *testing.T) {
|
|
||||||
timeRange := plugins.DataTimeRange{From: "5m", To: "now", Now: time.Now()}
|
|
||||||
|
|
||||||
query := backend.DataQuery{
|
|
||||||
RefID: "A",
|
|
||||||
TimeRange: backend.TimeRange{
|
|
||||||
From: timeRange.MustGetFrom(),
|
|
||||||
To: timeRange.MustGetTo(),
|
|
||||||
},
|
|
||||||
JSON: []byte(`{ "points": [
|
|
||||||
[
|
|
||||||
4, 1616557148000
|
|
||||||
],
|
|
||||||
[
|
|
||||||
null, 1616558756000
|
|
||||||
],
|
|
||||||
[
|
|
||||||
4, 1616561658000
|
|
||||||
]] }`),
|
|
||||||
}
|
|
||||||
|
|
||||||
req := &backend.QueryDataRequest{
|
|
||||||
PluginContext: backend.PluginContext{},
|
|
||||||
Queries: []backend.DataQuery{query},
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := p.handleManualEntryScenario(context.Background(), req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, resp)
|
|
||||||
|
|
||||||
dResp, exists := resp.Responses[query.RefID]
|
|
||||||
require.True(t, exists)
|
|
||||||
require.NoError(t, dResp.Error)
|
|
||||||
|
|
||||||
require.Len(t, dResp.Frames, 1)
|
|
||||||
frame := dResp.Frames[0]
|
|
||||||
require.Len(t, frame.Fields, 2)
|
|
||||||
require.Equal(t, "Time", frame.Fields[0].Name)
|
|
||||||
require.Equal(t, "Value", frame.Fields[1].Name)
|
|
||||||
require.Equal(t, 3, frame.Rows())
|
|
||||||
|
|
||||||
vals := frame.Fields[1]
|
|
||||||
v, _ := vals.ConcreteAt(0)
|
|
||||||
require.Equal(t, float64(4), v)
|
|
||||||
require.Nil(t, vals.At(1))
|
|
||||||
v, _ = vals.ConcreteAt(2)
|
|
||||||
require.Equal(t, float64(4), v)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseLabels(t *testing.T) {
|
func TestParseLabels(t *testing.T) {
|
||||||
@ -263,38 +212,3 @@ func TestParseLabels(t *testing.T) {
|
|||||||
assert.Equal(t, expectedTags, parseLabels(model), fmt.Sprintf("Actual tags in test case %d doesn't match expected tags", i+1))
|
assert.Equal(t, expectedTags, parseLabels(model), fmt.Sprintf("Actual tags in test case %d doesn't match expected tags", i+1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadCSV(t *testing.T) {
|
|
||||||
fBool, err := csvToFieldValues("T, F,F,T ,")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
fBool2, err := csvToFieldValues("true,false,T,F,F")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
fNum, err := csvToFieldValues("1,2,,4,5")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
fStr, err := csvToFieldValues("a,b,,,c")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
frame := data.NewFrame("", fBool, fBool2, fNum, fStr)
|
|
||||||
frameToJSON, err := data.FrameToJSON(frame)
|
|
||||||
require.NoError(t, err)
|
|
||||||
out := frameToJSON.Bytes(data.IncludeAll)
|
|
||||||
|
|
||||||
// require.Equal(t, "", string(out))
|
|
||||||
|
|
||||||
require.JSONEq(t, `{"schema":{
|
|
||||||
"fields":[
|
|
||||||
{"type":"boolean","typeInfo":{"frame":"bool","nullable":true}},
|
|
||||||
{"type":"boolean","typeInfo":{"frame":"bool","nullable":true}},
|
|
||||||
{"type":"number","typeInfo":{"frame":"float64","nullable":true}},
|
|
||||||
{"type":"string","typeInfo":{"frame":"string","nullable":true}}
|
|
||||||
]},"data":{
|
|
||||||
"values":[
|
|
||||||
[true,false,false,true,null],
|
|
||||||
[true,false,true,false,false],
|
|
||||||
[1,2,null,4,5],
|
|
||||||
["a","b",null,null,"c"]
|
|
||||||
]}}`, string(out))
|
|
||||||
}
|
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
|
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
|
||||||
"github.com/grafana/grafana/pkg/registry"
|
"github.com/grafana/grafana/pkg/registry"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -19,6 +20,7 @@ func init() {
|
|||||||
|
|
||||||
type testDataPlugin struct {
|
type testDataPlugin struct {
|
||||||
BackendPluginManager backendplugin.Manager `inject:""`
|
BackendPluginManager backendplugin.Manager `inject:""`
|
||||||
|
Cfg *setting.Cfg `inject:""`
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
scenarios map[string]*Scenario
|
scenarios map[string]*Scenario
|
||||||
queryMux *datasource.QueryTypeMux
|
queryMux *datasource.QueryTypeMux
|
||||||
|
17
pkg/tsdb/testdatasource/testdata/city_stats.csv.golden.txt
vendored
Normal file
17
pkg/tsdb/testdatasource/testdata/city_stats.csv.golden.txt
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
🌟 This was machine generated. Do not edit. 🌟
|
||||||
|
|
||||||
|
Frame[0]
|
||||||
|
Name: city_stats.csv
|
||||||
|
Dimensions: 2 Fields by 2 Rows
|
||||||
|
+-----------------+------------------+
|
||||||
|
| Name: City | Name: Population |
|
||||||
|
| Labels: | Labels: |
|
||||||
|
| Type: []*string | Type: []*int64 |
|
||||||
|
+-----------------+------------------+
|
||||||
|
| Stockholm | 1000000 |
|
||||||
|
| New York | 13333300 |
|
||||||
|
+-----------------+------------------+
|
||||||
|
|
||||||
|
|
||||||
|
====== TEST DATA RESPONSE (arrow base64) ======
|
||||||
|
FRAME=QVJST1cxAAD/////gAEAABAAAAAAAAoADgAMAAsABAAKAAAAFAAAAAAAAAEDAAoADAAAAAgABAAKAAAACAAAAFwAAAACAAAAKAAAAAQAAAAE////CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAACT///8IAAAAGAAAAA4AAABjaXR5X3N0YXRzLmNzdgAABAAAAG5hbWUAAAAAAgAAAIwAAAAEAAAAjv///xQAAABAAAAASAAAAAAAAgFMAAAAAQAAAAQAAAB8////CAAAABQAAAAKAAAAUG9wdWxhdGlvbgAABAAAAG5hbWUAAAAAAAAAAAgADAAIAAcACAAAAAAAAAFAAAAACgAAAFBvcHVsYXRpb24AAAAAEgAYABQAEwASAAwAAAAIAAQAEgAAABQAAABEAAAASAAAAAAABQFEAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABDaXR5AAAAAAQAAABuYW1lAAAAAAAAAAAEAAQABAAAAAQAAABDaXR5AAAAAP/////IAAAAFAAAAAAAAAAMABYAFAATAAwABAAMAAAAOAAAAAAAAAAUAAAAAAAAAwMACgAYAAwACAAEAAoAAAAUAAAAaAAAAAIAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAGAAAAAAAAAAoAAAAAAAAAAAAAAAAAAAAKAAAAAAAAAAQAAAAAAAAAAAAAAACAAAAAgAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAACQAAABEAAAAAAAAAU3RvY2tob2xtTmV3IFlvcmsAAAAAAAAAQEIPAAAAAAA0c8sAAAAAABAAAAAMABQAEgAMAAgABAAMAAAAEAAAACwAAAA8AAAAAAADAAEAAACQAQAAAAAAANAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoADAAAAAgABAAKAAAACAAAAFwAAAACAAAAKAAAAAQAAAAE////CAAAAAwAAAAAAAAAAAAAAAUAAAByZWZJZAAAACT///8IAAAAGAAAAA4AAABjaXR5X3N0YXRzLmNzdgAABAAAAG5hbWUAAAAAAgAAAIwAAAAEAAAAjv///xQAAABAAAAASAAAAAAAAgFMAAAAAQAAAAQAAAB8////CAAAABQAAAAKAAAAUG9wdWxhdGlvbgAABAAAAG5hbWUAAAAAAAAAAAgADAAIAAcACAAAAAAAAAFAAAAACgAAAFBvcHVsYXRpb24AAAAAEgAYABQAEwASAAwAAAAIAAQAEgAAABQAAABEAAAASAAAAAAABQFEAAAAAQAAAAwAAAAIAAwACAAEAAgAAAAIAAAAEAAAAAQAAABDaXR5AAAAAAQAAABuYW1lAAAAAAAAAAAEAAQABAAAAAQAAABDaXR5AAAAALABAABBUlJPVzE=
|
3
pkg/tsdb/testdatasource/testdata/mixed.csv
vendored
Normal file
3
pkg/tsdb/testdatasource/testdata/mixed.csv
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Field1,Field2,Field3,123
|
||||||
|
True,Hello,6,
|
||||||
|
False,6,World,6
|
|
17
pkg/tsdb/testdatasource/testdata/mixed.golden.txt
vendored
Normal file
17
pkg/tsdb/testdatasource/testdata/mixed.golden.txt
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
🌟 This was machine generated. Do not edit. 🌟
|
||||||
|
|
||||||
|
Frame[0]
|
||||||
|
Name: mixed
|
||||||
|
Dimensions: 4 Fields by 2 Rows
|
||||||
|
+---------------+-----------------+-----------------+----------------+
|
||||||
|
| Name: Field1 | Name: Field2 | Name: Field3 | Name: 123 |
|
||||||
|
| Labels: | Labels: | Labels: | Labels: |
|
||||||
|
| Type: []*bool | Type: []*string | Type: []*string | Type: []*int64 |
|
||||||
|
+---------------+-----------------+-----------------+----------------+
|
||||||
|
| true | Hello | 6 | null |
|
||||||
|
| false | 6 | World | 6 |
|
||||||
|
+---------------+-----------------+-----------------+----------------+
|
||||||
|
|
||||||
|
|
||||||
|
====== TEST DATA RESPONSE (arrow base64) ======
|
||||||
|
FRAME=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
|
18
pkg/tsdb/testdatasource/testdata/population_by_state.csv.golden.txt
vendored
Normal file
18
pkg/tsdb/testdatasource/testdata/population_by_state.csv.golden.txt
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
🌟 This was machine generated. Do not edit. 🌟
|
||||||
|
|
||||||
|
Frame[0]
|
||||||
|
Name: population_by_state.csv
|
||||||
|
Dimensions: 4 Fields by 3 Rows
|
||||||
|
+-----------------+----------------+----------------+----------------+
|
||||||
|
| Name: State | Name: 2020 | Name: 2000 | Name: 1980 |
|
||||||
|
| Labels: | Labels: | Labels: | Labels: |
|
||||||
|
| Type: []*string | Type: []*int64 | Type: []*int64 | Type: []*int64 |
|
||||||
|
+-----------------+----------------+----------------+----------------+
|
||||||
|
| California | 39368078 | 33987977 | 23800800 |
|
||||||
|
| Texas | 29360759 | 20944499 | 14338208 |
|
||||||
|
| Florida | 21733312 | 16047515 | 9839835 |
|
||||||
|
+-----------------+----------------+----------------+----------------+
|
||||||
|
|
||||||
|
|
||||||
|
====== TEST DATA RESPONSE (arrow base64) ======
|
||||||
|
FRAME=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
|
3
pkg/tsdb/testdatasource/testdata/simple.csv
vendored
Normal file
3
pkg/tsdb/testdatasource/testdata/simple.csv
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Field1,Field2,Field3,Float,Time
|
||||||
|
A,5,6,6.7,1621987000000
|
||||||
|
B,6,7,8.9,1621988000000
|
|
17
pkg/tsdb/testdatasource/testdata/simple.golden.txt
vendored
Normal file
17
pkg/tsdb/testdatasource/testdata/simple.golden.txt
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
🌟 This was machine generated. Do not edit. 🌟
|
||||||
|
|
||||||
|
Frame[0]
|
||||||
|
Name: simple
|
||||||
|
Dimensions: 5 Fields by 2 Rows
|
||||||
|
+-----------------+----------------+----------------+------------------+-------------------------------+
|
||||||
|
| Name: Field1 | Name: Field2 | Name: Field3 | Name: Float | Name: Time |
|
||||||
|
| Labels: | Labels: | Labels: | Labels: | Labels: |
|
||||||
|
| Type: []*string | Type: []*int64 | Type: []*int64 | Type: []*float64 | Type: []*time.Time |
|
||||||
|
+-----------------+----------------+----------------+------------------+-------------------------------+
|
||||||
|
| A | 5 | 6 | 6.7 | 2021-05-25 23:56:40 +0000 UTC |
|
||||||
|
| B | 6 | 7 | 8.9 | 2021-05-26 00:13:20 +0000 UTC |
|
||||||
|
+-----------------+----------------+----------------+------------------+-------------------------------+
|
||||||
|
|
||||||
|
|
||||||
|
====== TEST DATA RESPONSE (arrow base64) ======
|
||||||
|
FRAME=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
|
@ -6,7 +6,7 @@ import { useAsync } from 'react-use';
|
|||||||
import { selectors as editorSelectors } from '@grafana/e2e-selectors';
|
import { selectors as editorSelectors } from '@grafana/e2e-selectors';
|
||||||
import { Input, InlineFieldRow, InlineField, Select, TextArea, InlineSwitch } from '@grafana/ui';
|
import { Input, InlineFieldRow, InlineField, Select, TextArea, InlineSwitch } from '@grafana/ui';
|
||||||
import { QueryEditorProps, SelectableValue } from '@grafana/data';
|
import { QueryEditorProps, SelectableValue } from '@grafana/data';
|
||||||
import { StreamingClientEditor, ManualEntryEditor, RandomWalkEditor } from './components';
|
import { StreamingClientEditor, RandomWalkEditor } from './components';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { TestDataDataSource } from './datasource';
|
import { TestDataDataSource } from './datasource';
|
||||||
@ -17,6 +17,8 @@ import { defaultCSVWaveQuery, defaultPulseQuery, defaultQuery } from './constant
|
|||||||
import { GrafanaLiveEditor } from './components/GrafanaLiveEditor';
|
import { GrafanaLiveEditor } from './components/GrafanaLiveEditor';
|
||||||
import { NodeGraphEditor } from './components/NodeGraphEditor';
|
import { NodeGraphEditor } from './components/NodeGraphEditor';
|
||||||
import { defaultStreamQuery } from './runStreams';
|
import { defaultStreamQuery } from './runStreams';
|
||||||
|
import { CSVFileEditor } from './components/CSVFileEditor';
|
||||||
|
import { CSVContentEditor } from './components/CSVContentEditor';
|
||||||
|
|
||||||
const showLabelsFor = ['random_walk', 'predictable_pulse'];
|
const showLabelsFor = ['random_walk', 'predictable_pulse'];
|
||||||
const endpoints = [
|
const endpoints = [
|
||||||
@ -38,6 +40,20 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: Props)
|
|||||||
query = { ...defaultQuery, ...query };
|
query = { ...defaultQuery, ...query };
|
||||||
|
|
||||||
const { loading, value: scenarioList } = useAsync<Scenario[]>(async () => {
|
const { loading, value: scenarioList } = useAsync<Scenario[]>(async () => {
|
||||||
|
// migrate manual_entry (unusable since 7, removed in 8)
|
||||||
|
if (query.scenarioId === 'manual_entry' && (query as any).points) {
|
||||||
|
let csvContent = 'Time,Value\n';
|
||||||
|
for (const point of (query as any).points) {
|
||||||
|
csvContent += `${point[1]},${point[0]}\n`;
|
||||||
|
}
|
||||||
|
onChange({
|
||||||
|
refId: query.refId,
|
||||||
|
datasource: query.datasource,
|
||||||
|
scenarioId: 'csv_content',
|
||||||
|
csvContent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return datasource.getScenarios();
|
return datasource.getScenarios();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -205,10 +221,11 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: Props)
|
|||||||
)}
|
)}
|
||||||
</InlineFieldRow>
|
</InlineFieldRow>
|
||||||
|
|
||||||
{scenarioId === 'manual_entry' && <ManualEntryEditor onChange={onUpdate} query={query} onRunQuery={onRunQuery} />}
|
|
||||||
{scenarioId === 'random_walk' && <RandomWalkEditor onChange={onInputChange} query={query} />}
|
{scenarioId === 'random_walk' && <RandomWalkEditor onChange={onInputChange} query={query} />}
|
||||||
{scenarioId === 'streaming_client' && <StreamingClientEditor onChange={onStreamClientChange} query={query} />}
|
{scenarioId === 'streaming_client' && <StreamingClientEditor onChange={onStreamClientChange} query={query} />}
|
||||||
{scenarioId === 'live' && <GrafanaLiveEditor onChange={onUpdate} query={query} />}
|
{scenarioId === 'live' && <GrafanaLiveEditor onChange={onUpdate} query={query} />}
|
||||||
|
{scenarioId === 'csv_file' && <CSVFileEditor onChange={onUpdate} query={query} />}
|
||||||
|
{scenarioId === 'csv_content' && <CSVContentEditor onChange={onUpdate} query={query} />}
|
||||||
{scenarioId === 'logs' && (
|
{scenarioId === 'logs' && (
|
||||||
<InlineFieldRow>
|
<InlineFieldRow>
|
||||||
<InlineField label="Lines" labelWidth={14}>
|
<InlineField label="Lines" labelWidth={14}>
|
||||||
|
21
public/app/plugins/datasource/testdata/components/CSVContentEditor.tsx
vendored
Normal file
21
public/app/plugins/datasource/testdata/components/CSVContentEditor.tsx
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React, { ChangeEvent } from 'react';
|
||||||
|
import { InlineField, TextArea } from '@grafana/ui';
|
||||||
|
import { EditorProps } from '../QueryEditor';
|
||||||
|
|
||||||
|
export const CSVContentEditor = ({ onChange, query }: EditorProps) => {
|
||||||
|
const onContent = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
onChange({ ...query, csvContent: e.currentTarget.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineField label="CSV" labelWidth={14}>
|
||||||
|
<TextArea
|
||||||
|
width="100%"
|
||||||
|
rows={10}
|
||||||
|
onBlur={onContent}
|
||||||
|
placeholder="CSV content"
|
||||||
|
defaultValue={query.csvContent ?? ''}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
);
|
||||||
|
};
|
26
public/app/plugins/datasource/testdata/components/CSVFileEditor.tsx
vendored
Normal file
26
public/app/plugins/datasource/testdata/components/CSVFileEditor.tsx
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { InlineField, InlineFieldRow, Select } from '@grafana/ui';
|
||||||
|
import { SelectableValue } from '@grafana/data';
|
||||||
|
import { EditorProps } from '../QueryEditor';
|
||||||
|
|
||||||
|
export const CSVFileEditor = ({ onChange, query }: EditorProps) => {
|
||||||
|
const onChangeFileName = ({ value }: SelectableValue<string>) => {
|
||||||
|
onChange({ ...query, csvFileName: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = ['population_by_state.csv', 'city_stats.csv'].map((name) => ({ label: name, value: name }));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InlineFieldRow>
|
||||||
|
<InlineField label="File" labelWidth={14}>
|
||||||
|
<Select
|
||||||
|
width={32}
|
||||||
|
onChange={onChangeFileName}
|
||||||
|
placeholder="Select csv file"
|
||||||
|
options={files}
|
||||||
|
value={files.find((f) => f.value === query.csvFileName)}
|
||||||
|
/>
|
||||||
|
</InlineField>
|
||||||
|
</InlineFieldRow>
|
||||||
|
);
|
||||||
|
};
|
@ -1,87 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
||||||
import userEvent from '@testing-library/user-event';
|
|
||||||
import { ManualEntryEditor, Props } from './ManualEntryEditor';
|
|
||||||
import { defaultQuery } from '../constants';
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
const mockOnChange = jest.fn();
|
|
||||||
const setup = (testProps?: Partial<Props>) => {
|
|
||||||
const props = {
|
|
||||||
onRunQuery: jest.fn(),
|
|
||||||
query: defaultQuery,
|
|
||||||
onChange: mockOnChange,
|
|
||||||
...testProps,
|
|
||||||
};
|
|
||||||
|
|
||||||
return render(<ManualEntryEditor {...props} />);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('ManualEntryEditor', () => {
|
|
||||||
it('should render', () => {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
expect(screen.getByLabelText(/New value/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByLabelText(/Time/i)).toBeInTheDocument();
|
|
||||||
expect(screen.getByRole('button', { name: /Add/i })).toBeInTheDocument();
|
|
||||||
expect(screen.getByText(/select point/i)).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add new point', async () => {
|
|
||||||
setup();
|
|
||||||
|
|
||||||
userEvent.type(screen.getByLabelText(/New value/i), '10');
|
|
||||||
userEvent.clear(screen.getByLabelText(/Time/i));
|
|
||||||
userEvent.type(screen.getByLabelText(/Time/i), '2020-11-01T14:19:30+00:00');
|
|
||||||
userEvent.click(screen.getByRole('button', { name: /Add/i }));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockOnChange).toHaveBeenCalledWith(expect.objectContaining({ points: [[10, 1604240370000]] }));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should list selected points and delete selected ones', async () => {
|
|
||||||
const editor = setup({
|
|
||||||
query: {
|
|
||||||
...defaultQuery,
|
|
||||||
points: [
|
|
||||||
[10, 1604240370000],
|
|
||||||
[15, 1604340370000],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
let select = screen.getByText('All values').nextSibling!;
|
|
||||||
await fireEvent.keyDown(select, { keyCode: 40 });
|
|
||||||
const points = screen.getAllByLabelText('Select option');
|
|
||||||
expect(points).toHaveLength(2);
|
|
||||||
expect(screen.queryByRole('button', { name: 'Delete' })).not.toBeInTheDocument();
|
|
||||||
|
|
||||||
await userEvent.click(points[0]);
|
|
||||||
|
|
||||||
expect(screen.getByRole('button', { name: 'Delete' })).toBeInTheDocument();
|
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole('button', { name: 'Delete' }));
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(mockOnChange).toHaveBeenCalledWith(expect.objectContaining({ points: [[15, 1604340370000]] }));
|
|
||||||
});
|
|
||||||
|
|
||||||
editor.rerender(
|
|
||||||
<ManualEntryEditor
|
|
||||||
query={{
|
|
||||||
...defaultQuery,
|
|
||||||
points: [[15, 1604340370000]],
|
|
||||||
}}
|
|
||||||
onChange={jest.fn()}
|
|
||||||
onRunQuery={jest.fn()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
select = screen.getByText('All values').nextSibling!;
|
|
||||||
await fireEvent.keyDown(select, { keyCode: 40 });
|
|
||||||
expect(screen.getAllByLabelText('Select option')).toHaveLength(1);
|
|
||||||
expect(screen.queryByRole('button', { name: 'Delete' })).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,92 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { dateMath, dateTime, SelectableValue } from '@grafana/data';
|
|
||||||
import { Form, InlineField, InlineFieldRow, Input, InputControl, Select, Button } from '@grafana/ui';
|
|
||||||
import { EditorProps } from '../QueryEditor';
|
|
||||||
import { NewPoint } from '../types';
|
|
||||||
|
|
||||||
export interface Props extends EditorProps {
|
|
||||||
onRunQuery: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ManualEntryEditor = ({ onChange, query, onRunQuery }: Props) => {
|
|
||||||
const points = query.points ?? [];
|
|
||||||
|
|
||||||
const addPoint = (point: NewPoint) => {
|
|
||||||
const newPointTime = dateMath.parse(point.newPointTime);
|
|
||||||
const pointsUpdated = [...points, [Number(point.newPointValue), newPointTime!.valueOf()]].sort(
|
|
||||||
(a, b) => a[1] - b[1]
|
|
||||||
);
|
|
||||||
onChange({ ...query, points: pointsUpdated });
|
|
||||||
onRunQuery();
|
|
||||||
};
|
|
||||||
|
|
||||||
const deletePoint = (point: SelectableValue) => {
|
|
||||||
const pointsUpdated = points.filter((_, index) => index !== point.value);
|
|
||||||
onChange({ ...query, points: pointsUpdated });
|
|
||||||
onRunQuery();
|
|
||||||
};
|
|
||||||
|
|
||||||
const pointOptions = points.map((point, index) => {
|
|
||||||
return {
|
|
||||||
label: dateTime(point[1]).format('MMMM Do YYYY, H:mm:ss') + ' : ' + point[0],
|
|
||||||
value: index,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form onSubmit={addPoint} maxWidth="none">
|
|
||||||
{({ register, control, watch, setValue }) => {
|
|
||||||
const selectedPoint = watch('selectedPoint' as any) as SelectableValue;
|
|
||||||
return (
|
|
||||||
<InlineFieldRow>
|
|
||||||
<InlineField label="New value" labelWidth={14}>
|
|
||||||
<Input
|
|
||||||
{...register('newPointValue')}
|
|
||||||
width={32}
|
|
||||||
type="number"
|
|
||||||
placeholder="value"
|
|
||||||
id={`newPointValue-${query.refId}`}
|
|
||||||
/>
|
|
||||||
</InlineField>
|
|
||||||
<InlineField label="Time" labelWidth={14}>
|
|
||||||
<Input
|
|
||||||
{...register('newPointTime')}
|
|
||||||
width={32}
|
|
||||||
id={`newPointTime-${query.refId}`}
|
|
||||||
placeholder="time"
|
|
||||||
defaultValue={dateTime().format()}
|
|
||||||
/>
|
|
||||||
</InlineField>
|
|
||||||
<InlineField>
|
|
||||||
<Button variant="secondary">Add</Button>
|
|
||||||
</InlineField>
|
|
||||||
<InlineField label="All values">
|
|
||||||
<InputControl
|
|
||||||
name={'selectedPoint' as any}
|
|
||||||
control={control}
|
|
||||||
render={({ field: { ref, ...field } }) => (
|
|
||||||
<Select {...field} options={pointOptions} width={32} placeholder="Select point" />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</InlineField>
|
|
||||||
|
|
||||||
{selectedPoint?.value !== undefined && (
|
|
||||||
<InlineField>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="destructive"
|
|
||||||
onClick={() => {
|
|
||||||
setValue('selectedPoint' as any, [{ value: undefined, label: 'Select value' }]);
|
|
||||||
deletePoint(selectedPoint);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
</InlineField>
|
|
||||||
)}
|
|
||||||
</InlineFieldRow>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,3 +1,2 @@
|
|||||||
export { StreamingClientEditor } from './StreamingClientEditor';
|
export { StreamingClientEditor } from './StreamingClientEditor';
|
||||||
export { ManualEntryEditor } from './ManualEntryEditor';
|
|
||||||
export { RandomWalkEditor } from './RandomWalkEditor';
|
export { RandomWalkEditor } from './RandomWalkEditor';
|
||||||
|
@ -61,6 +61,19 @@ export class TestDataDataSource extends DataSourceWithBackend<TestDataQuery> {
|
|||||||
case 'node_graph':
|
case 'node_graph':
|
||||||
streams.push(this.nodesQuery(target, options));
|
streams.push(this.nodesQuery(target, options));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// Unusable since 7, removed in 8
|
||||||
|
case 'manual_entry': {
|
||||||
|
let csvContent = 'Time,Value\n';
|
||||||
|
if ((target as any).points) {
|
||||||
|
for (const point of (target as any).points) {
|
||||||
|
csvContent += `${point[1]},${point[0]}\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target.scenarioId = 'csv_content';
|
||||||
|
target.csvContent = csvContent;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (target.alias) {
|
if (target.alias) {
|
||||||
target.alias = this.templateSrv.replace(target.alias, options.scopedVars);
|
target.alias = this.templateSrv.replace(target.alias, options.scopedVars);
|
||||||
|
11
public/app/plugins/datasource/testdata/types.ts
vendored
11
public/app/plugins/datasource/testdata/types.ts
vendored
@ -6,19 +6,10 @@ export interface Scenario {
|
|||||||
stringInput: string;
|
stringInput: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PointValue = number;
|
|
||||||
|
|
||||||
export interface NewPoint {
|
|
||||||
newPointValue: string;
|
|
||||||
newPointTime: string;
|
|
||||||
}
|
|
||||||
export type Points = PointValue[][];
|
|
||||||
|
|
||||||
export interface TestDataQuery extends DataQuery {
|
export interface TestDataQuery extends DataQuery {
|
||||||
alias?: string;
|
alias?: string;
|
||||||
scenarioId: string;
|
scenarioId: string;
|
||||||
stringInput?: string;
|
stringInput?: string;
|
||||||
points?: Points;
|
|
||||||
stream?: StreamingQuery;
|
stream?: StreamingQuery;
|
||||||
pulseWave?: PulseWaveQuery;
|
pulseWave?: PulseWaveQuery;
|
||||||
csvWave?: CSVWave[];
|
csvWave?: CSVWave[];
|
||||||
@ -27,6 +18,8 @@ export interface TestDataQuery extends DataQuery {
|
|||||||
levelColumn?: boolean;
|
levelColumn?: boolean;
|
||||||
channel?: string; // for grafana live
|
channel?: string; // for grafana live
|
||||||
nodes?: NodesQuery;
|
nodes?: NodesQuery;
|
||||||
|
csvFileName?: string;
|
||||||
|
csvContent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodesQuery {
|
export interface NodesQuery {
|
||||||
|
@ -10,7 +10,6 @@ export class TestDataVariableSupport extends StandardVariableSupport<TestDataDat
|
|||||||
stringInput: query.query,
|
stringInput: query.query,
|
||||||
scenarioId: 'variables-query',
|
scenarioId: 'variables-query',
|
||||||
csvWave: undefined,
|
csvWave: undefined,
|
||||||
points: [],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
public/testdata/city_stats.csv
vendored
Normal file
5
public/testdata/city_stats.csv
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
City, Population
|
||||||
|
Stockholm, 1000000
|
||||||
|
New York, 13333300
|
||||||
|
|
||||||
|
|
|
6
public/testdata/population_by_state.csv
vendored
Normal file
6
public/testdata/population_by_state.csv
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
State, 2020, 2000, 1980
|
||||||
|
California, 39368078,33987977,23800800
|
||||||
|
Texas, 29360759, 20944499, 14338208
|
||||||
|
Florida, 21733312, 16047515, 9839835
|
||||||
|
|
||||||
|
|
|
Loading…
Reference in New Issue
Block a user