mirror of
https://github.com/grafana/grafana.git
synced 2025-01-15 19:22:34 -06:00
Templating: Introduce macros to simplify and optimize some scopedVars (#65317)
* Templating: Introduce macros to simplify and optimize some scopedVars * Fixing tests * fix test * minor fix * refactoring so macros work with formatting * remove breaking change and keep current inconsistency * Rename valueIndex to rowIndex * Minor fixes * Added test dashboard * Added tags to dashboard * Update * Added test to check it returns match * Update * Fixed dashboard * fix
This commit is contained in:
parent
2b73f8cfd5
commit
b7b608418d
@ -3089,8 +3089,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "6"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
|
||||
],
|
||||
"public/app/features/dashboard/state/PanelModel.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
@ -3873,6 +3872,14 @@ exports[`better eslint`] = {
|
||||
"public/app/features/teams/state/selectors.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/templating/formatVariableValue.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
|
||||
],
|
||||
"public/app/features/templating/macroRegistry.test.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/templating/template_srv.mock.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
@ -3891,12 +3898,9 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "11"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "12"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "13"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "14"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "14"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "15"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "16"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "17"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "18"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "19"]
|
||||
[0, 0, 0, "Do not use any type assertions.", "16"]
|
||||
],
|
||||
"public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
|
871
devenv/dev-dashboards/feature-templating/datadata-macros.json
Normal file
871
devenv/dev-dashboards/feature-templating/datadata-macros.json
Normal file
@ -0,0 +1,871 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 1267,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"code": {
|
||||
"language": "plaintext",
|
||||
"showLineNumbers": false,
|
||||
"showMiniMap": false
|
||||
},
|
||||
"content": "* `__all_variables`=${__all_variables}\n* `__url_time_range`=${__url_time_range}",
|
||||
"mode": "markdown"
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"title": "Panel Title",
|
||||
"type": "text"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {
|
||||
"type": "auto"
|
||||
},
|
||||
"inspect": false
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "value=${__value.raw}&time=${__value.time}&__value:percentencode=${__value:percentencode}&text=${__value.text}",
|
||||
"url": "value=${__value.raw}&time=${__value.time}justvalue=${__value:percentencode}&text=${__value.text}"
|
||||
}
|
||||
],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "percent"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 3
|
||||
},
|
||||
"id": 2,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"show": false
|
||||
},
|
||||
"showHeader": true,
|
||||
"showRowNums": false
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"title": "DataLink: with __value.raw=&__value.time=&__value:percentencode=",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "Value link",
|
||||
"url": "value=${__value.raw}"
|
||||
}
|
||||
],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 3
|
||||
},
|
||||
"id": 3,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 5
|
||||
}
|
||||
],
|
||||
"title": "Stat panel with __value.raw ",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-GrYlRd"
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "${__value.raw}",
|
||||
"url": "${__value.raw}"
|
||||
}
|
||||
],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 10
|
||||
},
|
||||
"id": 6,
|
||||
"options": {
|
||||
"displayMode": "basic",
|
||||
"minVizHeight": 10,
|
||||
"minVizWidth": 0,
|
||||
"orientation": "horizontal",
|
||||
"reduceOptions": {
|
||||
"calcs": [],
|
||||
"fields": "",
|
||||
"values": true
|
||||
},
|
||||
"showUnfilled": true,
|
||||
"valueMode": "color"
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"csvFileName": "browser_marketshare.csv",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_file"
|
||||
}
|
||||
],
|
||||
"title": "data link __value.raw",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "limit",
|
||||
"options": {
|
||||
"limitField": 5
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "bargauge"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"description": "Since this is using getFrameDisplayName it works kind badly (especially with testdata) and only returns the `Series (refId)`. \n\nSo this should show:\n* Series (Query1)\n* Series (Query2)",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"displayName": "${__series.name}",
|
||||
"links": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "Value link",
|
||||
"url": "value=${__calc}"
|
||||
}
|
||||
],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 10
|
||||
},
|
||||
"id": 12,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "Query1",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 1
|
||||
},
|
||||
{
|
||||
"alias": "",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"hide": false,
|
||||
"refId": "Query2",
|
||||
"scenarioId": "random_walk",
|
||||
"seriesCount": 1
|
||||
}
|
||||
],
|
||||
"title": "${series.name} in display name",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {
|
||||
"type": "auto"
|
||||
},
|
||||
"inspect": false
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "__data.refId=${__data.refId}&__data.fields[0]=${__data.fields[0]}&cluster=${__field.labels.cluster}",
|
||||
"url": "refId=${__data.refId}&__data.fields[0]=${__data.fields[0]}&cluster=${__field.labels.cluster}"
|
||||
}
|
||||
],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "percent"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 17
|
||||
},
|
||||
"id": 11,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"show": false
|
||||
},
|
||||
"showHeader": true,
|
||||
"showRowNums": false
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"labels": "cluster=US",
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "DataLink: refId=${__data.refId}&__data.fields[0]=${__data.fields[0]}&cluster=${__field.labels.cluster}",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 0,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "auto",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "${__value.raw}",
|
||||
"url": "${__value.raw}"
|
||||
}
|
||||
],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 17
|
||||
},
|
||||
"id": 10,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"alias": "10,20,30,40",
|
||||
"csvContent": "Time, value, test\n\"2023-03-24T17:12:12.347Z\", 10,hello\n\"2023-03-24T17:22:12.347Z\", 20,asd\n\"2023-03-24T17:32:12.347Z\", 30,asd2\n\"2023-03-24T17:42:12.347Z\", 40,as34\n",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_content"
|
||||
},
|
||||
{
|
||||
"alias": "5,6,7",
|
||||
"csvContent": "Time, value, test\n\"2023-03-24T17:12:12.347Z\", 5,hello\n\"2023-03-24T17:22:12.347Z\", 6,asd\n\"2023-03-24T17:42:12.347Z\", 7,as34\n",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "B",
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "Data links with ${__value.raw}",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "joinByField",
|
||||
"options": {}
|
||||
}
|
||||
],
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {
|
||||
"type": "auto"
|
||||
},
|
||||
"inspect": false
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"title": "__field.name=${__field.name}&__field.labels.cluster=${__field.labels.cluster}",
|
||||
"url": "__field.name=${__field.name}&__field.labels.cluster=${__field.labels.cluster}"
|
||||
}
|
||||
],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "percent"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 24
|
||||
},
|
||||
"id": 13,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"show": false
|
||||
},
|
||||
"showHeader": true,
|
||||
"showRowNums": false
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"labels": "cluster=US",
|
||||
"refId": "A",
|
||||
"scenarioId": "random_walk"
|
||||
}
|
||||
],
|
||||
"title": "DataLink: __field.name=&__field.labels.cluster",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"description": "The stat display names should be \n* Stockholm = Bad\n* New York = Good \n",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"displayName": "$__cell_0 = $__cell_2",
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 24
|
||||
},
|
||||
"id": 14,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": true
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvContent": "name, value, name2\nStockholm, 10, Good\nNew York, 15, Bad",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "DisplayName with __cell_0 = __cell_2",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"description": "The stat display names should be \n* Stockholm = Bad\n* New York = Good \n",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"custom": {
|
||||
"align": "auto",
|
||||
"cellOptions": {
|
||||
"type": "auto"
|
||||
},
|
||||
"inspect": false
|
||||
},
|
||||
"displayName": "${__field.name}",
|
||||
"links": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "Value link",
|
||||
"url": "value=${__calc}"
|
||||
}
|
||||
],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 31
|
||||
},
|
||||
"id": 15,
|
||||
"options": {
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"countRows": false,
|
||||
"fields": "",
|
||||
"reducer": [
|
||||
"sum"
|
||||
],
|
||||
"show": false
|
||||
},
|
||||
"showHeader": true,
|
||||
"showRowNums": false
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvContent": "name, value, name2\nStockholm, 10, Good\nNew York, 15, Bad",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "DisplayName: __field.name",
|
||||
"type": "table"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"description": "The stat display names should be \n* Stockholm = Bad\n* New York = Good \n",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"displayName": "${__data.fields[0]} = ${__data.fields[2]}",
|
||||
"links": [
|
||||
{
|
||||
"targetBlank": true,
|
||||
"title": "__data.fields[0] = ${__data.fields[0]} = __value.raw = ${__value.raw}",
|
||||
"url": "__data.fields[0] = ${__data.fields[0]} = __value.raw = ${__value.raw}"
|
||||
}
|
||||
],
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 7,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 31
|
||||
},
|
||||
"id": 16,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": true
|
||||
},
|
||||
"textMode": "auto"
|
||||
},
|
||||
"pluginVersion": "9.5.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"alias": "",
|
||||
"csvContent": "name, value, name2\nStockholm, 10, Good\nNew York, 15, Bad",
|
||||
"datasource": {
|
||||
"type": "testdata",
|
||||
"uid": "PD8C576611E62080A"
|
||||
},
|
||||
"refId": "A",
|
||||
"scenarioId": "csv_content"
|
||||
}
|
||||
],
|
||||
"title": "$__data.fields[0] = $__data.fields[2] with datalinks",
|
||||
"type": "stat"
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 38,
|
||||
"style": "dark",
|
||||
"tags": ["gdev", "templating"],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
"selected": false,
|
||||
"text": "A",
|
||||
"value": "A"
|
||||
},
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"multi": false,
|
||||
"name": "customVar",
|
||||
"options": [
|
||||
{
|
||||
"selected": true,
|
||||
"text": "A",
|
||||
"value": "A"
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "B",
|
||||
"value": "B"
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "C",
|
||||
"value": "C"
|
||||
}
|
||||
],
|
||||
"query": "A,B,C",
|
||||
"queryValue": "",
|
||||
"skipUrlSync": false,
|
||||
"type": "custom"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "2023-03-24T17:12:12.347Z",
|
||||
"to": "2023-03-24T17:42:12.347Z"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "Templating - Macros",
|
||||
"uid": "e7c29343-6d1e-4167-9c13-803fe5be8c46",
|
||||
"version": 48,
|
||||
"weekStart": ""
|
||||
}
|
@ -149,6 +149,13 @@ local dashboard = grafana.dashboard;
|
||||
id: 0,
|
||||
}
|
||||
},
|
||||
dashboard.new('datadata-macros', import '../dev-dashboards/feature-templating/datadata-macros.json') +
|
||||
resource.addMetadata('folder', 'dev-dashboards') +
|
||||
{
|
||||
spec+: {
|
||||
id: 0,
|
||||
}
|
||||
},
|
||||
dashboard.new('demo1', import '../dev-dashboards/datasource-testdata/demo1.json') +
|
||||
resource.addMetadata('folder', 'dev-dashboards') +
|
||||
{
|
||||
|
@ -329,7 +329,7 @@ describe('applyFieldOverrides', () => {
|
||||
data.fields[1].getLinks!({ valueRowIndex: 0 });
|
||||
|
||||
expect(data.fields[1].config.decimals).toEqual(1);
|
||||
expect(replaceVariablesCalls[3].__value.value.text).toEqual('100.0');
|
||||
expect(replaceVariablesCalls[3].__dataContext?.value.rowIndex).toEqual(0);
|
||||
});
|
||||
|
||||
it('creates a deep clone of field config', () => {
|
||||
|
@ -5,13 +5,13 @@ import usePrevious from 'react-use/lib/usePrevious';
|
||||
import { VariableFormatID } from '@grafana/schema';
|
||||
|
||||
import { compareArrayValues, compareDataFrameStructures, guessFieldTypeForField } from '../dataframe';
|
||||
import { getTimeField } from '../dataframe/processDataFrame';
|
||||
import { PanelPlugin } from '../panel/PanelPlugin';
|
||||
import { GrafanaTheme2 } from '../themes';
|
||||
import { asHexString } from '../themes/colorManipulator';
|
||||
import { fieldMatchers, reduceField, ReducerID } from '../transformations';
|
||||
import {
|
||||
ApplyFieldOverrideOptions,
|
||||
DataContextScopedVar,
|
||||
DataFrame,
|
||||
DataLink,
|
||||
DecimalCount,
|
||||
@ -34,9 +34,8 @@ import {
|
||||
ValueLinkConfig,
|
||||
} from '../types';
|
||||
import { FieldMatcher } from '../types/transformations';
|
||||
import { DataLinkBuiltInVars, locationUtil } from '../utils';
|
||||
import { locationUtil } from '../utils';
|
||||
import { mapInternalLinkToExplore } from '../utils/dataLinks';
|
||||
import { formattedValueToString } from '../valueFormats';
|
||||
|
||||
import { FieldConfigOptionsRegistry } from './FieldConfigOptionsRegistry';
|
||||
import { getDisplayProcessor, getRawDisplayProcessor } from './displayProcessor';
|
||||
@ -370,29 +369,21 @@ export const getLinksSupplier =
|
||||
if (!field.config.links || field.config.links.length === 0) {
|
||||
return [];
|
||||
}
|
||||
const timeRangeUrl = locationUtil.getTimeRangeUrlParams();
|
||||
const { timeField } = getTimeField(frame);
|
||||
|
||||
return field.config.links.map((link: DataLink) => {
|
||||
const variablesQuery = locationUtil.getVariablesUrlParams();
|
||||
let dataFrameVars = {};
|
||||
let valueVars = {};
|
||||
let dataContext: DataContextScopedVar = { value: { frame, field } };
|
||||
|
||||
// We are not displaying reduction result
|
||||
if (config.valueRowIndex !== undefined && !isNaN(config.valueRowIndex)) {
|
||||
dataContext.value.rowIndex = config.valueRowIndex;
|
||||
|
||||
const fieldsProxy = getFieldDisplayValuesProxy({
|
||||
frame,
|
||||
rowIndex: config.valueRowIndex,
|
||||
timeZone: timeZone,
|
||||
});
|
||||
|
||||
valueVars = {
|
||||
raw: field.values.get(config.valueRowIndex),
|
||||
numeric: fieldsProxy[field.name].numeric,
|
||||
text: fieldsProxy[field.name].text,
|
||||
time: timeField ? timeField.values.get(config.valueRowIndex) : undefined,
|
||||
};
|
||||
|
||||
dataFrameVars = {
|
||||
__data: {
|
||||
value: {
|
||||
@ -404,32 +395,13 @@ export const getLinksSupplier =
|
||||
},
|
||||
};
|
||||
} else {
|
||||
if (config.calculatedValue) {
|
||||
valueVars = {
|
||||
raw: config.calculatedValue.numeric,
|
||||
numeric: config.calculatedValue.numeric,
|
||||
text: formattedValueToString(config.calculatedValue),
|
||||
};
|
||||
}
|
||||
dataContext.value.calculatedValue = config.calculatedValue;
|
||||
}
|
||||
|
||||
const variables: ScopedVars = {
|
||||
...fieldScopedVars,
|
||||
__value: {
|
||||
text: 'Value',
|
||||
value: valueVars,
|
||||
},
|
||||
...dataFrameVars,
|
||||
[DataLinkBuiltInVars.keepTime]: {
|
||||
text: timeRangeUrl,
|
||||
value: timeRangeUrl,
|
||||
skipFormat: true,
|
||||
},
|
||||
[DataLinkBuiltInVars.includeVars]: {
|
||||
text: variablesQuery,
|
||||
value: variablesQuery,
|
||||
skipFormat: true,
|
||||
},
|
||||
__dataContext: dataContext,
|
||||
};
|
||||
|
||||
if (link.onClick) {
|
||||
|
@ -273,7 +273,7 @@ describe('calculateField transformer w/ timeseries', () => {
|
||||
};
|
||||
for (const key of Object.keys(variables)) {
|
||||
if (target === `$${key}`) {
|
||||
return variables[key].value + '';
|
||||
return variables[key]!.value + '';
|
||||
}
|
||||
}
|
||||
return target;
|
||||
|
@ -219,7 +219,7 @@ describe('filterByName transformer', () => {
|
||||
},
|
||||
};
|
||||
for (const key of Object.keys(variables)) {
|
||||
return target.replace(`$${key}`, variables[key].value);
|
||||
return target.replace(`$${key}`, variables[key]!.value);
|
||||
}
|
||||
return target;
|
||||
},
|
||||
|
@ -1,8 +1,24 @@
|
||||
import { DataFrame, Field } from './dataFrame';
|
||||
import { DisplayValue } from './displayValue';
|
||||
|
||||
export interface ScopedVar<T = any> {
|
||||
text?: any;
|
||||
value: T;
|
||||
skipUrlSync?: boolean;
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
export interface ScopedVars extends Record<string, ScopedVar> {}
|
||||
export interface ScopedVars {
|
||||
__dataContext?: DataContextScopedVar;
|
||||
[key: string]: ScopedVar | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by data link macros
|
||||
*/
|
||||
export interface DataContextScopedVar {
|
||||
value: {
|
||||
frame: DataFrame;
|
||||
field: Field;
|
||||
rowIndex?: number;
|
||||
calculatedValue?: DisplayValue;
|
||||
};
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ describe('mapInternalLinkToExplore', () => {
|
||||
config: {},
|
||||
values: new ArrayVector([2]),
|
||||
},
|
||||
replaceVariables: (val, scopedVars) => val.replace(/\$var/g, scopedVars!['var1'].value),
|
||||
replaceVariables: (val, scopedVars) => val.replace(/\$var/g, scopedVars!['var1']!.value),
|
||||
});
|
||||
|
||||
expect(decodeURIComponent(link.href)).toEqual(
|
||||
|
@ -65,7 +65,7 @@ function getDefaultDataFrame(): DataFrame {
|
||||
overrides: [],
|
||||
},
|
||||
replaceVariables: (value, vars, format) => {
|
||||
return vars && value === '${__value.text}' ? vars['__value'].value.text : value;
|
||||
return vars && value === '${__value.text}' ? '${__value.text} interpolation' : value;
|
||||
},
|
||||
timeZone: 'utc',
|
||||
theme: createTheme(),
|
||||
@ -144,10 +144,10 @@ describe('Table', () => {
|
||||
const rows = within(getTable()).getAllByRole('row');
|
||||
expect(rows).toHaveLength(5);
|
||||
expect(getRowsData(rows)).toEqual([
|
||||
{ time: '2021-01-01 00:00:00', temperature: '10', link: '10' },
|
||||
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: 'NaN' },
|
||||
{ time: '2021-01-01 01:00:00', temperature: '11', link: '11' },
|
||||
{ time: '2021-01-01 02:00:00', temperature: '12', link: '12' },
|
||||
{ time: '2021-01-01 00:00:00', temperature: '10', link: '${__value.text} interpolation' },
|
||||
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: '${__value.text} interpolation' },
|
||||
{ time: '2021-01-01 01:00:00', temperature: '11', link: '${__value.text} interpolation' },
|
||||
{ time: '2021-01-01 02:00:00', temperature: '12', link: '${__value.text} interpolation' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -203,10 +203,10 @@ describe('Table', () => {
|
||||
const rows = within(getTable()).getAllByRole('row');
|
||||
expect(rows).toHaveLength(5);
|
||||
expect(getRowsData(rows)).toEqual([
|
||||
{ time: '2021-01-01 02:00:00', temperature: '12', link: '12' },
|
||||
{ time: '2021-01-01 01:00:00', temperature: '11', link: '11' },
|
||||
{ time: '2021-01-01 00:00:00', temperature: '10', link: '10' },
|
||||
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: 'NaN' },
|
||||
{ time: '2021-01-01 02:00:00', temperature: '12', link: '${__value.text} interpolation' },
|
||||
{ time: '2021-01-01 01:00:00', temperature: '11', link: '${__value.text} interpolation' },
|
||||
{ time: '2021-01-01 00:00:00', temperature: '10', link: '${__value.text} interpolation' },
|
||||
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: '${__value.text} interpolation' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -582,10 +582,10 @@ describe('Table', () => {
|
||||
const rows = within(getTable()).getAllByRole('row');
|
||||
expect(rows).toHaveLength(5);
|
||||
expect(getRowsData(rows)).toEqual([
|
||||
{ time: '2021-01-01 00:00:00', temperature: '10', link: '10' },
|
||||
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: 'NaN' },
|
||||
{ time: '2021-01-01 01:00:00', temperature: '11', link: '11' },
|
||||
{ time: '2021-01-01 02:00:00', temperature: '12', link: '12' },
|
||||
{ time: '2021-01-01 00:00:00', temperature: '10', link: '${__value.text} interpolation' },
|
||||
{ time: '2021-01-01 03:00:00', temperature: 'NaN', link: '${__value.text} interpolation' },
|
||||
{ time: '2021-01-01 01:00:00', temperature: '11', link: '${__value.text} interpolation' },
|
||||
{ time: '2021-01-01 02:00:00', temperature: '12', link: '${__value.text} interpolation' },
|
||||
]);
|
||||
|
||||
await userEvent.click(within(rows[1]).getByLabelText('Expand row'));
|
||||
|
@ -784,7 +784,7 @@ export function queryLogsSample<TQuery extends DataQuery, TOptions extends DataS
|
||||
}
|
||||
|
||||
function getIntervalInfo(scopedVars: ScopedVars, timespanMs: number): { interval: string; intervalMs?: number } {
|
||||
if (scopedVars.__interval) {
|
||||
if (scopedVars.__interval_ms) {
|
||||
let intervalMs: number = scopedVars.__interval_ms.value;
|
||||
let interval = '';
|
||||
// below 5 seconds we force the resolution to be per 1ms as interval in scopedVars is not less than 10ms
|
||||
|
@ -644,8 +644,8 @@ describe('DashboardModel', () => {
|
||||
model.processRepeats();
|
||||
expect(model.panels.filter((x) => x.type === 'row')).toHaveLength(2);
|
||||
expect(model.panels.filter((x) => x.type !== 'row')).toHaveLength(4);
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.dc.value).toBe('dc1');
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.app.value).toBe('se1');
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.dc?.value).toBe('dc1');
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.app?.value).toBe('se1');
|
||||
|
||||
const saveModel = model.getSaveModelClone();
|
||||
expect(saveModel.panels.length).toBe(2);
|
||||
@ -697,15 +697,15 @@ describe('DashboardModel', () => {
|
||||
model.processRepeats();
|
||||
expect(model.panels.filter((x) => x.type === 'row')).toHaveLength(2);
|
||||
expect(model.panels.filter((x) => x.type !== 'row')).toHaveLength(4);
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.dc.value).toBe('dc1');
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.app.value).toBe('se1');
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.dc?.value).toBe('dc1');
|
||||
expect(model.panels.find((x) => x.type !== 'row')?.scopedVars?.app?.value).toBe('se1');
|
||||
|
||||
model.snapshot = { timestamp: new Date() };
|
||||
const saveModel = model.getSaveModelClone();
|
||||
expect(saveModel.panels.filter((x) => x.type === 'row')).toHaveLength(2);
|
||||
expect(saveModel.panels.filter((x) => x.type !== 'row')).toHaveLength(4);
|
||||
expect(saveModel.panels.find((x) => x.type !== 'row')?.scopedVars?.dc.value).toBe('dc1');
|
||||
expect(saveModel.panels.find((x) => x.type !== 'row')?.scopedVars?.app.value).toBe('se1');
|
||||
expect(saveModel.panels.find((x) => x.type !== 'row')?.scopedVars?.dc?.value).toBe('dc1');
|
||||
expect(saveModel.panels.find((x) => x.type !== 'row')?.scopedVars?.app?.value).toBe('se1');
|
||||
|
||||
model.collapseRows();
|
||||
const savedModelWithCollapsedRows = model.getSaveModelClone();
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ComponentClass } from 'react';
|
||||
|
||||
import {
|
||||
DataLinkBuiltInVars,
|
||||
FieldConfigProperty,
|
||||
PanelData,
|
||||
PanelProps,
|
||||
@ -19,7 +18,6 @@ import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
|
||||
import { TemplateSrv } from '../../templating/template_srv';
|
||||
import { variableAdapters } from '../../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../../variables/query/adapter';
|
||||
import { setTimeSrv } from '../services/TimeSrv';
|
||||
import { TimeOverrideResult } from '../utils/panel';
|
||||
|
||||
import { PanelModel } from './PanelModel';
|
||||
@ -27,13 +25,6 @@ import { PanelModel } from './PanelModel';
|
||||
standardFieldConfigEditorRegistry.setInit(() => mockStandardFieldConfigOptions());
|
||||
standardEditorsRegistry.setInit(() => mockStandardFieldConfigOptions());
|
||||
|
||||
setTimeSrv({
|
||||
timeRangeForUrl: () => ({
|
||||
from: 1607687293000,
|
||||
to: 1607687293100,
|
||||
}),
|
||||
} as any);
|
||||
|
||||
const getVariables = () => variablesMock;
|
||||
const getVariableWithName = (name: string) => variablesMock.filter((v) => v.name === name)[0];
|
||||
const getFilteredVariables = jest.fn();
|
||||
@ -211,21 +202,12 @@ describe('PanelModel', () => {
|
||||
bbb: { value: 'BBB', text: 'upperB' },
|
||||
};
|
||||
});
|
||||
|
||||
it('should interpolate variables', () => {
|
||||
const out = model.replaceVariables('hello $aaa');
|
||||
expect(out).toBe('hello AAA');
|
||||
});
|
||||
|
||||
it('should interpolate $__url_time_range variable', () => {
|
||||
const out = model.replaceVariables(`/d/1?$${DataLinkBuiltInVars.keepTime}`);
|
||||
expect(out).toBe('/d/1?from=1607687293000&to=1607687293100');
|
||||
});
|
||||
|
||||
it('should interpolate $__all_variables variable', () => {
|
||||
const out = model.replaceVariables(`/d/1?$${DataLinkBuiltInVars.includeVars}`);
|
||||
expect(out).toBe('/d/1?var-test1=val1&var-test2=val2&var-test3=Value%203&var-test4=A&var-test4=B');
|
||||
});
|
||||
|
||||
it('should prefer the local variable value', () => {
|
||||
const extra = { aaa: { text: '???', value: 'XXX' } };
|
||||
const out = model.replaceVariables('hello $aaa and $bbb', extra);
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
DataConfigSource,
|
||||
DataFrameDTO,
|
||||
DataLink,
|
||||
DataLinkBuiltInVars,
|
||||
DataQuery,
|
||||
DataTransformerConfig,
|
||||
EventBusSrv,
|
||||
@ -13,7 +12,6 @@ import {
|
||||
PanelPlugin,
|
||||
PanelPluginDataSupport,
|
||||
ScopedVars,
|
||||
urlUtil,
|
||||
PanelModel as IPanelModel,
|
||||
DataSourceRef,
|
||||
CoreApp,
|
||||
@ -36,8 +34,6 @@ import {
|
||||
} from 'app/types/events';
|
||||
|
||||
import { PanelQueryRunner } from '../../query/state/PanelQueryRunner';
|
||||
import { getVariablesUrlParams } from '../../variables/getAllVariableValuesForUrl';
|
||||
import { getTimeSrv } from '../services/TimeSrv';
|
||||
import { TimeOverrideResult } from '../utils/panel';
|
||||
|
||||
export interface GridPos {
|
||||
@ -639,23 +635,6 @@ export class PanelModel implements DataConfigSource, IPanelModel {
|
||||
replaceVariables(value: string, extraVars: ScopedVars | undefined, format?: string | Function) {
|
||||
const lastRequest = this.getQueryRunner().getLastRequest();
|
||||
const vars: ScopedVars = Object.assign({}, this.scopedVars, lastRequest?.scopedVars, extraVars);
|
||||
|
||||
const allVariablesParams = getVariablesUrlParams(vars);
|
||||
const variablesQuery = urlUtil.toUrlParams(allVariablesParams);
|
||||
const timeRangeUrl = urlUtil.toUrlParams(getTimeSrv().timeRangeForUrl());
|
||||
|
||||
vars[DataLinkBuiltInVars.keepTime] = {
|
||||
text: timeRangeUrl,
|
||||
value: timeRangeUrl,
|
||||
skipFormat: true,
|
||||
};
|
||||
|
||||
vars[DataLinkBuiltInVars.includeVars] = {
|
||||
text: variablesQuery,
|
||||
value: variablesQuery,
|
||||
skipFormat: true,
|
||||
};
|
||||
|
||||
return getTemplateSrv().replace(value, vars, format);
|
||||
}
|
||||
|
||||
|
@ -168,9 +168,9 @@ describe('PanelQueryRunner', () => {
|
||||
});
|
||||
|
||||
it('should pass scopedVars to datasource with interval props', async () => {
|
||||
expect(ctx.queryCalledWith?.scopedVars.server.text).toBe('Server1');
|
||||
expect(ctx.queryCalledWith?.scopedVars.__interval.text).toBe('5m');
|
||||
expect(ctx.queryCalledWith?.scopedVars.__interval_ms.text).toBe('300000');
|
||||
expect(ctx.queryCalledWith?.scopedVars.server!.text).toBe('Server1');
|
||||
expect(ctx.queryCalledWith?.scopedVars.__interval!.text).toBe('5m');
|
||||
expect(ctx.queryCalledWith?.scopedVars.__interval_ms!.text).toBe('300000');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -26,24 +26,15 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> {
|
||||
public static Component = DashboardSceneRenderer;
|
||||
private urlSyncManager?: UrlSyncManager;
|
||||
|
||||
public activate() {
|
||||
super.activate();
|
||||
}
|
||||
|
||||
/**
|
||||
* It's better to do this before activate / mount to not trigger unnessary re-renders
|
||||
*/
|
||||
public initUrlSync() {
|
||||
this.urlSyncManager = new UrlSyncManager(this);
|
||||
this.urlSyncManager.initSync();
|
||||
}
|
||||
|
||||
public deactivate() {
|
||||
super.deactivate();
|
||||
|
||||
if (this.urlSyncManager) {
|
||||
this.urlSyncManager!.cleanUp();
|
||||
if (!this.urlSyncManager) {
|
||||
this.urlSyncManager = new UrlSyncManager(this);
|
||||
}
|
||||
|
||||
this.urlSyncManager.initSync();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ describe('SearchResultsTable', () => {
|
||||
overrides: [],
|
||||
},
|
||||
replaceVariables: (value, vars, format) => {
|
||||
return vars && value === '${__value.text}' ? vars['__value'].value.text : value;
|
||||
return vars && value === '${__value.text}' ? vars['__value']!.value.text : value;
|
||||
},
|
||||
theme: createTheme(),
|
||||
});
|
||||
|
86
public/app/features/templating/dataMacros.test.ts
Normal file
86
public/app/features/templating/dataMacros.test.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { initTemplateSrv } from 'test/helpers/initTemplateSrv';
|
||||
|
||||
import { DataContextScopedVar, FieldType, toDataFrame } from '@grafana/data';
|
||||
import { TemplateSrv } from '@grafana/runtime';
|
||||
|
||||
describe('templateSrv', () => {
|
||||
let _templateSrv: TemplateSrv;
|
||||
|
||||
beforeEach(() => {
|
||||
_templateSrv = initTemplateSrv('hello', []);
|
||||
});
|
||||
|
||||
const data = toDataFrame({
|
||||
name: 'A',
|
||||
fields: [
|
||||
{
|
||||
name: 'number',
|
||||
type: FieldType.number,
|
||||
values: [5, 10],
|
||||
display: (value: number) => {
|
||||
return { text: value.toString(), numeric: value, suffix: '%' };
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'time',
|
||||
type: FieldType.time,
|
||||
values: [5000, 10000],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
it('Should interpolate __value.* expressions with dataContext in scopedVars', () => {
|
||||
const dataContext: DataContextScopedVar = {
|
||||
value: {
|
||||
frame: data,
|
||||
field: data.fields[0],
|
||||
rowIndex: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const scopedVars = { __dataContext: dataContext };
|
||||
|
||||
expect(_templateSrv.replace('${__value.raw}', scopedVars)).toBe('10');
|
||||
expect(_templateSrv.replace('${__value.numeric}', scopedVars)).toBe('10');
|
||||
expect(_templateSrv.replace('${__value}', scopedVars)).toBe('10%');
|
||||
expect(_templateSrv.replace('${__value.text}', scopedVars)).toBe('10');
|
||||
expect(_templateSrv.replace('${__value.time}', scopedVars)).toBe('10000');
|
||||
// can apply format as well
|
||||
expect(_templateSrv.replace('${__value:percentencode}', scopedVars)).toBe('10%25');
|
||||
});
|
||||
|
||||
it('Should interpolate __value.* with calculatedValue', () => {
|
||||
const dataContext: DataContextScopedVar = {
|
||||
value: {
|
||||
frame: data,
|
||||
field: data.fields[0],
|
||||
calculatedValue: {
|
||||
text: '15',
|
||||
numeric: 15,
|
||||
suffix: '%',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const scopedVars = { __dataContext: dataContext };
|
||||
|
||||
expect(_templateSrv.replace('${__value.raw}', scopedVars)).toBe('15');
|
||||
expect(_templateSrv.replace('${__value.numeric}', scopedVars)).toBe('15');
|
||||
expect(_templateSrv.replace('${__value}', scopedVars)).toBe('15%');
|
||||
expect(_templateSrv.replace('${__value.text}', scopedVars)).toBe('15%');
|
||||
expect(_templateSrv.replace('${__value.time}', scopedVars)).toBe('');
|
||||
});
|
||||
|
||||
it('Should return match when ${__value.*} is used and no dataContext or rowIndex is found', () => {
|
||||
const dataContext: DataContextScopedVar = {
|
||||
value: {
|
||||
frame: data,
|
||||
field: data.fields[0],
|
||||
},
|
||||
};
|
||||
|
||||
const scopedVars = { __dataContext: dataContext };
|
||||
|
||||
expect(_templateSrv.replace('${__value.raw}', scopedVars)).toBe('${__value.raw}');
|
||||
});
|
||||
});
|
76
public/app/features/templating/dataMacros.ts
Normal file
76
public/app/features/templating/dataMacros.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { DisplayProcessor, FieldType, formattedValueToString, getDisplayProcessor, ScopedVars } from '@grafana/data';
|
||||
import { VariableCustomFormatterFn } from '@grafana/scenes';
|
||||
|
||||
import { formatVariableValue } from './formatVariableValue';
|
||||
|
||||
/**
|
||||
* ${__value.raw/nummeric/text/time} macro
|
||||
*/
|
||||
export function valueMacro(
|
||||
match: string,
|
||||
fieldPath?: string,
|
||||
scopedVars?: ScopedVars,
|
||||
format?: string | VariableCustomFormatterFn
|
||||
) {
|
||||
const value = getValueForValueMacro(match, fieldPath, scopedVars);
|
||||
return formatVariableValue(value, format);
|
||||
}
|
||||
|
||||
function getValueForValueMacro(match: string, fieldPath?: string, scopedVars?: ScopedVars) {
|
||||
const dataContext = scopedVars?.__dataContext;
|
||||
if (!dataContext) {
|
||||
return match;
|
||||
}
|
||||
|
||||
const { frame, rowIndex, field, calculatedValue } = dataContext.value;
|
||||
|
||||
if (calculatedValue) {
|
||||
switch (fieldPath) {
|
||||
case 'numeric':
|
||||
return calculatedValue.numeric.toString();
|
||||
case 'raw':
|
||||
return calculatedValue.numeric;
|
||||
case 'time':
|
||||
return '';
|
||||
case 'text':
|
||||
default:
|
||||
return formattedValueToString(calculatedValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (rowIndex === undefined) {
|
||||
return match;
|
||||
}
|
||||
|
||||
if (fieldPath === 'time') {
|
||||
const timeField = frame.fields.find((f) => f.type === FieldType.time);
|
||||
return timeField ? timeField.values.get(rowIndex) : undefined;
|
||||
}
|
||||
|
||||
const value = field.values.get(rowIndex);
|
||||
if (fieldPath === 'raw') {
|
||||
return value;
|
||||
}
|
||||
|
||||
const displayProcessor = field.display ?? getFallbackDisplayProcessor();
|
||||
const result = displayProcessor(value);
|
||||
|
||||
switch (fieldPath) {
|
||||
case 'numeric':
|
||||
return result.numeric;
|
||||
case 'text':
|
||||
return result.text;
|
||||
default:
|
||||
return formattedValueToString(result);
|
||||
}
|
||||
}
|
||||
|
||||
let fallbackDisplayProcessor: DisplayProcessor | undefined;
|
||||
|
||||
function getFallbackDisplayProcessor() {
|
||||
if (!fallbackDisplayProcessor) {
|
||||
fallbackDisplayProcessor = getDisplayProcessor();
|
||||
}
|
||||
|
||||
return fallbackDisplayProcessor;
|
||||
}
|
105
public/app/features/templating/formatVariableValue.test.ts
Normal file
105
public/app/features/templating/formatVariableValue.test.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { silenceConsoleOutput } from 'test/core/utils/silenceConsoleOutput';
|
||||
|
||||
import { VariableFormatID } from '@grafana/schema';
|
||||
|
||||
import { formatVariableValue } from './formatVariableValue';
|
||||
|
||||
describe('format variable to string values', () => {
|
||||
silenceConsoleOutput();
|
||||
|
||||
it('single value should return value', () => {
|
||||
const result = formatVariableValue('test');
|
||||
expect(result).toBe('test');
|
||||
});
|
||||
|
||||
it('should use glob format when unknown format provided', () => {
|
||||
let result = formatVariableValue('test', 'nonexistentformat');
|
||||
expect(result).toBe('test');
|
||||
result = formatVariableValue(['test', 'test1'], 'nonexistentformat');
|
||||
expect(result).toBe('{test,test1}');
|
||||
});
|
||||
|
||||
it('multi value and glob format should render glob string', () => {
|
||||
const result = formatVariableValue(['test', 'test2'], 'glob');
|
||||
expect(result).toBe('{test,test2}');
|
||||
});
|
||||
|
||||
it('multi value and lucene should render as lucene expr', () => {
|
||||
const result = formatVariableValue(['test', 'test2'], 'lucene');
|
||||
expect(result).toBe('("test" OR "test2")');
|
||||
});
|
||||
|
||||
it('multi value and regex format should render regex string', () => {
|
||||
const result = formatVariableValue(['test.', 'test2'], 'regex');
|
||||
expect(result).toBe('(test\\.|test2)');
|
||||
});
|
||||
|
||||
it('multi value and pipe should render pipe string', () => {
|
||||
const result = formatVariableValue(['test', 'test2'], 'pipe');
|
||||
expect(result).toBe('test|test2');
|
||||
});
|
||||
|
||||
it('multi value and distributed should render distributed string', () => {
|
||||
const result = formatVariableValue(['test', 'test2'], 'distributed', {
|
||||
name: 'build',
|
||||
});
|
||||
expect(result).toBe('test,build=test2');
|
||||
});
|
||||
|
||||
it('multi value and distributed should render when not string', () => {
|
||||
const result = formatVariableValue(['test'], 'distributed', {
|
||||
name: 'build',
|
||||
});
|
||||
expect(result).toBe('test');
|
||||
});
|
||||
|
||||
it('multi value and csv format should render csv string', () => {
|
||||
const result = formatVariableValue(['test', 'test2'], VariableFormatID.CSV);
|
||||
expect(result).toBe('test,test2');
|
||||
});
|
||||
|
||||
it('multi value and percentencode format should render percent-encoded string', () => {
|
||||
const result = formatVariableValue(['foo()bar BAZ', 'test2'], VariableFormatID.PercentEncode);
|
||||
expect(result).toBe('%7Bfoo%28%29bar%20BAZ%2Ctest2%7D');
|
||||
});
|
||||
|
||||
it('slash should be properly escaped in regex format', () => {
|
||||
const result = formatVariableValue('Gi3/14', 'regex');
|
||||
expect(result).toBe('Gi3\\/14');
|
||||
});
|
||||
|
||||
it('single value and singlequote format should render string with value enclosed in single quotes', () => {
|
||||
const result = formatVariableValue('test', 'singlequote');
|
||||
expect(result).toBe("'test'");
|
||||
});
|
||||
|
||||
it('multi value and singlequote format should render string with values enclosed in single quotes', () => {
|
||||
const result = formatVariableValue(['test', "test'2"], 'singlequote');
|
||||
expect(result).toBe("'test','test\\'2'");
|
||||
});
|
||||
|
||||
it('single value and doublequote format should render string with value enclosed in double quotes', () => {
|
||||
const result = formatVariableValue('test', 'doublequote');
|
||||
expect(result).toBe('"test"');
|
||||
});
|
||||
|
||||
it('multi value and doublequote format should render string with values enclosed in double quotes', () => {
|
||||
const result = formatVariableValue(['test', 'test"2'], 'doublequote');
|
||||
expect(result).toBe('"test","test\\"2"');
|
||||
});
|
||||
|
||||
it('single value and sqlstring format should render string with value enclosed in single quotes', () => {
|
||||
const result = formatVariableValue("test'value", 'sqlstring');
|
||||
expect(result).toBe(`'test''value'`);
|
||||
});
|
||||
|
||||
it('multi value and sqlstring format should render string with values enclosed in single quotes', () => {
|
||||
const result = formatVariableValue(['test', "test'value2"], 'sqlstring');
|
||||
expect(result).toBe(`'test','test''value2'`);
|
||||
});
|
||||
|
||||
it('raw format should leave value intact and do no escaping', () => {
|
||||
const result = formatVariableValue("'test\n", 'raw');
|
||||
expect(result).toBe("'test\n");
|
||||
});
|
||||
});
|
50
public/app/features/templating/formatVariableValue.ts
Normal file
50
public/app/features/templating/formatVariableValue.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { formatRegistry, FormatRegistryID } from '@grafana/scenes';
|
||||
|
||||
import { isAdHoc } from '../variables/guard';
|
||||
|
||||
import { getVariableWrapper } from './LegacyVariableWrapper';
|
||||
|
||||
export function formatVariableValue(value: any, format?: any, variable?: any, text?: string): string {
|
||||
// for some scopedVars there is no variable
|
||||
variable = variable || {};
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isAdHoc(variable) && format !== FormatRegistryID.queryParam) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// if it's an object transform value to string
|
||||
if (!Array.isArray(value) && typeof value === 'object') {
|
||||
value = `${value}`;
|
||||
}
|
||||
|
||||
if (typeof format === 'function') {
|
||||
return format(value, variable, formatVariableValue);
|
||||
}
|
||||
|
||||
if (!format) {
|
||||
format = FormatRegistryID.glob;
|
||||
}
|
||||
|
||||
// some formats have arguments that come after ':' character
|
||||
let args = format.split(':');
|
||||
if (args.length > 1) {
|
||||
format = args[0];
|
||||
args = args.slice(1);
|
||||
} else {
|
||||
args = [];
|
||||
}
|
||||
|
||||
let formatItem = formatRegistry.getIfExists(format);
|
||||
|
||||
if (!formatItem) {
|
||||
console.error(`Variable format ${format} not found. Using glob format as fallback.`);
|
||||
formatItem = formatRegistry.get(FormatRegistryID.glob);
|
||||
}
|
||||
|
||||
const formatVariable = getVariableWrapper(variable, value, text ?? value);
|
||||
return formatItem.formatter(value, args, formatVariable);
|
||||
}
|
54
public/app/features/templating/macroRegistry.test.ts
Normal file
54
public/app/features/templating/macroRegistry.test.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { initTemplateSrv } from 'test/helpers/initTemplateSrv';
|
||||
|
||||
import { DataLinkBuiltInVars } from '@grafana/data';
|
||||
import { getTemplateSrv, setTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { setTimeSrv } from '../dashboard/services/TimeSrv';
|
||||
import { variableAdapters } from '../variables/adapters';
|
||||
import { createQueryVariableAdapter } from '../variables/query/adapter';
|
||||
|
||||
describe('__all_variables', () => {
|
||||
beforeAll(() => {
|
||||
variableAdapters.register(createQueryVariableAdapter());
|
||||
|
||||
setTemplateSrv(
|
||||
initTemplateSrv('hello', [
|
||||
{
|
||||
type: 'query',
|
||||
name: 'test',
|
||||
rootStateKey: 'hello',
|
||||
current: { value: ['val1', 'val2'] },
|
||||
getValueForUrl: function () {
|
||||
return this.current.value;
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should interpolate correctly', () => {
|
||||
const out = getTemplateSrv().replace(`/d/1?$${DataLinkBuiltInVars.includeVars}`);
|
||||
expect(out).toBe('/d/1?var-test=val1&var-test=val2');
|
||||
});
|
||||
|
||||
it('should interpolate and take scopedVars into account', () => {
|
||||
const out = getTemplateSrv().replace(`/d/1?$${DataLinkBuiltInVars.includeVars}`, { test: { value: 'val3' } });
|
||||
expect(out).toBe('/d/1?var-test=val3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('__url_time_range', () => {
|
||||
beforeAll(() => {
|
||||
setTimeSrv({
|
||||
timeRangeForUrl: () => ({
|
||||
from: 1607687293000,
|
||||
to: 1607687293100,
|
||||
}),
|
||||
} as any);
|
||||
});
|
||||
|
||||
it('should interpolate to url params', () => {
|
||||
const out = getTemplateSrv().replace(`/d/1?$${DataLinkBuiltInVars.keepTime}`);
|
||||
expect(out).toBe('/d/1?from=1607687293000&to=1607687293100');
|
||||
});
|
||||
});
|
22
public/app/features/templating/macroRegistry.ts
Normal file
22
public/app/features/templating/macroRegistry.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { DataLinkBuiltInVars, ScopedVars, urlUtil } from '@grafana/data';
|
||||
|
||||
import { getTimeSrv } from '../dashboard/services/TimeSrv';
|
||||
import { getVariablesUrlParams } from '../variables/getAllVariableValuesForUrl';
|
||||
|
||||
import { valueMacro } from './dataMacros';
|
||||
import { MacroHandler } from './types';
|
||||
|
||||
export const macroRegistry: Record<string, MacroHandler> = {
|
||||
['__value']: valueMacro,
|
||||
[DataLinkBuiltInVars.includeVars]: includeVarsMacro,
|
||||
[DataLinkBuiltInVars.keepTime]: urlTimeRangeMacro,
|
||||
};
|
||||
|
||||
function includeVarsMacro(match: string, fieldPath?: string, scopedVars?: ScopedVars) {
|
||||
const allVariablesParams = getVariablesUrlParams(scopedVars);
|
||||
return urlUtil.toUrlParams(allVariablesParams);
|
||||
}
|
||||
|
||||
function urlTimeRangeMacro() {
|
||||
return urlUtil.toUrlParams(getTimeSrv().timeRangeForUrl());
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { dateTime, TimeRange } from '@grafana/data';
|
||||
import { setDataSourceSrv, VariableInterpolation } from '@grafana/runtime';
|
||||
import { FormatRegistryID, TestVariable } from '@grafana/scenes';
|
||||
import { VariableFormatID } from '@grafana/schema';
|
||||
|
||||
import { silenceConsoleOutput } from '../../../test/core/utils/silenceConsoleOutput';
|
||||
import { initTemplateSrv } from '../../../test/helpers/initTemplateSrv';
|
||||
@ -424,104 +423,6 @@ describe('templateSrv', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('format variable to string values', () => {
|
||||
it('single value should return value', () => {
|
||||
const result = _templateSrv.formatValue('test');
|
||||
expect(result).toBe('test');
|
||||
});
|
||||
|
||||
it('should use glob format when unknown format provided', () => {
|
||||
let result = _templateSrv.formatValue('test', 'nonexistentformat');
|
||||
expect(result).toBe('test');
|
||||
result = _templateSrv.formatValue(['test', 'test1'], 'nonexistentformat');
|
||||
expect(result).toBe('{test,test1}');
|
||||
});
|
||||
|
||||
it('multi value and glob format should render glob string', () => {
|
||||
const result = _templateSrv.formatValue(['test', 'test2'], 'glob');
|
||||
expect(result).toBe('{test,test2}');
|
||||
});
|
||||
|
||||
it('multi value and lucene should render as lucene expr', () => {
|
||||
const result = _templateSrv.formatValue(['test', 'test2'], 'lucene');
|
||||
expect(result).toBe('("test" OR "test2")');
|
||||
});
|
||||
|
||||
it('multi value and regex format should render regex string', () => {
|
||||
const result = _templateSrv.formatValue(['test.', 'test2'], 'regex');
|
||||
expect(result).toBe('(test\\.|test2)');
|
||||
});
|
||||
|
||||
it('multi value and pipe should render pipe string', () => {
|
||||
const result = _templateSrv.formatValue(['test', 'test2'], 'pipe');
|
||||
expect(result).toBe('test|test2');
|
||||
});
|
||||
|
||||
it('multi value and distributed should render distributed string', () => {
|
||||
const result = _templateSrv.formatValue(['test', 'test2'], 'distributed', {
|
||||
name: 'build',
|
||||
});
|
||||
expect(result).toBe('test,build=test2');
|
||||
});
|
||||
|
||||
it('multi value and distributed should render when not string', () => {
|
||||
const result = _templateSrv.formatValue(['test'], 'distributed', {
|
||||
name: 'build',
|
||||
});
|
||||
expect(result).toBe('test');
|
||||
});
|
||||
|
||||
it('multi value and csv format should render csv string', () => {
|
||||
const result = _templateSrv.formatValue(['test', 'test2'], VariableFormatID.CSV);
|
||||
expect(result).toBe('test,test2');
|
||||
});
|
||||
|
||||
it('multi value and percentencode format should render percent-encoded string', () => {
|
||||
const result = _templateSrv.formatValue(['foo()bar BAZ', 'test2'], VariableFormatID.PercentEncode);
|
||||
expect(result).toBe('%7Bfoo%28%29bar%20BAZ%2Ctest2%7D');
|
||||
});
|
||||
|
||||
it('slash should be properly escaped in regex format', () => {
|
||||
const result = _templateSrv.formatValue('Gi3/14', 'regex');
|
||||
expect(result).toBe('Gi3\\/14');
|
||||
});
|
||||
|
||||
it('single value and singlequote format should render string with value enclosed in single quotes', () => {
|
||||
const result = _templateSrv.formatValue('test', 'singlequote');
|
||||
expect(result).toBe("'test'");
|
||||
});
|
||||
|
||||
it('multi value and singlequote format should render string with values enclosed in single quotes', () => {
|
||||
const result = _templateSrv.formatValue(['test', "test'2"], 'singlequote');
|
||||
expect(result).toBe("'test','test\\'2'");
|
||||
});
|
||||
|
||||
it('single value and doublequote format should render string with value enclosed in double quotes', () => {
|
||||
const result = _templateSrv.formatValue('test', 'doublequote');
|
||||
expect(result).toBe('"test"');
|
||||
});
|
||||
|
||||
it('multi value and doublequote format should render string with values enclosed in double quotes', () => {
|
||||
const result = _templateSrv.formatValue(['test', 'test"2'], 'doublequote');
|
||||
expect(result).toBe('"test","test\\"2"');
|
||||
});
|
||||
|
||||
it('single value and sqlstring format should render string with value enclosed in single quotes', () => {
|
||||
const result = _templateSrv.formatValue("test'value", 'sqlstring');
|
||||
expect(result).toBe(`'test''value'`);
|
||||
});
|
||||
|
||||
it('multi value and sqlstring format should render string with values enclosed in single quotes', () => {
|
||||
const result = _templateSrv.formatValue(['test', "test'value2"], 'sqlstring');
|
||||
expect(result).toBe(`'test','test''value2'`);
|
||||
});
|
||||
|
||||
it('raw format should leave value intact and do no escaping', () => {
|
||||
const result = _templateSrv.formatValue("'test\n", 'raw');
|
||||
expect(result).toBe("'test\n");
|
||||
});
|
||||
});
|
||||
|
||||
describe('can check if variable exists', () => {
|
||||
beforeEach(() => {
|
||||
_templateSrv = initTemplateSrv(key, [{ type: 'query', name: 'test', current: { value: 'oogle' } }]);
|
||||
@ -747,6 +648,7 @@ describe('templateSrv', () => {
|
||||
let passedValue: string | null = null;
|
||||
_templateSrv.replace('this.${test}.filters', {}, (value: string) => {
|
||||
passedValue = value;
|
||||
return '';
|
||||
});
|
||||
|
||||
expect(passedValue).toBe('[object Object]');
|
||||
@ -763,6 +665,7 @@ describe('templateSrv', () => {
|
||||
let passedValue: string | null = null;
|
||||
_templateSrv.replace('this.${test}.filters', {}, (value: string) => {
|
||||
passedValue = value;
|
||||
return '';
|
||||
});
|
||||
|
||||
expect(passedValue).toBe('hello');
|
||||
@ -904,15 +807,6 @@ describe('templateSrv', () => {
|
||||
const target = _templateSrv.replace('${adhoc}', { adhoc: { value: 'value2', text: 'value2' } }, 'queryparam');
|
||||
expect(target).toBe('var-adhoc=value2');
|
||||
});
|
||||
|
||||
it('Variable named ${__all_variables} is already formatted so skip any formatting', () => {
|
||||
const target = _templateSrv.replace(
|
||||
'${__all_variables}',
|
||||
{ __all_variables: { value: 'var-server=server+name+with+plus%2B', skipFormat: true } },
|
||||
'percentencode'
|
||||
);
|
||||
expect(target).toBe('var-server=server+name+with+plus%2B');
|
||||
});
|
||||
});
|
||||
|
||||
describe('scenes compatibility', () => {
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
AdHocVariableFilter,
|
||||
AdHocVariableModel,
|
||||
TypedVariableModel,
|
||||
ScopedVar,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
getDataSourceSrv,
|
||||
@ -14,7 +15,7 @@ import {
|
||||
TemplateSrv as BaseTemplateSrv,
|
||||
VariableInterpolation,
|
||||
} from '@grafana/runtime';
|
||||
import { sceneGraph, FormatRegistryID, formatRegistry, VariableCustomFormatterFn } from '@grafana/scenes';
|
||||
import { sceneGraph, FormatRegistryID, VariableCustomFormatterFn } from '@grafana/scenes';
|
||||
|
||||
import { variableAdapters } from '../variables/adapters';
|
||||
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from '../variables/constants';
|
||||
@ -22,7 +23,8 @@ import { isAdHoc } from '../variables/guard';
|
||||
import { getFilteredVariables, getVariables, getVariableWithName } from '../variables/state/selectors';
|
||||
import { variableRegex } from '../variables/utils';
|
||||
|
||||
import { getVariableWrapper } from './LegacyVariableWrapper';
|
||||
import { formatVariableValue } from './formatVariableValue';
|
||||
import { macroRegistry } from './macroRegistry';
|
||||
|
||||
interface FieldAccessorCache {
|
||||
[key: string]: (obj: any) => any;
|
||||
@ -135,51 +137,6 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
return filters;
|
||||
}
|
||||
|
||||
formatValue(value: any, format?: any, variable?: any, text?: string): string {
|
||||
// for some scopedVars there is no variable
|
||||
variable = variable || {};
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (isAdHoc(variable) && format !== FormatRegistryID.queryParam) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// if it's an object transform value to string
|
||||
if (!Array.isArray(value) && typeof value === 'object') {
|
||||
value = `${value}`;
|
||||
}
|
||||
|
||||
if (typeof format === 'function') {
|
||||
return format(value, variable, this.formatValue);
|
||||
}
|
||||
|
||||
if (!format) {
|
||||
format = FormatRegistryID.glob;
|
||||
}
|
||||
|
||||
// some formats have arguments that come after ':' character
|
||||
let args = format.split(':');
|
||||
if (args.length > 1) {
|
||||
format = args[0];
|
||||
args = args.slice(1);
|
||||
} else {
|
||||
args = [];
|
||||
}
|
||||
|
||||
let formatItem = formatRegistry.getIfExists(format);
|
||||
|
||||
if (!formatItem) {
|
||||
console.error(`Variable format ${format} not found. Using glob format as fallback.`);
|
||||
formatItem = formatRegistry.get(FormatRegistryID.glob);
|
||||
}
|
||||
|
||||
const formatVariable = getVariableWrapper(variable, value, text ?? value);
|
||||
return formatItem.formatter(value, args, formatVariable);
|
||||
}
|
||||
|
||||
setGrafanaVariable(name: string, value: any) {
|
||||
this.grafanaVariables.set(name, value);
|
||||
}
|
||||
@ -257,12 +214,7 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
return (this.fieldAccessorCache[fieldPath] = property(fieldPath));
|
||||
}
|
||||
|
||||
private getVariableValue(variableName: string, fieldPath: string | undefined, scopedVars: ScopedVars) {
|
||||
const scopedVar = scopedVars[variableName];
|
||||
if (!scopedVar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private getVariableValue(scopedVar: ScopedVar, fieldPath: string | undefined) {
|
||||
if (fieldPath) {
|
||||
return this.getFieldAccessor(fieldPath)(scopedVar.value);
|
||||
}
|
||||
@ -270,13 +222,7 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
return scopedVar.value;
|
||||
}
|
||||
|
||||
private getVariableText(variableName: string, value: any, scopedVars: ScopedVars) {
|
||||
const scopedVar = scopedVars[variableName];
|
||||
|
||||
if (!scopedVar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private getVariableText(scopedVar: ScopedVar, value: any) {
|
||||
if (scopedVar.value === value || typeof value !== 'string') {
|
||||
return scopedVar.text;
|
||||
}
|
||||
@ -287,7 +233,7 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
replace(
|
||||
target?: string,
|
||||
scopedVars?: ScopedVars,
|
||||
format?: string | Function,
|
||||
format?: string | Function | undefined,
|
||||
interpolations?: VariableInterpolation[]
|
||||
): string {
|
||||
if (scopedVars && scopedVars.__sceneObject) {
|
||||
@ -321,25 +267,26 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
match: string,
|
||||
variableName: string,
|
||||
fieldPath: string,
|
||||
format: string | Function | undefined,
|
||||
format: string | VariableCustomFormatterFn | undefined,
|
||||
scopedVars: ScopedVars | undefined
|
||||
) {
|
||||
const variable = this.getVariableAtIndex(variableName);
|
||||
const scopedVar = scopedVars?.[variableName];
|
||||
|
||||
if (scopedVars) {
|
||||
const value = this.getVariableValue(variableName, fieldPath, scopedVars);
|
||||
const text = this.getVariableText(variableName, value, scopedVars);
|
||||
if (scopedVar) {
|
||||
const value = this.getVariableValue(scopedVar, fieldPath);
|
||||
const text = this.getVariableText(scopedVar, value);
|
||||
|
||||
if (value !== null && value !== undefined) {
|
||||
if (scopedVars[variableName]?.skipFormat) {
|
||||
format = undefined;
|
||||
}
|
||||
|
||||
return this.formatValue(value, format, variable, text);
|
||||
return formatVariableValue(value, format, variable, text);
|
||||
}
|
||||
}
|
||||
|
||||
if (!variable) {
|
||||
if (macroRegistry[variableName]) {
|
||||
return macroRegistry[variableName](match, fieldPath, scopedVars, format);
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
@ -347,12 +294,12 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
const value = variableAdapters.get(variable.type).getValueForUrl(variable);
|
||||
const text = isAdHoc(variable) ? variable.id : variable.current.text;
|
||||
|
||||
return this.formatValue(value, format, variable, text);
|
||||
return formatVariableValue(value, format, variable, text);
|
||||
}
|
||||
|
||||
const systemValue = this.grafanaVariables.get(variable.current.value);
|
||||
if (systemValue) {
|
||||
return this.formatValue(systemValue, format, variable);
|
||||
return formatVariableValue(systemValue, format, variable);
|
||||
}
|
||||
|
||||
let value = variable.current.value;
|
||||
@ -368,15 +315,13 @@ export class TemplateSrv implements BaseTemplateSrv {
|
||||
}
|
||||
|
||||
if (fieldPath) {
|
||||
const fieldValue = this.getVariableValue(variableName, fieldPath, {
|
||||
[variableName]: { value, text },
|
||||
});
|
||||
const fieldValue = this.getVariableValue({ value, text }, fieldPath);
|
||||
if (fieldValue !== null && fieldValue !== undefined) {
|
||||
return this.formatValue(fieldValue, format, variable, text);
|
||||
return formatVariableValue(fieldValue, format, variable, text);
|
||||
}
|
||||
}
|
||||
|
||||
return this.formatValue(value, format, variable, text);
|
||||
return formatVariableValue(value, format, variable, text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
11
public/app/features/templating/types.ts
Normal file
11
public/app/features/templating/types.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ScopedVars } from '@grafana/data';
|
||||
import { VariableCustomFormatterFn } from '@grafana/scenes';
|
||||
|
||||
export interface MacroHandler {
|
||||
(
|
||||
match: string,
|
||||
fieldPath: string | undefined,
|
||||
scopedVars: ScopedVars | undefined,
|
||||
format: string | VariableCustomFormatterFn | undefined
|
||||
): string;
|
||||
}
|
@ -101,13 +101,15 @@ describe('getAllVariableValuesForUrl', () => {
|
||||
describe('fillVariableValuesForUrl with multi value, scopedVars and skip url sync', () => {
|
||||
beforeEach(() => {
|
||||
setTemplateSrv(
|
||||
initTemplateSrv(key, [{ type: 'query', name: 'test', rootStateKey: key, current: { value: ['val1', 'val2'] } }])
|
||||
initTemplateSrv(key, [
|
||||
{ type: 'query', name: 'test', rootStateKey: key, current: { value: ['val1', 'val2'] }, skipUrlSync: true },
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it('should not set scoped value as url params', () => {
|
||||
const params = getVariablesUrlParams({
|
||||
test: { value: 'val1', text: 'val1text', skipUrlSync: true },
|
||||
test: { value: 'val1', text: 'val1text' },
|
||||
});
|
||||
expect(params['var-test']).toBe(undefined);
|
||||
});
|
||||
|
@ -10,16 +10,15 @@ export function getVariablesUrlParams(scopedVars?: ScopedVars): UrlQueryMap {
|
||||
|
||||
for (let i = 0; i < variables.length; i++) {
|
||||
const variable = variables[i];
|
||||
if (scopedVars && scopedVars[variable.name] !== void 0) {
|
||||
if (scopedVars[variable.name].skipUrlSync) {
|
||||
continue;
|
||||
}
|
||||
params[VARIABLE_PREFIX + variable.name] = scopedVars[variable.name].value;
|
||||
const scopedVar = scopedVars && scopedVars[variable.name];
|
||||
|
||||
if (variable.skipUrlSync) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scopedVar) {
|
||||
params[VARIABLE_PREFIX + variable.name] = scopedVar.value;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
if (variable.skipUrlSync) {
|
||||
continue;
|
||||
}
|
||||
params[VARIABLE_PREFIX + variable.name] = variableAdapters.get(variable.type).getValueForUrl(variable as any);
|
||||
}
|
||||
}
|
||||
|
@ -193,10 +193,7 @@ export class TestDataDataSource extends DataSourceWithBackend<TestData> {
|
||||
|
||||
variablesQuery(target: TestData, options: DataQueryRequest<TestData>): Observable<DataQueryResponse> {
|
||||
const query = target.stringInput ?? '';
|
||||
const interpolatedQuery = this.templateSrv.replace(
|
||||
query,
|
||||
getSearchFilterScopedVar({ query, wildcardChar: '*', options: options.scopedVars })
|
||||
);
|
||||
const interpolatedQuery = this.templateSrv.replace(query, getSearchFilterScopedVar({ query, wildcardChar: '*' }));
|
||||
const children = queryMetricTree(interpolatedQuery);
|
||||
const items = children.map((item) => ({ value: item.name, text: item.name }));
|
||||
const dataFrame = new ArrayDataFrame(items);
|
||||
|
@ -18,7 +18,7 @@ const templateSrv = {
|
||||
if (scopedVars) {
|
||||
// For testing variables replacement in link
|
||||
each(scopedVars, (val, key) => {
|
||||
value = value.replace('$' + key, val.value);
|
||||
value = value.replace('$' + key, val?.value);
|
||||
});
|
||||
}
|
||||
return value;
|
||||
|
Loading…
Reference in New Issue
Block a user