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,
|
||||
"gnetId": null,
|
||||
"graphTooltip": 0,
|
||||
"id": 441,
|
||||
"links": [],
|
||||
"panels": [
|
||||
{
|
||||
@ -56,19 +55,21 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 13,
|
||||
"w": 24,
|
||||
"h": 10,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 9,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 1,
|
||||
"groupWidth": 0.82,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"calcs": [
|
||||
"max"
|
||||
],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
"placement": "right"
|
||||
},
|
||||
"orientation": "auto",
|
||||
"showValue": "auto",
|
||||
@ -79,11 +80,85 @@
|
||||
},
|
||||
"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": "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"
|
||||
},
|
||||
{
|
||||
@ -124,10 +199,152 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"w": 24,
|
||||
"h": 11,
|
||||
"w": 8,
|
||||
"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,
|
||||
"options": {
|
||||
@ -142,7 +359,7 @@
|
||||
"showValue": "auto",
|
||||
"text": {
|
||||
"size": 10,
|
||||
"valueSize": 10
|
||||
"valueSize": 25
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
@ -150,11 +367,11 @@
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"scenarioId": "categorical_data"
|
||||
"panelId": 9,
|
||||
"refId": "A"
|
||||
}
|
||||
],
|
||||
"title": "Plenty od data, fixed value sizing",
|
||||
"title": "Fixed value sizing",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
@ -195,267 +412,42 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"h": 11,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 25
|
||||
"y": 21
|
||||
},
|
||||
"id": 11,
|
||||
"id": 18,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 1,
|
||||
"groupWidth": 0.82,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"calcs": [
|
||||
"max"
|
||||
],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
},
|
||||
"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"
|
||||
"placement": "right"
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"showValue": "auto",
|
||||
"text": {
|
||||
"size": 10
|
||||
},
|
||||
"text": {},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"csvContent": "Name,Stat1,Stat2\nStockholm, 10, 15\nNew York, 19, 5\nLondon, 10, 1\nLong value, 15,10",
|
||||
"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"
|
||||
}
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "Auto sizing & auto show values",
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"datasource": null,
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
@ -492,65 +484,37 @@
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 12,
|
||||
"h": 11,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 37
|
||||
"y": 21
|
||||
},
|
||||
"id": 14,
|
||||
"id": 19,
|
||||
"options": {
|
||||
"barWidth": 1,
|
||||
"groupWidth": 1,
|
||||
"groupWidth": 0.89,
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"calcs": [
|
||||
"max"
|
||||
],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom"
|
||||
"placement": "right"
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"showValue": "always",
|
||||
"text": {
|
||||
"size": 10
|
||||
},
|
||||
"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": "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"
|
||||
}
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "auto show values & little room",
|
||||
"type": "barchart"
|
||||
}
|
||||
],
|
||||
@ -571,7 +535,7 @@
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "BarChart - value text sizing",
|
||||
"title": "BarChart - Panel Tests - Value sizing",
|
||||
"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"
|
||||
datapointsOutsideRangeQuery queryType = "datapoints_outside_range"
|
||||
csvMetricValuesQuery queryType = "csv_metric_values"
|
||||
manualEntryQuery queryType = "manual_entry"
|
||||
predictablePulseQuery queryType = "predictable_pulse"
|
||||
predictableCSVWaveQuery queryType = "predictable_csv_wave"
|
||||
streamingClientQuery queryType = "streaming_client"
|
||||
@ -39,7 +38,8 @@ const (
|
||||
serverError500Query queryType = "server_error_500"
|
||||
logsQuery queryType = "logs"
|
||||
nodeGraphQuery queryType = "node_graph"
|
||||
categoricalDataQuery queryType = "categorical_data"
|
||||
csvFileQueryType queryType = "csv_file"
|
||||
csvContentQueryType queryType = "csv_content"
|
||||
)
|
||||
|
||||
type queryType string
|
||||
@ -117,12 +117,6 @@ Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means
|
||||
handler: p.handleDatapointsOutsideRangeScenario,
|
||||
})
|
||||
|
||||
p.registerScenario(&Scenario{
|
||||
ID: string(manualEntryQuery),
|
||||
Name: "Manual Entry",
|
||||
handler: p.handleManualEntryScenario,
|
||||
})
|
||||
|
||||
p.registerScenario(&Scenario{
|
||||
ID: string(csvMetricValuesQuery),
|
||||
Name: "CSV Metric Values",
|
||||
@ -190,9 +184,15 @@ Timestamps will line up evenly on timeStepSeconds (For example, 60 seconds means
|
||||
})
|
||||
|
||||
p.registerScenario(&Scenario{
|
||||
ID: string(categoricalDataQuery),
|
||||
Name: "Categorical Data",
|
||||
handler: p.handleCategoricalDataScenario,
|
||||
ID: string(csvFileQueryType),
|
||||
Name: "CSV File",
|
||||
handler: p.handleCsvFileScenario,
|
||||
})
|
||||
|
||||
p.registerScenario(&Scenario{
|
||||
ID: string(csvContentQueryType),
|
||||
Name: "CSV Content",
|
||||
handler: p.handleCsvContentScenario,
|
||||
})
|
||||
|
||||
p.queryMux.HandleFunc("", p.handleFallbackScenario)
|
||||
@ -286,97 +286,6 @@ func (p *testDataPlugin) handleDatapointsOutsideRangeScenario(ctx context.Contex
|
||||
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) {
|
||||
resp := backend.NewQueryDataResponse()
|
||||
|
||||
@ -388,7 +297,7 @@ func (p *testDataPlugin) handleCSVMetricValuesScenario(ctx context.Context, req
|
||||
|
||||
stringInput := model.Get("stringInput").MustString()
|
||||
|
||||
valueField, err := csvToFieldValues(stringInput)
|
||||
valueField, err := csvLineToField(stringInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -695,27 +604,6 @@ func (p *testDataPlugin) handleLogsScenario(ctx context.Context, req *backend.Qu
|
||||
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 {
|
||||
timeWalkerMs := query.TimeRange.From.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)
|
||||
})
|
||||
})
|
||||
|
||||
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) {
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
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/coreplugin"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -19,6 +20,7 @@ func init() {
|
||||
|
||||
type testDataPlugin struct {
|
||||
BackendPluginManager backendplugin.Manager `inject:""`
|
||||
Cfg *setting.Cfg `inject:""`
|
||||
logger log.Logger
|
||||
scenarios map[string]*Scenario
|
||||
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 { Input, InlineFieldRow, InlineField, Select, TextArea, InlineSwitch } from '@grafana/ui';
|
||||
import { QueryEditorProps, SelectableValue } from '@grafana/data';
|
||||
import { StreamingClientEditor, ManualEntryEditor, RandomWalkEditor } from './components';
|
||||
import { StreamingClientEditor, RandomWalkEditor } from './components';
|
||||
|
||||
// Types
|
||||
import { TestDataDataSource } from './datasource';
|
||||
@ -17,6 +17,8 @@ import { defaultCSVWaveQuery, defaultPulseQuery, defaultQuery } from './constant
|
||||
import { GrafanaLiveEditor } from './components/GrafanaLiveEditor';
|
||||
import { NodeGraphEditor } from './components/NodeGraphEditor';
|
||||
import { defaultStreamQuery } from './runStreams';
|
||||
import { CSVFileEditor } from './components/CSVFileEditor';
|
||||
import { CSVContentEditor } from './components/CSVContentEditor';
|
||||
|
||||
const showLabelsFor = ['random_walk', 'predictable_pulse'];
|
||||
const endpoints = [
|
||||
@ -38,6 +40,20 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: Props)
|
||||
query = { ...defaultQuery, ...query };
|
||||
|
||||
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();
|
||||
}, []);
|
||||
|
||||
@ -205,10 +221,11 @@ export const QueryEditor = ({ query, datasource, onChange, onRunQuery }: Props)
|
||||
)}
|
||||
</InlineFieldRow>
|
||||
|
||||
{scenarioId === 'manual_entry' && <ManualEntryEditor onChange={onUpdate} query={query} onRunQuery={onRunQuery} />}
|
||||
{scenarioId === 'random_walk' && <RandomWalkEditor onChange={onInputChange} query={query} />}
|
||||
{scenarioId === 'streaming_client' && <StreamingClientEditor onChange={onStreamClientChange} 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' && (
|
||||
<InlineFieldRow>
|
||||
<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 { ManualEntryEditor } from './ManualEntryEditor';
|
||||
export { RandomWalkEditor } from './RandomWalkEditor';
|
||||
|
@ -61,6 +61,19 @@ export class TestDataDataSource extends DataSourceWithBackend<TestDataQuery> {
|
||||
case 'node_graph':
|
||||
streams.push(this.nodesQuery(target, options));
|
||||
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:
|
||||
if (target.alias) {
|
||||
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;
|
||||
}
|
||||
|
||||
export type PointValue = number;
|
||||
|
||||
export interface NewPoint {
|
||||
newPointValue: string;
|
||||
newPointTime: string;
|
||||
}
|
||||
export type Points = PointValue[][];
|
||||
|
||||
export interface TestDataQuery extends DataQuery {
|
||||
alias?: string;
|
||||
scenarioId: string;
|
||||
stringInput?: string;
|
||||
points?: Points;
|
||||
stream?: StreamingQuery;
|
||||
pulseWave?: PulseWaveQuery;
|
||||
csvWave?: CSVWave[];
|
||||
@ -27,6 +18,8 @@ export interface TestDataQuery extends DataQuery {
|
||||
levelColumn?: boolean;
|
||||
channel?: string; // for grafana live
|
||||
nodes?: NodesQuery;
|
||||
csvFileName?: string;
|
||||
csvContent?: string;
|
||||
}
|
||||
|
||||
export interface NodesQuery {
|
||||
|
@ -10,7 +10,6 @@ export class TestDataVariableSupport extends StandardVariableSupport<TestDataDat
|
||||
stringInput: query.query,
|
||||
scenarioId: 'variables-query',
|
||||
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