diff --git a/.betterer.results b/.betterer.results
index 86d7ad924b9..c43b7369f1a 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -1899,9 +1899,17 @@ exports[`better eslint`] = {
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
],
- "public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts:5381": [
+ "public/app/features/dashboard-scene/scene/RowRepeaterBehavior.ts:5381": [
+ [0, 0, 0, "Do not use any type assertions.", "0"]
+ ],
+ "public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
],
+ "public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.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/dashboard-scene/serialization/transformSceneToSaveModel.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"]
],
diff --git a/devenv/dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel-and-vertical-repeating-panel.json b/devenv/dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel-and-vertical-repeating-panel.json
deleted file mode 100644
index d241b37a2a8..00000000000
--- a/devenv/dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel-and-vertical-repeating-panel.json
+++ /dev/null
@@ -1,334 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": "-- Grafana --",
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "target": {
- "limit": 100,
- "matchAny": false,
- "tags": [],
- "type": "dashboard"
- },
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "fiscalYearStartMonth": 0,
- "graphTooltip": 0,
- "iteration": 1640181176989,
- "links": [],
- "liveNow": false,
- "panels": [
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 0
- },
- "id": 2,
- "panels": [],
- "repeat": "row",
- "title": "Row title $row",
- "type": "row"
- },
- {
- "datasource": {
- "type": "testdata"
- },
- "description": "",
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "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"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green"
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 0,
- "y": 1
- },
- "id": 4,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "title": "Panel Title",
- "type": "timeseries"
- },
- {
- "datasource": {
- "type": "testdata"
- },
- "description": "",
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "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"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green"
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 8,
- "y": 1
- },
- "id": 9,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "repeat": "vertical",
- "repeatDirection": "v",
- "title": "Vertical repeating $vertical",
- "type": "timeseries"
- }
- ],
- "schemaVersion": 34,
- "tags": [],
- "templating": {
- "list": [
- {
- "current": {
- "selected": true,
- "text": [
- "All"
- ],
- "value": [
- "$__all"
- ]
- },
- "hide": 0,
- "includeAll": true,
- "multi": true,
- "name": "vertical",
- "options": [
- {
- "selected": true,
- "text": "All",
- "value": "$__all"
- },
- {
- "selected": false,
- "text": "1",
- "value": "1"
- },
- {
- "selected": false,
- "text": "2",
- "value": "2"
- },
- {
- "selected": false,
- "text": "3",
- "value": "3"
- }
- ],
- "query": "1,2,3",
- "queryValue": "",
- "skipUrlSync": false,
- "type": "custom"
- },
- {
- "current": {
- "selected": true,
- "text": [
- "All"
- ],
- "value": [
- "$__all"
- ]
- },
- "hide": 0,
- "includeAll": true,
- "multi": true,
- "name": "horizontal",
- "options": [
- {
- "selected": true,
- "text": "All",
- "value": "$__all"
- },
- {
- "selected": false,
- "text": "1",
- "value": "1"
- },
- {
- "selected": false,
- "text": "2",
- "value": "2"
- },
- {
- "selected": false,
- "text": "3",
- "value": "3"
- }
- ],
- "query": "1,2,3",
- "queryValue": "",
- "skipUrlSync": false,
- "type": "custom"
- },
- {
- "current": {
- "selected": true,
- "text": [
- "All"
- ],
- "value": [
- "$__all"
- ]
- },
- "hide": 0,
- "includeAll": true,
- "multi": true,
- "name": "row",
- "options": [
- {
- "selected": true,
- "text": "All",
- "value": "$__all"
- },
- {
- "selected": false,
- "text": "1",
- "value": "1"
- },
- {
- "selected": false,
- "text": "2",
- "value": "2"
- },
- {
- "selected": false,
- "text": "3",
- "value": "3"
- }
- ],
- "query": "1,2,3",
- "queryValue": "",
- "skipUrlSync": false,
- "type": "custom"
- }
- ]
- },
- "time": {
- "from": "now-6h",
- "to": "now"
- },
- "timepicker": {},
- "timezone": "utc",
- "title": "Repeating a row with a non-repeating panel and vertical repeating panel",
- "uid": "7lS-ojt7z",
- "version": 2,
- "weekStart": ""
-}
diff --git a/devenv/dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel.json b/devenv/dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel.json
deleted file mode 100644
index 976cf891c46..00000000000
--- a/devenv/dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel.json
+++ /dev/null
@@ -1,257 +0,0 @@
-{
- "annotations": {
- "list": [
- {
- "builtIn": 1,
- "datasource": "-- Grafana --",
- "enable": true,
- "hide": true,
- "iconColor": "rgba(0, 211, 255, 1)",
- "name": "Annotations & Alerts",
- "target": {
- "limit": 100,
- "matchAny": false,
- "tags": [],
- "type": "dashboard"
- },
- "type": "dashboard"
- }
- ]
- },
- "editable": true,
- "fiscalYearStartMonth": 0,
- "graphTooltip": 0,
- "iteration": 1640181195825,
- "links": [],
- "liveNow": false,
- "panels": [
- {
- "collapsed": false,
- "gridPos": {
- "h": 1,
- "w": 24,
- "x": 0,
- "y": 0
- },
- "id": 2,
- "panels": [],
- "repeat": "row",
- "title": "Row title $row",
- "type": "row"
- },
- {
- "datasource": {
- "type": "testdata"
- },
- "description": "",
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "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"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green"
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": []
- },
- "gridPos": {
- "h": 8,
- "w": 8,
- "x": 0,
- "y": 1
- },
- "id": 4,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
- }
- },
- "title": "Panel Title",
- "type": "timeseries"
- }
- ],
- "schemaVersion": 34,
- "tags": [],
- "templating": {
- "list": [
- {
- "current": {
- "selected": true,
- "text": [
- "All"
- ],
- "value": [
- "$__all"
- ]
- },
- "hide": 0,
- "includeAll": true,
- "multi": true,
- "name": "vertical",
- "options": [
- {
- "selected": true,
- "text": "All",
- "value": "$__all"
- },
- {
- "selected": false,
- "text": "1",
- "value": "1"
- },
- {
- "selected": false,
- "text": "2",
- "value": "2"
- },
- {
- "selected": false,
- "text": "3",
- "value": "3"
- }
- ],
- "query": "1,2,3",
- "queryValue": "",
- "skipUrlSync": false,
- "type": "custom"
- },
- {
- "current": {
- "selected": true,
- "text": [
- "All"
- ],
- "value": [
- "$__all"
- ]
- },
- "hide": 0,
- "includeAll": true,
- "multi": true,
- "name": "horizontal",
- "options": [
- {
- "selected": true,
- "text": "All",
- "value": "$__all"
- },
- {
- "selected": false,
- "text": "1",
- "value": "1"
- },
- {
- "selected": false,
- "text": "2",
- "value": "2"
- },
- {
- "selected": false,
- "text": "3",
- "value": "3"
- }
- ],
- "query": "1,2,3",
- "queryValue": "",
- "skipUrlSync": false,
- "type": "custom"
- },
- {
- "current": {
- "selected": true,
- "text": [
- "All"
- ],
- "value": [
- "$__all"
- ]
- },
- "hide": 0,
- "includeAll": true,
- "multi": true,
- "name": "row",
- "options": [
- {
- "selected": true,
- "text": "All",
- "value": "$__all"
- },
- {
- "selected": false,
- "text": "1",
- "value": "1"
- },
- {
- "selected": false,
- "text": "2",
- "value": "2"
- },
- {
- "selected": false,
- "text": "3",
- "value": "3"
- }
- ],
- "query": "1,2,3",
- "queryValue": "",
- "skipUrlSync": false,
- "type": "custom"
- }
- ]
- },
- "time": {
- "from": "now-6h",
- "to": "now"
- },
- "timepicker": {},
- "timezone": "utc",
- "title": "Repeating a row with a non-repeating panel",
- "uid": "ZzyTojpnz",
- "version": 3,
- "weekStart": ""
-}
diff --git a/devenv/dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel-and-horizontal-repeating-panel.json b/devenv/dev-dashboards/feature-templating/templating-repeating-rows.json
similarity index 50%
rename from devenv/dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel-and-horizontal-repeating-panel.json
rename to devenv/dev-dashboards/feature-templating/templating-repeating-rows.json
index 1f11911b38d..f8939497d93 100644
--- a/devenv/dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel-and-horizontal-repeating-panel.json
+++ b/devenv/dev-dashboards/feature-templating/templating-repeating-rows.json
@@ -4,19 +4,13 @@
{
"builtIn": 1,
"datasource": {
- "type": "datasource",
- "uid": "grafana"
+ "type": "grafana",
+ "uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
- "target": {
- "limit": 100,
- "matchAny": false,
- "tags": [],
- "type": "dashboard"
- },
"type": "dashboard"
}
]
@@ -24,41 +18,72 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
- "id": 85,
"links": [],
"liveNow": false,
"panels": [
{
"collapsed": false,
- "datasource": {
- "type": "testdata",
- "uid": "PD8C576611E62080A"
- },
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
- "id": 2,
+ "id": 20,
"panels": [],
- "repeat": "row",
- "title": "Row $row",
+ "title": "Row at the top - not repeated - saved expanded",
+ "type": "row"
+ },
+ {
+ "gridPos": {
+ "h": 2,
+ "w": 24,
+ "x": 0,
+ "y": 1
+ },
+ "id": 15,
+ "options": {
+ "code": {
+ "language": "plaintext",
+ "showLineNumbers": false,
+ "showMiniMap": false
+ },
+ "content": "
\n Repeated row below. The row has \n a panel that is also repeated horizontally based\n on values in the $pod variable. \n
",
+ "mode": "markdown"
+ },
+ "pluginVersion": "10.2.0-pre",
+ "type": "text"
+ },
+ {
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 3
+ },
+ "id": 16,
+ "panels": [],
+ "repeat": "server",
+ "repeatDirection": "h",
+ "title": "Row for server $server",
"type": "row"
},
{
"datasource": {
- "type": "testdata"
+ "type": "testdata",
+ "uid": "PD8C576611E62080A"
},
- "description": "",
"fieldConfig": {
"defaults": {
"color": {
"mode": "palette-classic"
},
"custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
"axisLabel": "",
"axisPlacement": "auto",
+ "axisShow": false,
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
@@ -68,6 +93,7 @@
"tooltip": false,
"viz": false
},
+ "insertNulls": false,
"lineInterpolation": "linear",
"lineWidth": 1,
"pointSize": 5,
@@ -89,7 +115,8 @@
"mode": "absolute",
"steps": [
{
- "color": "green"
+ "color": "green",
+ "value": null
},
{
"color": "red",
@@ -101,145 +128,174 @@
"overrides": []
},
"gridPos": {
- "h": 8,
- "w": 8,
+ "h": 6,
+ "w": 12,
"x": 0,
- "y": 1
+ "y": 4
},
- "id": 4,
+ "id": 2,
+ "maxPerRow": 3,
"options": {
"legend": {
"calcs": [],
"displayMode": "list",
- "placement": "bottom"
+ "placement": "bottom",
+ "showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
- "title": "Row $row non-repeating panel",
+ "repeat": "pod",
+ "repeatDirection": "h",
+ "targets": [
+ {
+ "alias": "server = $server, pod id = $pod ",
+ "datasource": {
+ "type": "testdata",
+ "uid": "PD8C576611E62080A"
+ },
+ "refId": "A",
+ "scenarioId": "random_walk",
+ "seriesCount": 1
+ }
+ ],
+ "title": "server = $server, pod = $pod",
"type": "timeseries"
},
{
- "datasource": {
- "type": "testdata"
- },
- "description": "",
- "fieldConfig": {
- "defaults": {
- "color": {
- "mode": "palette-classic"
- },
- "custom": {
- "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"
- }
- },
- "mappings": [],
- "thresholds": {
- "mode": "absolute",
- "steps": [
- {
- "color": "green"
- },
- {
- "color": "red",
- "value": 80
- }
- ]
- }
- },
- "overrides": []
- },
+ "collapsed": true,
"gridPos": {
- "h": 8,
- "w": 8,
+ "h": 1,
+ "w": 24,
"x": 0,
- "y": 9
+ "y": 21
},
- "id": 9,
- "options": {
- "legend": {
- "calcs": [],
- "displayMode": "list",
- "placement": "bottom"
- },
- "tooltip": {
- "mode": "single",
- "sort": "none"
+ "id": 25,
+ "panels": [
+ {
+ "gridPos": {
+ "h": 2,
+ "w": 24,
+ "x": 0,
+ "y": 22
+ },
+ "id": 30,
+ "options": {
+ "code": {
+ "language": "plaintext",
+ "showLineNumbers": false,
+ "showMiniMap": false
+ },
+ "content": "\n Just a panel\n
",
+ "mode": "markdown"
+ },
+ "pluginVersion": "10.2.0-pre",
+ "type": "text"
}
- },
- "repeat": "horizontal",
- "repeatDirection": "h",
- "title": "Row $row repeating panel $horizontal",
- "type": "timeseries"
+ ],
+ "title": "Row at the bottom - not repeated - saved collapsed ",
+ "type": "row"
}
],
- "schemaVersion": 36,
- "tags": [],
+ "refresh": "",
+ "schemaVersion": 38,
+ "tags": [
+ "templating",
+ "gdev"
+ ],
"templating": {
"list": [
{
"current": {
"selected": true,
"text": [
- "All"
+ "A",
+ "B"
],
"value": [
- "$__all"
+ "A",
+ "B"
]
},
"hide": 0,
"includeAll": true,
"multi": true,
- "name": "vertical",
+ "name": "server",
"options": [
{
- "selected": true,
+ "selected": false,
"text": "All",
"value": "$__all"
},
{
- "selected": false,
- "text": "1",
- "value": "1"
+ "selected": true,
+ "text": "A",
+ "value": "A"
+ },
+ {
+ "selected": true,
+ "text": "B",
+ "value": "B"
},
{
"selected": false,
- "text": "2",
- "value": "2"
+ "text": "C",
+ "value": "C"
},
{
"selected": false,
- "text": "3",
- "value": "3"
+ "text": "D",
+ "value": "D"
+ },
+ {
+ "selected": false,
+ "text": "E",
+ "value": "E"
+ },
+ {
+ "selected": false,
+ "text": "F",
+ "value": "F"
+ },
+ {
+ "selected": false,
+ "text": "E",
+ "value": "E"
+ },
+ {
+ "selected": false,
+ "text": "G",
+ "value": "G"
+ },
+ {
+ "selected": false,
+ "text": "H",
+ "value": "H"
+ },
+ {
+ "selected": false,
+ "text": "I",
+ "value": "I"
+ },
+ {
+ "selected": false,
+ "text": "J",
+ "value": "J"
+ },
+ {
+ "selected": false,
+ "text": "K",
+ "value": "K"
+ },
+ {
+ "selected": false,
+ "text": "L",
+ "value": "L"
}
],
- "query": "1,2,3",
+ "query": "A,B,C,D,E,F,E,G,H,I,J,K,L",
"queryValue": "",
"skipUrlSync": false,
"type": "custom"
@@ -248,80 +304,51 @@
"current": {
"selected": true,
"text": [
- "All"
+ "Bob",
+ "Rob"
],
"value": [
- "$__all"
+ "1",
+ "2"
]
},
"hide": 0,
"includeAll": true,
"multi": true,
- "name": "horizontal",
+ "name": "pod",
"options": [
{
- "selected": true,
+ "selected": false,
"text": "All",
"value": "$__all"
},
{
- "selected": false,
- "text": "1",
+ "selected": true,
+ "text": "Bob",
"value": "1"
},
- {
- "selected": false,
- "text": "2",
- "value": "2"
- },
- {
- "selected": false,
- "text": "3",
- "value": "3"
- }
- ],
- "query": "1,2,3",
- "queryValue": "",
- "skipUrlSync": false,
- "type": "custom"
- },
- {
- "current": {
- "selected": true,
- "text": [
- "All"
- ],
- "value": [
- "$__all"
- ]
- },
- "hide": 0,
- "includeAll": true,
- "multi": true,
- "name": "row",
- "options": [
{
"selected": true,
- "text": "All",
- "value": "$__all"
- },
- {
- "selected": false,
- "text": "1",
- "value": "1"
- },
- {
- "selected": false,
- "text": "2",
+ "text": "Rob",
"value": "2"
},
{
"selected": false,
- "text": "3",
+ "text": "Sod",
"value": "3"
+ },
+ {
+ "selected": false,
+ "text": "Hod",
+ "value": "4"
+ },
+ {
+ "selected": false,
+ "text": "Cod",
+ "value": "5"
}
],
- "query": "1,2,3",
+ "query": "Bob : 1, Rob : 2,Sod : 3, Hod : 4, Cod : 5",
"queryValue": "",
"skipUrlSync": false,
"type": "custom"
@@ -333,9 +360,9 @@
"to": "now"
},
"timepicker": {},
- "timezone": "utc",
- "title": "Repeating a row with a non-repeating panel and horizontal repeating panel",
- "uid": "k3PEoCpnk",
+ "timezone": "",
+ "title": "Repeating rows",
+ "uid": "Repeating-rows-uid",
"version": 1,
"weekStart": ""
-}
+}
\ No newline at end of file
diff --git a/devenv/jsonnet/dev-dashboards.libsonnet b/devenv/jsonnet/dev-dashboards.libsonnet
index 69a1666fc23..73541ca8bb1 100644
--- a/devenv/jsonnet/dev-dashboards.libsonnet
+++ b/devenv/jsonnet/dev-dashboards.libsonnet
@@ -30,27 +30,6 @@ local dashboard = grafana.dashboard;
id: 0,
}
},
- dashboard.new('Repeating-a-row-with-a-non-repeating-pan', import '../dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel-and-horizontal-repeating-panel.json') +
- resource.addMetadata('folder', 'dev-dashboards') +
- {
- spec+: {
- id: 0,
- }
- },
- dashboard.new('Repeating-a-row-with-a-non-repeating-pan', import '../dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel-and-vertical-repeating-panel.json') +
- resource.addMetadata('folder', 'dev-dashboards') +
- {
- spec+: {
- id: 0,
- }
- },
- dashboard.new('Repeating-a-row-with-a-non-repeating-pan', import '../dev-dashboards/e2e-repeats/Repeating-a-row-with-a-non-repeating-panel.json') +
- resource.addMetadata('folder', 'dev-dashboards') +
- {
- spec+: {
- id: 0,
- }
- },
dashboard.new('Repeating-a-row-with-a-repeating-horizon', import '../dev-dashboards/e2e-repeats/Repeating-a-row-with-a-repeating-horizontal-panel.json') +
resource.addMetadata('folder', 'dev-dashboards') +
{
@@ -625,6 +604,13 @@ local dashboard = grafana.dashboard;
id: 0,
}
},
+ dashboard.new('templating-repeating-rows', import '../dev-dashboards/feature-templating/templating-repeating-rows.json') +
+ resource.addMetadata('folder', 'dev-dashboards') +
+ {
+ spec+: {
+ id: 0,
+ }
+ },
dashboard.new('templating-textbox-e2e-scenarios', import '../dev-dashboards/feature-templating/templating-textbox-e2e-scenarios.json') +
resource.addMetadata('folder', 'dev-dashboards') +
{
diff --git a/e2e/dashboards-suite/Repeating_a_row_with_a_non_repeating_panel.spec.ts b/e2e/dashboards-suite/Repeating_a_row_with_a_non_repeating_panel.spec.ts
deleted file mode 100644
index d07b4e609a6..00000000000
--- a/e2e/dashboards-suite/Repeating_a_row_with_a_non_repeating_panel.spec.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { e2e } from '../utils';
-const PAGE_UNDER_TEST = 'k3PEoCpnk/repeating-a-row-with-a-non-repeating-panel-and-horizontal-repeating-panel';
-const DASHBOARD_NAME = 'Repeating a row with a non-repeating panel and horizontal repeating panel';
-
-describe('Repeating a row with repeated panels and a non-repeating panel', () => {
- beforeEach(() => {
- e2e.flows.login('admin', 'admin');
- });
-
- it('should be able to collapse and expand a repeated row without losing panels', () => {
- e2e.flows.openDashboard({ uid: PAGE_UNDER_TEST });
- e2e().contains(DASHBOARD_NAME).should('be.visible');
-
- const panelsToCheck = [
- 'Row 2 non-repeating panel',
- 'Row 2 repeating panel 1',
- 'Row 2 repeating panel 2',
- 'Row 2 repeating panel 3',
- ];
-
- // Collapse Row 1 first so the Row 2 panels all fit on the screen
- e2e.components.DashboardRow.title('Row 1').click();
-
- // Rows are expanded by default, so check that all panels are visible
- panelsToCheck.forEach((title) => {
- e2e.components.Panels.Panel.title(title).should('be.visible');
- });
-
- // Collapse the row and check panels are no longer visible
- e2e.components.DashboardRow.title('Row 2').click();
- panelsToCheck.forEach((title) => {
- e2e.components.Panels.Panel.title(title).should('not.exist');
- });
-
- // Expand the row and check all panels are visible again
- e2e.components.DashboardRow.title('Row 2').click();
- panelsToCheck.forEach((title) => {
- e2e.components.Panels.Panel.title(title).should('be.visible');
- });
- });
-});
diff --git a/package.json b/package.json
index edfed010391..43cacb56df7 100644
--- a/package.json
+++ b/package.json
@@ -253,7 +253,7 @@
"@grafana/lezer-traceql": "0.0.5",
"@grafana/monaco-logql": "^0.0.7",
"@grafana/runtime": "workspace:*",
- "@grafana/scenes": "^0.29.0",
+ "@grafana/scenes": "^1.1.1",
"@grafana/schema": "workspace:*",
"@grafana/ui": "workspace:*",
"@kusto/monaco-kusto": "^7.4.0",
diff --git a/public/app/features/dashboard-scene/scene/PanelRepeaterGridItem.tsx b/public/app/features/dashboard-scene/scene/PanelRepeaterGridItem.tsx
index 60ac7438eec..6fd7506d29d 100644
--- a/public/app/features/dashboard-scene/scene/PanelRepeaterGridItem.tsx
+++ b/public/app/features/dashboard-scene/scene/PanelRepeaterGridItem.tsx
@@ -14,11 +14,12 @@ import {
SceneGridItemLike,
sceneGraph,
MultiValueVariable,
- VariableValueSingle,
LocalValueVariable,
} from '@grafana/scenes';
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN } from 'app/core/constants';
+import { getMultiVariableValues } from '../utils/utils';
+
interface PanelRepeaterGridItemState extends SceneGridItemStateLike {
source: VizPanel;
repeatedPanels?: VizPanel[];
@@ -106,7 +107,7 @@ export class PanelRepeaterGridItem extends SceneObjectBase o.value),
- texts: options.map((o) => o.label),
- };
- }
-
- return {
- values: Array.isArray(value) ? value : [value],
- texts: Array.isArray(text) ? text : [text],
- };
- }
-
private getMaxPerRow(): number {
return this.state.maxPerRow ?? 4;
}
diff --git a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx
new file mode 100644
index 00000000000..204394c372f
--- /dev/null
+++ b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.test.tsx
@@ -0,0 +1,144 @@
+import {
+ EmbeddedScene,
+ SceneCanvasText,
+ SceneGridItem,
+ SceneGridLayout,
+ SceneGridRow,
+ SceneTimeRange,
+ SceneVariableSet,
+ TestVariable,
+} from '@grafana/scenes';
+import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants';
+
+import { activateFullSceneTree } from '../utils/test-utils';
+
+import { RepeatDirection } from './PanelRepeaterGridItem';
+import { RowRepeaterBehavior } from './RowRepeaterBehavior';
+
+describe('RowRepeaterBehavior', () => {
+ describe('Given scene with variable with 5 values', () => {
+ let scene: EmbeddedScene, grid: SceneGridLayout;
+
+ beforeEach(async () => {
+ ({ scene, grid } = buildScene({ variableQueryTime: 0 }));
+ activateFullSceneTree(scene);
+ await new Promise((r) => setTimeout(r, 1));
+ });
+
+ it('Should repeat row', () => {
+ // Verify that panel above row remains
+ expect(grid.state.children[0]).toBeInstanceOf(SceneGridItem);
+ // Verify that first row still has repeat behavior
+ const row1 = grid.state.children[1] as SceneGridRow;
+ expect(row1.state.$behaviors?.[0]).toBeInstanceOf(RowRepeaterBehavior);
+ expect(row1.state.$variables!.state.variables[0].getValue()).toBe('1');
+
+ const row2 = grid.state.children[2] as SceneGridRow;
+ expect(row2.state.$variables!.state.variables[0].getValueText?.()).toBe('B');
+
+ // Should give repeated panels unique keys
+ const gridItem = row2.state.children[0] as SceneGridItem;
+ expect(gridItem.state.body?.state.key).toBe('canvas-1-row-1');
+ });
+
+ it('Should push row at the bottom down', () => {
+ // Should push row at the bottom down
+ const rowAtTheBottom = grid.state.children[6] as SceneGridRow;
+ expect(rowAtTheBottom.state.title).toBe('Row at the bottom');
+
+ // Panel at the top is 10, each row is (1+5)*5 = 30, so the grid item below it should be 40
+ expect(rowAtTheBottom.state.y).toBe(40);
+ });
+
+ it('Should handle second repeat cycle and update remove old repeats', async () => {
+ // trigger another repeat cycle by changing the variable
+ const variable = scene.state.$variables!.state.variables[0] as TestVariable;
+ variable.changeValueTo(['2', '3']);
+
+ await new Promise((r) => setTimeout(r, 1));
+
+ // should now only have 2 repeated rows (and the panel above + the row at the bottom)
+ expect(grid.state.children.length).toBe(4);
+ });
+ });
+});
+
+interface SceneOptions {
+ variableQueryTime: number;
+ maxPerRow?: number;
+ itemHeight?: number;
+ repeatDirection?: RepeatDirection;
+}
+
+function buildScene(options: SceneOptions) {
+ const grid = new SceneGridLayout({
+ children: [
+ new SceneGridItem({
+ x: 0,
+ y: 0,
+ width: 24,
+ height: 10,
+ body: new SceneCanvasText({
+ text: 'Panel above row',
+ }),
+ }),
+ new SceneGridRow({
+ x: 0,
+ y: 10,
+ width: 24,
+ height: 1,
+ $behaviors: [
+ new RowRepeaterBehavior({
+ variableName: 'server',
+ sources: [
+ new SceneGridItem({
+ x: 0,
+ y: 11,
+ width: 24,
+ height: 5,
+ body: new SceneCanvasText({
+ key: 'canvas-1',
+ text: 'Panel inside repeated row, server = $server',
+ }),
+ }),
+ ],
+ }),
+ ],
+ }),
+ new SceneGridRow({
+ x: 0,
+ y: 16,
+ width: 24,
+ height: 5,
+ title: 'Row at the bottom',
+ }),
+ ],
+ });
+
+ const scene = new EmbeddedScene({
+ $timeRange: new SceneTimeRange({ from: 'now-6h', to: 'now' }),
+ $variables: new SceneVariableSet({
+ variables: [
+ new TestVariable({
+ name: 'server',
+ query: 'A.*',
+ value: ALL_VARIABLE_VALUE,
+ text: ALL_VARIABLE_TEXT,
+ isMulti: true,
+ includeAll: true,
+ delayMs: options.variableQueryTime,
+ optionsToReturn: [
+ { label: 'A', value: '1' },
+ { label: 'B', value: '2' },
+ { label: 'C', value: '3' },
+ { label: 'D', value: '4' },
+ { label: 'E', value: '5' },
+ ],
+ }),
+ ],
+ }),
+ body: grid,
+ });
+
+ return { scene, grid };
+}
diff --git a/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.ts b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.ts
new file mode 100644
index 00000000000..fa8dc661005
--- /dev/null
+++ b/public/app/features/dashboard-scene/scene/RowRepeaterBehavior.ts
@@ -0,0 +1,215 @@
+import {
+ LocalValueVariable,
+ MultiValueVariable,
+ sceneGraph,
+ SceneGridItemLike,
+ SceneGridLayout,
+ SceneGridRow,
+ SceneObjectBase,
+ SceneObjectState,
+ SceneVariable,
+ SceneVariableSet,
+ VariableDependencyConfig,
+ VariableValueSingle,
+} from '@grafana/scenes';
+
+import { getMultiVariableValues } from '../utils/utils';
+
+interface RowRepeaterBehaviorState extends SceneObjectState {
+ variableName: string;
+ sources: SceneGridItemLike[];
+}
+
+/**
+ * This behavior will run an effect function when specified variables change
+ */
+
+export class RowRepeaterBehavior extends SceneObjectBase {
+ protected _variableDependency = new VariableDependencyConfig(this, {
+ variableNames: [this.state.variableName],
+ onVariableUpdatesCompleted: this._onVariableChanged.bind(this),
+ });
+
+ private _isWaitingForVariables = false;
+
+ public constructor(state: RowRepeaterBehaviorState) {
+ super(state);
+
+ this.addActivationHandler(() => this._activationHandler());
+ }
+
+ private _activationHandler() {
+ // If we our variable is ready we can process repeats on activation
+ if (sceneGraph.hasVariableDependencyInLoadingState(this)) {
+ this._isWaitingForVariables = true;
+ } else {
+ this._performRepeat();
+ }
+ }
+
+ private _onVariableChanged(changedVariables: Set, dependencyChanged: boolean): void {
+ if (dependencyChanged) {
+ this._performRepeat();
+ return;
+ }
+
+ // If we are waiting for variables and the variable is no longer loading then we are ready to repeat as well
+ if (this._isWaitingForVariables && !sceneGraph.hasVariableDependencyInLoadingState(this)) {
+ this._isWaitingForVariables = false;
+ this._performRepeat();
+ }
+ }
+
+ private _performRepeat() {
+ const variable = sceneGraph.lookupVariable(this.state.variableName, this.parent?.parent!);
+
+ if (!variable) {
+ console.error('RepeatedRowBehavior: Variable not found');
+ return;
+ }
+
+ if (!(variable instanceof MultiValueVariable)) {
+ console.error('RepeatedRowBehavior: Variable is not a MultiValueVariable');
+ return;
+ }
+
+ if (!(this.parent instanceof SceneGridRow)) {
+ console.error('RepeatedRowBehavior: Parent is not a SceneGridRow');
+ return;
+ }
+
+ const layout = sceneGraph.getLayout(this);
+
+ if (!(layout instanceof SceneGridLayout)) {
+ console.error('RepeatedRowBehavior: Layout is not a SceneGridLayout');
+ return;
+ }
+
+ const rowToRepeat = this.parent as SceneGridRow;
+ const { values, texts } = getMultiVariableValues(variable);
+ const rows: SceneGridRow[] = [];
+ const rowContentHeight = getRowContentHeight(this.state.sources);
+ let maxYOfRows = 0;
+
+ // Loop through variable values and create repeates
+ for (let index = 0; index < values.length; index++) {
+ const children: SceneGridItemLike[] = [];
+
+ // Loop through panels inside row
+ for (const source of this.state.sources) {
+ const sourceItemY = source.state.y ?? 0;
+ const itemY = sourceItemY + (rowContentHeight + 1) * index;
+
+ const itemClone = source.clone({
+ key: `${source.state.key}-clone-${index}`,
+ y: itemY,
+ });
+
+ //Make sure all the child scene objects have unique keys
+ ensureUniqueKeys(itemClone, index);
+
+ children.push(itemClone);
+
+ if (maxYOfRows < itemY + itemClone.state.height!) {
+ maxYOfRows = itemY + itemClone.state.height!;
+ }
+ }
+
+ const rowClone = this.getRowClone(rowToRepeat, index, values[index], texts[index], rowContentHeight, children);
+ rows.push(rowClone);
+ }
+
+ updateLayout(layout, rows, maxYOfRows, rowToRepeat);
+ }
+
+ getRowClone(
+ rowToRepeat: SceneGridRow,
+ index: number,
+ value: VariableValueSingle,
+ text: VariableValueSingle,
+ rowContentHeight: number,
+ children: SceneGridItemLike[]
+ ): SceneGridRow {
+ if (index === 0) {
+ rowToRepeat.setState({
+ // not activated
+ $variables: new SceneVariableSet({
+ variables: [new LocalValueVariable({ name: this.state.variableName, value, text: String(text) })],
+ }),
+ children,
+ });
+ return rowToRepeat;
+ }
+
+ const sourceRowY = rowToRepeat.state.y ?? 0;
+
+ return rowToRepeat.clone({
+ key: `${rowToRepeat.state.key}-clone-${index}`,
+ $variables: new SceneVariableSet({
+ variables: [new LocalValueVariable({ name: this.state.variableName, value, text: String(text) })],
+ }),
+ $behaviors: [],
+ children,
+ y: sourceRowY + rowContentHeight * index + index,
+ });
+ }
+}
+
+function getRowContentHeight(panels: SceneGridItemLike[]): number {
+ let maxY = 0;
+ let minY = Number.MAX_VALUE;
+
+ for (const panel of panels) {
+ if (panel.state.y! + panel.state.height! > maxY) {
+ maxY = panel.state.y! + panel.state.height!;
+ }
+ if (panel.state.y! < minY) {
+ minY = panel.state.y!;
+ }
+ }
+
+ return maxY - minY;
+}
+
+function updateLayout(layout: SceneGridLayout, rows: SceneGridRow[], maxYOfRows: number, rowToRepeat: SceneGridRow) {
+ const allChildren = getLayoutChildrenFilterOutRepeatClones(layout, rowToRepeat);
+ const index = allChildren.indexOf(rowToRepeat);
+
+ if (index === -1) {
+ throw new Error('RowRepeaterBehavior: Parent row not found in layout children');
+ }
+
+ const newChildren = [...allChildren.slice(0, index), ...rows, ...allChildren.slice(index + 1)];
+
+ // Is there grid items after rows?
+ if (allChildren.length > index + 1) {
+ const childrenAfter = allChildren.slice(index + 1);
+ const firstChildAfterY = childrenAfter[0].state.y!;
+ const diff = maxYOfRows - firstChildAfterY;
+
+ for (const child of childrenAfter) {
+ if (child.state.y! < maxYOfRows) {
+ child.setState({ y: child.state.y! + diff });
+ }
+ }
+ }
+
+ layout.setState({ children: newChildren });
+}
+
+function getLayoutChildrenFilterOutRepeatClones(layout: SceneGridLayout, rowToRepeat: SceneGridRow) {
+ return layout.state.children.filter((child) => {
+ if (child.state.key?.startsWith(`${rowToRepeat.state.key}-clone-`)) {
+ return false;
+ }
+
+ return true;
+ });
+}
+
+function ensureUniqueKeys(item: SceneGridItemLike, rowIndex: number) {
+ item.forEachChild((child) => {
+ child.setState({ key: `${child.state.key}-row-${rowIndex}` });
+ ensureUniqueKeys(child, rowIndex);
+ });
+}
diff --git a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap
index 73b4e697eb5..332723bfc4f 100644
--- a/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap
+++ b/public/app/features/dashboard-scene/serialization/__snapshots__/transformSceneToSaveModel.test.ts.snap
@@ -1,6 +1,124 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`transformSceneToSaveModel Given a scene Should transform back to peristed model 1`] = `
+exports[`transformSceneToSaveModel Given a scene with rows Should transform back to peristed model 1`] = `
+{
+ "editable": true,
+ "fiscalYearStartMonth": 0,
+ "graphTooltip": 0,
+ "links": [],
+ "panels": [
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0,
+ },
+ "id": 20,
+ "panels": [],
+ "title": "Row at the top - not repeated - saved expanded",
+ "type": "row",
+ },
+ {
+ "fieldConfig": {
+ "defaults": {},
+ "overrides": [],
+ },
+ "gridPos": {
+ "h": 2,
+ "w": 24,
+ "x": 0,
+ "y": 1,
+ },
+ "id": 15,
+ "options": {
+ "code": {
+ "language": "plaintext",
+ "showLineNumbers": false,
+ "showMiniMap": false,
+ },
+ "content": "
+ Repeated row below. The row has
+ a panel that is also repeated horizontally based
+ on values in the $pod variable.
+
",
+ "mode": "markdown",
+ },
+ "title": "",
+ "transformations": [],
+ "transparent": false,
+ "type": "text",
+ },
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 3,
+ },
+ "id": 16,
+ "panels": [],
+ "repeat": "server",
+ "title": "Row for server $server",
+ "type": "row",
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 25,
+ },
+ "id": 25,
+ "panels": [
+ {
+ "fieldConfig": {
+ "defaults": {},
+ "overrides": [],
+ },
+ "gridPos": {
+ "h": 2,
+ "w": 24,
+ "x": 0,
+ "y": 26,
+ },
+ "id": 30,
+ "options": {
+ "code": {
+ "language": "plaintext",
+ "showLineNumbers": false,
+ "showMiniMap": false,
+ },
+ "content": "
+ Just a panel
+
",
+ "mode": "markdown",
+ },
+ "transformations": [],
+ "transparent": false,
+ "type": "text",
+ },
+ ],
+ "title": "Row at the bottom - not repeated - saved collapsed ",
+ "type": "row",
+ },
+ ],
+ "schemaVersion": 36,
+ "tags": [],
+ "time": {
+ "from": "now-6h",
+ "to": "now",
+ },
+ "timezone": "browser",
+ "title": "Repeating rows",
+ "uid": "Repeating-rows-uid",
+}
+`;
+
+exports[`transformSceneToSaveModel Given a simple scene Should transform back to peristed model 1`] = `
{
"editable": true,
"fiscalYearStartMonth": 0,
@@ -45,6 +163,63 @@ exports[`transformSceneToSaveModel Given a scene Should transform back to perist
"transparent": false,
"type": "timeseries",
},
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 8,
+ },
+ "id": 5,
+ "panels": [],
+ "title": "Row title",
+ "type": "row",
+ },
+ {
+ "fieldConfig": {
+ "defaults": {},
+ "overrides": [],
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 12,
+ "x": 0,
+ "y": 9,
+ },
+ "id": 29,
+ "options": {},
+ "title": "panel inside row",
+ "transformations": [],
+ "transparent": false,
+ "type": "timeseries",
+ },
+ {
+ "fieldConfig": {
+ "defaults": {},
+ "overrides": [],
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 11,
+ "x": 12,
+ "y": 9,
+ },
+ "id": 25,
+ "options": {
+ "code": {
+ "language": "plaintext",
+ "showLineNumbers": false,
+ "showMiniMap": false,
+ },
+ "content": "content",
+ "mode": "markdown",
+ },
+ "title": "Transparent text panel",
+ "transformations": [],
+ "transparent": true,
+ "type": "text",
+ },
],
"schemaVersion": 36,
"tags": [],
diff --git a/public/app/features/dashboard-scene/serialization/testfiles/repeating_rows_and_panels.json b/public/app/features/dashboard-scene/serialization/testfiles/repeating_rows_and_panels.json
new file mode 100644
index 00000000000..555e7b80bd1
--- /dev/null
+++ b/public/app/features/dashboard-scene/serialization/testfiles/repeating_rows_and_panels.json
@@ -0,0 +1,353 @@
+{
+ "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,
+ "links": [],
+ "liveNow": false,
+ "panels": [
+ {
+ "collapsed": false,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 0
+ },
+ "id": 20,
+ "panels": [],
+ "title": "Row at the top - not repeated - saved expanded",
+ "type": "row"
+ },
+ {
+ "gridPos": {
+ "h": 2,
+ "w": 24,
+ "x": 0,
+ "y": 1
+ },
+ "id": 15,
+ "options": {
+ "code": {
+ "language": "plaintext",
+ "showLineNumbers": false,
+ "showMiniMap": false
+ },
+ "content": "\n Repeated row below. The row has \n a panel that is also repeated horizontally based\n on values in the $pod variable. \n
",
+ "mode": "markdown"
+ },
+ "pluginVersion": "10.2.0-pre",
+ "type": "text"
+ },
+ {
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 3
+ },
+ "id": 16,
+ "panels": [],
+ "repeat": "server",
+ "repeatDirection": "h",
+ "title": "Row for server $server",
+ "type": "row"
+ },
+ {
+ "datasource": {
+ "type": "testdata",
+ "uid": "PD8C576611E62080A"
+ },
+ "fieldConfig": {
+ "defaults": {
+ "color": {
+ "mode": "palette-classic"
+ },
+ "custom": {
+ "axisCenteredZero": false,
+ "axisColorMode": "text",
+ "axisLabel": "",
+ "axisPlacement": "auto",
+ "axisShow": false,
+ "barAlignment": 0,
+ "drawStyle": "line",
+ "fillOpacity": 0,
+ "gradientMode": "none",
+ "hideFrom": {
+ "legend": false,
+ "tooltip": false,
+ "viz": false
+ },
+ "insertNulls": false,
+ "lineInterpolation": "linear",
+ "lineWidth": 1,
+ "pointSize": 5,
+ "scaleDistribution": {
+ "type": "linear"
+ },
+ "showPoints": "auto",
+ "spanNulls": false,
+ "stacking": {
+ "group": "A",
+ "mode": "none"
+ },
+ "thresholdsStyle": {
+ "mode": "off"
+ }
+ },
+ "mappings": [],
+ "thresholds": {
+ "mode": "absolute",
+ "steps": [
+ {
+ "color": "green",
+ "value": null
+ },
+ {
+ "color": "red",
+ "value": 80
+ }
+ ]
+ }
+ },
+ "overrides": []
+ },
+ "gridPos": {
+ "h": 10,
+ "w": 12,
+ "x": 0,
+ "y": 4
+ },
+ "id": 2,
+ "maxPerRow": 3,
+ "options": {
+ "legend": {
+ "calcs": [],
+ "displayMode": "list",
+ "placement": "bottom",
+ "showLegend": true
+ },
+ "tooltip": {
+ "mode": "single",
+ "sort": "none"
+ }
+ },
+ "repeat": "pod",
+ "repeatDirection": "h",
+ "targets": [
+ {
+ "alias": "server = $server, pod id = $pod ",
+ "datasource": {
+ "type": "testdata",
+ "uid": "PD8C576611E62080A"
+ },
+ "refId": "A",
+ "scenarioId": "random_walk",
+ "seriesCount": 1
+ }
+ ],
+ "title": "server = $server, pod = $pod",
+ "type": "timeseries"
+ },
+ {
+ "collapsed": true,
+ "gridPos": {
+ "h": 1,
+ "w": 24,
+ "x": 0,
+ "y": 25
+ },
+ "id": 25,
+ "panels": [
+ {
+ "gridPos": {
+ "h": 2,
+ "w": 24,
+ "x": 0,
+ "y": 26
+ },
+ "id": 30,
+ "options": {
+ "code": {
+ "language": "plaintext",
+ "showLineNumbers": false,
+ "showMiniMap": false
+ },
+ "content": "\n Just a panel\n
",
+ "mode": "markdown"
+ },
+ "pluginVersion": "10.2.0-pre",
+ "type": "text"
+ }
+ ],
+ "title": "Row at the bottom - not repeated - saved collapsed ",
+ "type": "row"
+ }
+ ],
+ "refresh": "",
+ "schemaVersion": 38,
+ "tags": ["templating", "gdev"],
+ "templating": {
+ "list": [
+ {
+ "current": {
+ "selected": true,
+ "text": ["A", "B"],
+ "value": ["A", "B"]
+ },
+ "hide": 0,
+ "includeAll": true,
+ "multi": true,
+ "name": "server",
+ "options": [
+ {
+ "selected": false,
+ "text": "All",
+ "value": "$__all"
+ },
+ {
+ "selected": true,
+ "text": "A",
+ "value": "A"
+ },
+ {
+ "selected": true,
+ "text": "B",
+ "value": "B"
+ },
+ {
+ "selected": false,
+ "text": "C",
+ "value": "C"
+ },
+ {
+ "selected": false,
+ "text": "D",
+ "value": "D"
+ },
+ {
+ "selected": false,
+ "text": "E",
+ "value": "E"
+ },
+ {
+ "selected": false,
+ "text": "F",
+ "value": "F"
+ },
+ {
+ "selected": false,
+ "text": "E",
+ "value": "E"
+ },
+ {
+ "selected": false,
+ "text": "G",
+ "value": "G"
+ },
+ {
+ "selected": false,
+ "text": "H",
+ "value": "H"
+ },
+ {
+ "selected": false,
+ "text": "I",
+ "value": "I"
+ },
+ {
+ "selected": false,
+ "text": "J",
+ "value": "J"
+ },
+ {
+ "selected": false,
+ "text": "K",
+ "value": "K"
+ },
+ {
+ "selected": false,
+ "text": "L",
+ "value": "L"
+ }
+ ],
+ "query": "A,B,C,D,E,F,E,G,H,I,J,K,L",
+ "queryValue": "",
+ "skipUrlSync": false,
+ "type": "custom"
+ },
+ {
+ "current": {
+ "selected": true,
+ "text": ["Bob", "Rob"],
+ "value": ["1", "2"]
+ },
+ "hide": 0,
+ "includeAll": true,
+ "multi": true,
+ "name": "pod",
+ "options": [
+ {
+ "selected": false,
+ "text": "All",
+ "value": "$__all"
+ },
+ {
+ "selected": true,
+ "text": "Bob",
+ "value": "1"
+ },
+ {
+ "selected": true,
+ "text": "Rob",
+ "value": "2"
+ },
+ {
+ "selected": false,
+ "text": "Sod",
+ "value": "3"
+ },
+ {
+ "selected": false,
+ "text": "Hod",
+ "value": "4"
+ },
+ {
+ "selected": false,
+ "text": "Cod",
+ "value": "5"
+ }
+ ],
+ "query": "Bob : 1, Rob : 2,Sod : 3, Hod : 4, Cod : 5",
+ "queryValue": "",
+ "skipUrlSync": false,
+ "type": "custom"
+ }
+ ]
+ },
+ "time": {
+ "from": "now-6h",
+ "to": "now"
+ },
+ "timepicker": {},
+ "timezone": "",
+ "title": "Repeating rows",
+ "uid": "Repeating-rows-uid",
+ "version": 1,
+ "weekStart": ""
+}
diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts
index d015934e6fc..fc107fadff5 100644
--- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts
@@ -20,15 +20,18 @@ import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
import { PanelTimeRange } from '../scene/PanelTimeRange';
+import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
import { ShareQueryDataProvider } from '../scene/ShareQueryDataProvider';
+import repeatingRowsAndPanelsDashboardJson from './testfiles/repeating_rows_and_panels.json';
import {
createDashboardSceneFromDashboardModel,
buildGridItemForPanel,
createSceneVariableFromVariableModel,
+ transformSaveModelToScene,
} from './transformSaveModelToScene';
-describe('DashboardLoader', () => {
+describe('transformSaveModelToScene', () => {
describe('when creating dashboard scene', () => {
it('should initialize the DashboardScene with the model state', () => {
const dash = {
@@ -130,6 +133,7 @@ describe('DashboardLoader', () => {
const rowWithPanel = createPanelJSONFixture({
title: 'Row with panel',
type: 'row',
+ id: 10,
collapsed: false,
gridPos: {
h: 1,
@@ -182,6 +186,7 @@ describe('DashboardLoader', () => {
expect(body.state.children[1]).toBeInstanceOf(SceneGridRow);
const rowWithPanelsScene = body.state.children[1] as SceneGridRow;
expect(rowWithPanelsScene.state.title).toBe(rowWithPanel.title);
+ expect(rowWithPanelsScene.state.key).toBe('panel-10');
expect(rowWithPanelsScene.state.children).toHaveLength(1);
// Panel within row
expect(rowWithPanelsScene.state.children[0]).toBeInstanceOf(SceneGridItem);
@@ -410,6 +415,7 @@ describe('DashboardLoader', () => {
hide: 0,
});
});
+
it('should migrate query variable', () => {
const variable = {
allValue: null,
@@ -615,6 +621,22 @@ describe('DashboardLoader', () => {
expect(() => createSceneVariableFromVariableModel(variable)).toThrow();
});
});
+
+ describe('Repeating rows', () => {
+ it('Should build correct scene model', () => {
+ const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as any, meta: {} });
+ const body = scene.state.body as SceneGridLayout;
+ const row2 = body.state.children[1] as SceneGridRow;
+
+ expect(row2.state.$behaviors?.[0]).toBeInstanceOf(RowRepeaterBehavior);
+
+ const repeatBehavior = row2.state.$behaviors?.[0] as RowRepeaterBehavior;
+ expect(repeatBehavior.state.variableName).toBe('server');
+
+ const lastRow = body.state.children[body.state.children.length - 1] as SceneGridRow;
+ expect(lastRow.state.isCollapsed).toBe(true);
+ });
+ });
});
function buildGridItemForTest(saveModel: Partial): { gridItem: SceneGridItem; vizPanel: VizPanel } {
diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts
index f52f74fa8e3..34600d74f8f 100644
--- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts
@@ -35,6 +35,7 @@ import { LibraryVizPanel } from '../scene/LibraryVizPanel';
import { panelMenuBehavior } from '../scene/PanelMenuBehavior';
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
import { PanelTimeRange } from '../scene/PanelTimeRange';
+import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
import { createPanelDataProvider } from '../utils/createPanelDataProvider';
import { getVizPanelKeyForPanelId } from '../utils/utils';
@@ -67,14 +68,7 @@ export function createSceneObjectsForPanels(oldPanels: PanelModel[]): SceneGridI
if (!currentRow) {
if (Boolean(panel.collapsed)) {
// collapsed rows contain their panels within the row model
- panels.push(
- new SceneGridRow({
- title: panel.title,
- isCollapsed: true,
- y: panel.gridPos.y,
- children: panel.panels ? panel.panels.map(buildGridItemForPanel) : [],
- })
- );
+ panels.push(createRowFromPanelModel(panel, []));
} else {
// indicate new row to be processed
currentRow = panel;
@@ -83,13 +77,7 @@ export function createSceneObjectsForPanels(oldPanels: PanelModel[]): SceneGridI
// when a row has been processed, and we hit a next one for processing
if (currentRow.id !== panel.id) {
// commit previous row panels
- panels.push(
- new SceneGridRow({
- title: currentRow!.title,
- y: currentRow.gridPos.y,
- children: currentRowPanels,
- })
- );
+ panels.push(createRowFromPanelModel(currentRow, currentRowPanels));
currentRow = panel;
currentRowPanels = [];
@@ -121,18 +109,43 @@ export function createSceneObjectsForPanels(oldPanels: PanelModel[]): SceneGridI
// commit a row if it's the last one
if (currentRow) {
- panels.push(
- new SceneGridRow({
- title: currentRow!.title,
- y: currentRow.gridPos.y,
- children: currentRowPanels,
- })
- );
+ panels.push(createRowFromPanelModel(currentRow, currentRowPanels));
}
return panels;
}
+function createRowFromPanelModel(row: PanelModel, content: SceneGridItemLike[]): SceneGridItemLike {
+ if (Boolean(row.collapsed)) {
+ if (row.panels) {
+ content = row.panels.map(buildGridItemForPanel);
+ }
+ }
+
+ let behaviors: SceneObject[] | undefined;
+ let children = content;
+
+ if (row.repeat) {
+ // For repeated rows the children are stored in the behavior
+ children = [];
+ behaviors = [
+ new RowRepeaterBehavior({
+ variableName: row.repeat,
+ sources: content,
+ }),
+ ];
+ }
+
+ return new SceneGridRow({
+ key: getVizPanelKeyForPanelId(row.id),
+ title: row.title,
+ y: row.gridPos.y,
+ isCollapsed: row.collapsed,
+ children: children,
+ $behaviors: behaviors,
+ });
+}
+
export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel) {
let variables: SceneVariableSet | undefined = undefined;
diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts
index 36e3689738b..774bc109778 100644
--- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts
@@ -1,13 +1,16 @@
-import { SceneGridItemLike } from '@grafana/scenes';
-import { Panel } from '@grafana/schema';
+import { MultiValueVariable, SceneGridItemLike, SceneGridLayout, SceneGridRow, SceneVariable } from '@grafana/scenes';
+import { Panel, RowPanel } from '@grafana/schema';
import { PanelModel } from 'app/features/dashboard/state';
+import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
+
import dashboard_to_load1 from './testfiles/dashboard_to_load1.json';
+import repeatingRowsAndPanelsDashboardJson from './testfiles/repeating_rows_and_panels.json';
import { buildGridItemForPanel, transformSaveModelToScene } from './transformSaveModelToScene';
import { gridItemToPanel, transformSceneToSaveModel } from './transformSceneToSaveModel';
describe('transformSceneToSaveModel', () => {
- describe('Given a scene', () => {
+ describe('Given a simple scene', () => {
it('Should transform back to peristed model', () => {
const scene = transformSaveModelToScene({ dashboard: dashboard_to_load1 as any, meta: {} });
const saveModel = transformSceneToSaveModel(scene);
@@ -16,6 +19,40 @@ describe('transformSceneToSaveModel', () => {
});
});
+ describe('Given a scene with rows', () => {
+ it('Should transform back to peristed model', () => {
+ const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as any, meta: {} });
+ const saveModel = transformSceneToSaveModel(scene);
+ const row2: RowPanel = saveModel.panels![2] as RowPanel;
+
+ expect(row2.type).toBe('row');
+ expect(row2.repeat).toBe('server');
+ expect(saveModel).toMatchSnapshot();
+ });
+
+ it('Should remove repeated rows in save model', () => {
+ const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as any, meta: {} });
+
+ const variable = scene.state.$variables?.state.variables[0] as MultiValueVariable;
+ variable.changeValueTo(['a', 'b', 'c']);
+
+ const grid = scene.state.body as SceneGridLayout;
+ const rowWithRepeat = grid.state.children[1] as SceneGridRow;
+ const rowRepeater = rowWithRepeat.state.$behaviors![0] as RowRepeaterBehavior;
+
+ // trigger row repeater
+ rowRepeater.variableDependency?.variableUpdatesCompleted(new Set([variable]));
+
+ // Make sure the repeated rows have been added to runtime scene model
+ expect(grid.state.children.length).toBe(5);
+
+ const saveModel = transformSceneToSaveModel(scene);
+ const rows = saveModel.panels!.filter((p) => p.type === 'row');
+ // Verify the save model does not contain any repeated rows
+ expect(rows.length).toBe(3);
+ });
+ });
+
describe('Panel options', () => {
it('Given panel with time override', () => {
const gridItem = buildGridItemFromPanelSchema({
diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
index 0745c47af55..6ef94c84f37 100644
--- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
+++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts
@@ -1,10 +1,11 @@
-import { SceneGridItem, SceneGridItemLike, SceneGridLayout, VizPanel } from '@grafana/scenes';
-import { Dashboard, defaultDashboard, FieldConfigSource, Panel } from '@grafana/schema';
+import { SceneGridItem, SceneGridItemLike, SceneGridLayout, SceneGridRow, VizPanel } from '@grafana/scenes';
+import { Dashboard, defaultDashboard, FieldConfigSource, Panel, RowPanel } from '@grafana/schema';
import { sortedDeepCloneWithoutNulls } from 'app/core/utils/object';
import { DashboardScene } from '../scene/DashboardScene';
import { PanelRepeaterGridItem } from '../scene/PanelRepeaterGridItem';
import { PanelTimeRange } from '../scene/PanelTimeRange';
+import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior';
import { getPanelIdForVizPanel } from '../utils/utils';
export function transformSceneToSaveModel(scene: DashboardScene): Dashboard {
@@ -18,6 +19,14 @@ export function transformSceneToSaveModel(scene: DashboardScene): Dashboard {
if (child instanceof SceneGridItem) {
panels.push(gridItemToPanel(child));
}
+
+ if (child instanceof SceneGridRow) {
+ // Skip repeat clones
+ if (child.state.key!.indexOf('-clone-') > 0) {
+ continue;
+ }
+ gridRowToSaveModel(child, panels);
+ }
}
}
@@ -98,3 +107,37 @@ export function gridItemToPanel(gridItem: SceneGridItemLike): Panel {
return panel;
}
+
+export function gridRowToSaveModel(gridRow: SceneGridRow, panelsArray: Array) {
+ const rowPanel: RowPanel = {
+ type: 'row',
+ id: getPanelIdForVizPanel(gridRow),
+ title: gridRow.state.title,
+ gridPos: {
+ x: gridRow.state.x ?? 0,
+ y: gridRow.state.y ?? 0,
+ w: gridRow.state.width ?? 24,
+ h: gridRow.state.height ?? 1,
+ },
+ collapsed: Boolean(gridRow.state.isCollapsed),
+ panels: [],
+ };
+
+ if (gridRow.state.$behaviors?.length) {
+ const behavior = gridRow.state.$behaviors[0];
+
+ if (behavior instanceof RowRepeaterBehavior) {
+ rowPanel.repeat = behavior.state.variableName;
+ }
+ }
+
+ panelsArray.push(rowPanel);
+
+ const panelsInsideRow = gridRow.state.children.map(gridItemToPanel);
+
+ if (gridRow.state.isCollapsed) {
+ rowPanel.panels = panelsInsideRow;
+ } else {
+ panelsArray.push(...panelsInsideRow);
+ }
+}
diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts
index 2ede1d237f1..c6fabed5e62 100644
--- a/public/app/features/dashboard-scene/utils/utils.ts
+++ b/public/app/features/dashboard-scene/utils/utils.ts
@@ -1,10 +1,10 @@
-import { sceneGraph, SceneObject, VizPanel } from '@grafana/scenes';
+import { MultiValueVariable, sceneGraph, SceneObject, VizPanel } from '@grafana/scenes';
export function getVizPanelKeyForPanelId(panelId: number) {
return `panel-${panelId}`;
}
-export function getPanelIdForVizPanel(panel: VizPanel): number {
+export function getPanelIdForVizPanel(panel: SceneObject): number {
return parseInt(panel.state.key!.replace('panel-', ''), 10);
}
@@ -66,3 +66,19 @@ export function forceRenderChildren(model: SceneObject, recursive?: boolean) {
forceRenderChildren(child, recursive);
});
}
+
+export function getMultiVariableValues(variable: MultiValueVariable) {
+ const { value, text, options } = variable.state;
+
+ if (variable.hasAllValue()) {
+ return {
+ values: options.map((o) => o.value),
+ texts: options.map((o) => o.label),
+ };
+ }
+
+ return {
+ values: Array.isArray(value) ? value : [value],
+ texts: Array.isArray(text) ? text : [text],
+ };
+}
diff --git a/public/app/features/scenes/scenes/index.tsx b/public/app/features/scenes/scenes/index.tsx
index 34c2a83d4b4..b5bcf608dc9 100644
--- a/public/app/features/scenes/scenes/index.tsx
+++ b/public/app/features/scenes/scenes/index.tsx
@@ -4,7 +4,7 @@ import { getGridWithMultipleTimeRanges } from './gridMultiTimeRange';
import { getMultipleGridLayoutTest } from './gridMultiple';
import { getGridWithMultipleData } from './gridWithMultipleData';
import { getQueryVariableDemo } from './queryVariableDemo';
-import { getRepeatingPanelsDemo } from './repeatingPanels';
+import { getRepeatingPanelsDemo, getRepeatingRowsDemo } from './repeatingPanels';
import { getSceneWithRows } from './sceneWithRows';
import { getTransformationsDemo } from './transformations';
import { getVariablesDemo, getVariablesDemoWithAll } from './variablesDemo';
@@ -22,6 +22,7 @@ export function getScenes(): SceneDef[] {
{ title: 'Variables', getScene: getVariablesDemo },
{ title: 'Variables with All values', getScene: getVariablesDemoWithAll },
{ title: 'Variables - Repeating panels', getScene: getRepeatingPanelsDemo },
+ { title: 'Variables - Repeating rows', getScene: getRepeatingRowsDemo },
{ title: 'Query variable', getScene: getQueryVariableDemo },
{ title: 'Transformations demo', getScene: getTransformationsDemo },
];
diff --git a/public/app/features/scenes/scenes/repeatingPanels.tsx b/public/app/features/scenes/scenes/repeatingPanels.tsx
index c510b24f6d5..81442507e9f 100644
--- a/public/app/features/scenes/scenes/repeatingPanels.tsx
+++ b/public/app/features/scenes/scenes/repeatingPanels.tsx
@@ -8,9 +8,11 @@ import {
PanelBuilders,
SceneGridLayout,
SceneControlsSpacer,
+ SceneGridRow,
} from '@grafana/scenes';
import { VariableRefresh } from '@grafana/schema';
import { PanelRepeaterGridItem } from 'app/features/dashboard-scene/scene/PanelRepeaterGridItem';
+import { RowRepeaterBehavior } from 'app/features/dashboard-scene/scene/RowRepeaterBehavior';
import { DashboardScene } from '../../dashboard-scene/scene/DashboardScene';
@@ -38,7 +40,7 @@ export function getRepeatingPanelsDemo(): DashboardScene {
refresh: VariableRefresh.onTimeRangeChanged,
optionsToReturn: [
{ label: 'A', value: 'A' },
- { label: 'B', value: 'C' },
+ { label: 'B', value: 'B' },
],
options: [],
$behaviors: [changeVariable],
@@ -78,27 +80,24 @@ export function getRepeatingPanelsDemo(): DashboardScene {
function changeVariable(variable: TestVariable) {
const sub = variable.subscribeToState((state, old) => {
if (!state.loading && old.loading) {
- setTimeout(() => {
- if (variable.state.query === 'AB') {
- variable.setState({
- query: 'ABC',
- optionsToReturn: [
- { label: 'A', value: 'A' },
- { label: 'B', value: 'B' },
- { label: 'C', value: 'C' },
- ],
- });
- } else {
- variable.setState({
- query: 'AB',
- optionsToReturn: [
- { label: 'A', value: 'A' },
- { label: 'B', value: 'B' },
- ],
- });
- }
- });
- return;
+ if (variable.state.optionsToReturn.length === 2) {
+ variable.setState({
+ query: 'ABC',
+ optionsToReturn: [
+ { label: 'A', value: 'A' },
+ { label: 'B', value: 'B' },
+ { label: 'C', value: 'C' },
+ ],
+ });
+ } else {
+ variable.setState({
+ query: 'AB',
+ optionsToReturn: [
+ { label: 'A', value: 'A' },
+ { label: 'B', value: 'B' },
+ ],
+ });
+ }
}
});
@@ -106,3 +105,87 @@ function changeVariable(variable: TestVariable) {
sub.unsubscribe();
};
}
+
+export function getRepeatingRowsDemo(): DashboardScene {
+ return new DashboardScene({
+ title: 'Variables - Repeating rows',
+ $variables: new SceneVariableSet({
+ variables: [
+ new TestVariable({
+ name: 'server',
+ query: 'AB',
+ value: ['A', 'B', 'C'],
+ text: ['A', 'B', 'C'],
+ delayMs: 2000,
+ isMulti: true,
+ includeAll: true,
+ refresh: VariableRefresh.onTimeRangeChanged,
+ optionsToReturn: [
+ { label: 'A', value: 'A' },
+ { label: 'B', value: 'B' },
+ { label: 'C', value: 'C' },
+ ],
+ options: [],
+ //$behaviors: [changeVariable],
+ }),
+ new TestVariable({
+ name: 'pod',
+ query: 'AB',
+ value: ['Mu', 'Ma', 'Mi'],
+ text: ['Mu', 'Ma', 'Mi'],
+ delayMs: 2000,
+ isMulti: true,
+ includeAll: true,
+ refresh: VariableRefresh.onTimeRangeChanged,
+ optionsToReturn: [
+ { label: 'Mu', value: 'Mu' },
+ { label: 'Ma', value: 'Ma' },
+ { label: 'Mi', value: 'Mi' },
+ ],
+ options: [],
+ }),
+ ],
+ }),
+ body: new SceneGridLayout({
+ isDraggable: true,
+ isResizable: true,
+ children: [
+ new SceneGridRow({
+ title: 'Row $server',
+ key: 'Row A',
+ isCollapsed: false,
+ y: 0,
+ x: 0,
+ $behaviors: [
+ new RowRepeaterBehavior({
+ variableName: 'server',
+ sources: [
+ new PanelRepeaterGridItem({
+ variableName: 'pod',
+ x: 0,
+ y: 0,
+ width: 24,
+ height: 5,
+ itemHeight: 5,
+ //@ts-expect-error
+ source: PanelBuilders.timeseries()
+ .setTitle('server = $server, pod = $pod')
+ .setData(getQueryRunnerWithRandomWalkQuery({ alias: 'server = $server, pod = $pod' }))
+ .build(),
+ }),
+ ],
+ }),
+ ],
+ }),
+ ],
+ }),
+ $timeRange: new SceneTimeRange(),
+ actions: [],
+ controls: [
+ new VariableValueSelectors({}),
+ new SceneControlsSpacer(),
+ new SceneTimePicker({}),
+ new SceneRefreshPicker({}),
+ ],
+ });
+}
diff --git a/yarn.lock b/yarn.lock
index 81acbeeda27..1e530ed3683 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3939,9 +3939,9 @@ __metadata:
languageName: unknown
linkType: soft
-"@grafana/scenes@npm:^0.29.0":
- version: 0.29.0
- resolution: "@grafana/scenes@npm:0.29.0"
+"@grafana/scenes@npm:^1.1.1":
+ version: 1.1.1
+ resolution: "@grafana/scenes@npm:1.1.1"
dependencies:
"@grafana/e2e-selectors": 10.0.2
react-grid-layout: 1.3.4
@@ -3953,7 +3953,7 @@ __metadata:
"@grafana/runtime": 10.0.3
"@grafana/schema": 10.0.3
"@grafana/ui": 10.0.3
- checksum: 8a91ea0290d54c5c081595e85f853b14af90468da3d85b5cd83e26d24d4fc84cceea9be930aa9239439ff3af7388ae3f5bebe1973214c686f4cf143a64752548
+ checksum: 6405998a40e38f088443f5d4b1f5ea1f73e5bc0d08216e4aaccf8ff0b68ec4c3d691430857f357d3eed335dee0dc2e24c41c5c2f286fc7fdd32375382ad3eafe
languageName: node
linkType: hard
@@ -19288,7 +19288,7 @@ __metadata:
"@grafana/lezer-traceql": 0.0.5
"@grafana/monaco-logql": ^0.0.7
"@grafana/runtime": "workspace:*"
- "@grafana/scenes": ^0.29.0
+ "@grafana/scenes": ^1.1.1
"@grafana/schema": "workspace:*"
"@grafana/tsconfig": ^1.3.0-rc1
"@grafana/ui": "workspace:*"