From b0e9307d157dd7803e8b3e7654a3d61bbf10be35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 19 Aug 2025 16:42:15 +0200 Subject: [PATCH] Dashboards: Fixing saving and viewing snapshots for repeated panels (#109856) --- .../transformSaveModelToScene.test.ts | 27 +++++++++++++++++++ .../transformSaveModelToScene.ts | 16 +++++++++++ .../transformSceneToSaveModel.ts | 5 ++-- .../features/dashboard-scene/utils/utils.ts | 4 +-- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index 7e9302634cd..46b5324bbeb 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -999,6 +999,9 @@ describe('transformSaveModelToScene', () => { config: {}, }, ], + scopedVars: { + var1: { value: 'value1', text: 'text1' }, + }, }, ], }) as Panel; @@ -1021,6 +1024,30 @@ describe('transformSaveModelToScene', () => { { config: {}, name: 'Field 2', type: 'number' }, ]); }); + + it('should translate scopedVars to local variable value', () => { + const panel = createPanelSaveModel({ + title: 'test', + gridPos: { x: 1, y: 0, w: 12, h: 8 }, + targets: [ + { + queryType: 'snapshot', + }, + ], + // @ts-ignore + scopedVars: { + var1: { value: 'value1', text: 'text1' }, + }, + }) as Panel; + + const oldPanelModel = new PanelModel(panel); + const scenePanel = buildGridItemForPanel(oldPanelModel); + const vizPanel = scenePanel.state.body; + + expect(vizPanel.state.$variables?.state.variables[0].state.name).toBe('var1'); + expect(vizPanel.state.$variables?.state.variables[0].getValue()).toBe('value1'); + expect(vizPanel.state.$variables?.state.variables[0].getValueText?.()).toBe('text1'); + }); }); }); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 8657af91ba1..cf29d4fdf8c 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -18,6 +18,7 @@ import { SceneDataLayerProvider, UserActionEvent, SceneObjectState, + LocalValueVariable, } from '@grafana/scenes'; import { isWeekStart } from '@grafana/ui'; import { K8S_V1_DASHBOARD_API_CONFIG } from 'app/features/dashboard/api/v1'; @@ -443,6 +444,21 @@ export function buildGridItemForPanel(panel: PanelModel): DashboardGridItem { }); } + if (panel.scopedVars && panel.targets?.[0]?.queryType === 'snapshot') { + vizPanelState.$variables = new SceneVariableSet({ + variables: Object.entries(panel.scopedVars).map( + ([key, variable]) => + new LocalValueVariable({ + name: key, + value: variable?.value, + text: variable?.text, + isMulti: true, + includeAll: true, + }) + ), + }); + } + const body = new VizPanel(vizPanelState); return new DashboardGridItem({ diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index 8704ff7625c..f5b9a700bd8 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -38,6 +38,7 @@ import { DefaultGridLayoutManager } from '../scene/layout-default/DefaultGridLay import { RowRepeaterBehavior } from '../scene/layout-default/RowRepeaterBehavior'; import { isClonedKey } from '../utils/clone'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; +import { djb2Hash } from '../utils/djb2Hash'; import { calculateGridItemDimensions, getLibraryPanelBehavior, @@ -337,7 +338,7 @@ export function panelRepeaterToPanels(repeater: DashboardGridItem, isSnapshot = y = 0; if (repeater.state.repeatDirection === 'v') { x = repeater.state.x!; - y = index * h; + y = repeater.state.y! + index * h; } else { x = (index % columnCount) * w; y = repeater.state.y! + Math.floor(index / columnCount) * h; @@ -348,7 +349,7 @@ export function panelRepeaterToPanels(repeater: DashboardGridItem, isSnapshot = const localVariable = panel.state.$variables!.getByName(repeater.state.variableName!) as LocalValueVariable; const result: Panel = { - id: getPanelIdForVizPanel(panel), + id: djb2Hash(panel.state.key!), type: panel.state.pluginId, title: panel.state.title, gridPos, diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 1ab2c72969a..9db459df2ce 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -373,8 +373,8 @@ export function getLibraryPanelBehavior(vizPanel: VizPanel): LibraryPanelBehavio } export function calculateGridItemDimensions(repeater: DashboardGridItem) { - const rowCount = Math.ceil(repeater.state.repeatedPanels!.length / repeater.getMaxPerRow()); - const columnCount = Math.ceil(repeater.state.repeatedPanels!.length / rowCount); + const rowCount = Math.ceil(repeater.getPanelCount() / repeater.getMaxPerRow()); + const columnCount = Math.ceil(repeater.getPanelCount() / rowCount); const w = 24 / columnCount; const h = repeater.state.itemHeight ?? 10; return { h, w, columnCount };