mirror of https://github.com/grafana/grafana
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 * fixpull/65475/head
parent
2b73f8cfd5
commit
b7b608418d
@ -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": "" |
||||
} |
||||
@ -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; |
||||
}; |
||||
} |
||||
|
||||
@ -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}'); |
||||
}); |
||||
}); |
||||
@ -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; |
||||
} |
||||
@ -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"); |
||||
}); |
||||
}); |
||||
@ -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); |
||||
} |
||||
@ -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'); |
||||
}); |
||||
}); |
||||
@ -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()); |
||||
} |
||||
@ -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; |
||||
} |
||||
Loading…
Reference in new issue