From b3f173f15319544cb1e4f8cd5cb72879b3505f8a Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Fri, 3 May 2024 10:50:10 +0100 Subject: [PATCH] Dashboard: Fix issue where changes to panel in row weren't detected in panel edit (#87195) Closes #87171 --- .../transformSceneToSaveModel.test.ts | 107 ++++++++++++++---- .../transformSceneToSaveModel.ts | 80 +++++++------ 2 files changed, 134 insertions(+), 53 deletions(-) diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts index c12a7bf83cec..0f506c11c4bc 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts @@ -15,7 +15,7 @@ import { } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { getPluginLinkExtensions, setPluginImportUtils } from '@grafana/runtime'; -import { MultiValueVariable, SceneGridLayout, SceneGridRow, VizPanel } from '@grafana/scenes'; +import { MultiValueVariable, SceneGridLayout, SceneGridRow, SceneTimeRange, VizPanel } from '@grafana/scenes'; import { Dashboard, LoadingState, Panel, RowPanel, VariableRefresh } from '@grafana/schema'; import { PanelModel } from 'app/features/dashboard/state'; import { getTimeRange } from 'app/features/dashboard/utils/timeRange'; @@ -25,11 +25,12 @@ import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { buildPanelEditScene } from '../panel-edit/PanelEditor'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardGridItem } from '../scene/DashboardGridItem'; +import { DashboardScene } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; import { NEW_LINK } from '../settings/links/utils'; import { activateFullSceneTree, buildPanelRepeaterScene } from '../utils/test-utils'; -import { findVizPanelByKey, getVizPanelKeyForPanelId } from '../utils/utils'; +import { getVizPanelKeyForPanelId } from '../utils/utils'; import { GRAFANA_DATASOURCE_REF } from './const'; import dashboard_to_load1 from './testfiles/dashboard_to_load1.json'; @@ -787,7 +788,7 @@ describe('transformSceneToSaveModel', () => { activateFullSceneTree(scene); expect(repeater.state.repeatedPanels?.length).toBe(2); - const result = panelRepeaterToPanels(repeater, true); + const result = panelRepeaterToPanels(repeater, undefined, true); expect(result).toHaveLength(2); @@ -843,7 +844,7 @@ describe('transformSceneToSaveModel', () => { ); activateFullSceneTree(scene); - const result = panelRepeaterToPanels(repeater, true); + const result = panelRepeaterToPanels(repeater, undefined, true); expect(result).toHaveLength(1); @@ -868,7 +869,7 @@ describe('transformSceneToSaveModel', () => { activateFullSceneTree(scene); let panels: Panel[] = []; - gridRowToSaveModel(row, panels, true); + gridRowToSaveModel(row, panels, undefined, true); expect(panels).toHaveLength(2); expect(panels[0].repeat).toBe('handler'); @@ -896,7 +897,7 @@ describe('transformSceneToSaveModel', () => { activateFullSceneTree(scene); let panels: Panel[] = []; - gridRowToSaveModel(row, panels, true); + gridRowToSaveModel(row, panels, undefined, true); expect(panels[0].repeat).toBe('handler'); @@ -1008,21 +1009,87 @@ describe('transformSceneToSaveModel', () => { describe('Given a scene with an open panel editor', () => { it('should persist changes to panel model', async () => { - const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as any, meta: {} }); - activateFullSceneTree(scene); - await new Promise((r) => setTimeout(r, 1)); - scene.onEnterEditMode(); - const panel = findVizPanelByKey(scene, '15')!; - scene.setState({ editPanel: buildPanelEditScene(panel) }); - panel.onOptionsChange({ - mode: 'markdown', - code: { - language: 'plaintext', - showLineNumbers: false, - showMiniMap: false, - }, - content: 'new content', + const panel = new VizPanel({ + key: 'panel-1', + pluginId: 'text', }); + + const gridItem = new DashboardGridItem({ body: panel }); + + const editScene = buildPanelEditScene(panel); + const scene = new DashboardScene({ + editPanel: editScene, + isEditing: true, + body: new SceneGridLayout({ + children: [gridItem], + }), + $timeRange: new SceneTimeRange({ + from: 'now-6h', + to: 'now', + timeZone: '', + }), + }); + + editScene!.state.vizManager.state.panel.setState({ + options: { + mode: 'markdown', + code: { + language: 'plaintext', + showLineNumbers: false, + showMiniMap: false, + }, + content: 'new content', + }, + }); + activateFullSceneTree(scene); + const saveModel = transformSceneToSaveModel(scene); + expect((saveModel.panels![0] as any).options.content).toBe('new content'); + }); + + it('should persist changes to panel model in row', async () => { + const panel = new VizPanel({ + key: 'panel-1', + pluginId: 'text', + options: { + content: 'old content', + }, + }); + + const gridItem = new DashboardGridItem({ body: panel }); + + const editScene = buildPanelEditScene(panel); + const scene = new DashboardScene({ + editPanel: editScene, + isEditing: true, + body: new SceneGridLayout({ + children: [ + new SceneGridRow({ + key: '23', + isCollapsed: false, + children: [gridItem], + }), + ], + }), + $timeRange: new SceneTimeRange({ + from: 'now-6h', + to: 'now', + timeZone: '', + }), + }); + activateFullSceneTree(scene); + + editScene!.state.vizManager.state.panel.setState({ + options: { + mode: 'markdown', + code: { + language: 'plaintext', + showLineNumbers: false, + showMiniMap: false, + }, + content: 'new content', + }, + }); + const saveModel = transformSceneToSaveModel(scene); expect((saveModel.panels![1] as any).options.content).toBe('new content'); }); diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts index 6d918ba78cff..c6c0b3941a9e 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.ts @@ -33,7 +33,7 @@ import { GrafanaQueryType } from 'app/plugins/datasource/grafana/types'; import { DashboardDataLayerSet } from '../scene/DashboardDataLayerSet'; import { DashboardGridItem } from '../scene/DashboardGridItem'; -import { DashboardScene } from '../scene/DashboardScene'; +import { DashboardScene, DashboardSceneState } from '../scene/DashboardScene'; import { LibraryVizPanel } from '../scene/LibraryVizPanel'; import { PanelTimeRange } from '../scene/PanelTimeRange'; import { RowRepeaterBehavior } from '../scene/RowRepeaterBehavior'; @@ -57,25 +57,11 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa if (body instanceof SceneGridLayout) { for (const child of body.state.children) { if (child instanceof DashboardGridItem) { - let child_ = child; - // If we're saving while the panel editor is open, we need to persist those changes in the panel model - if ( - child.state.body instanceof VizPanel && - state.editPanel?.state.vizManager && - state.editPanel.state.vizManager.state.sourcePanel.resolve() === child.state.body - ) { - const childClone = child.clone(); - if (childClone.state.body instanceof VizPanel) { - state.editPanel.state.vizManager.commitChangesTo(childClone.state.body); - child_ = childClone; - } - } - // handle panel repeater scenario - if (child_.state.variableName) { - panels = panels.concat(panelRepeaterToPanels(child_, isSnapshot)); + if (child.state.variableName) { + panels = panels.concat(panelRepeaterToPanels(child, state, isSnapshot)); } else { - panels.push(gridItemToPanel(child_, isSnapshot)); + panels.push(gridItemToPanel(child, state, isSnapshot)); } } @@ -84,7 +70,7 @@ export function transformSceneToSaveModel(scene: DashboardScene, isSnapshot = fa if (child.state.key!.indexOf('-clone-') > 0 && !isSnapshot) { continue; } - gridRowToSaveModel(child, panels, isSnapshot); + gridRowToSaveModel(child, panels, state, isSnapshot); } } } @@ -169,7 +155,11 @@ export function libraryVizPanelToPanel(libPanel: LibraryVizPanel, gridPos: GridP } as Panel; } -export function gridItemToPanel(gridItem: DashboardGridItem, isSnapshot = false): Panel { +export function gridItemToPanel( + gridItem: DashboardGridItem, + sceneState?: DashboardSceneState, + isSnapshot = false +): Panel { let vizPanel: VizPanel | undefined; let x = 0, y = 0, @@ -186,21 +176,36 @@ export function gridItemToPanel(gridItem: DashboardGridItem, isSnapshot = false) return libraryVizPanelToPanel(gridItem.state.body, { x, y, w, h }); } - if (!(gridItem.state.body instanceof VizPanel)) { + let gridItem_ = gridItem; + + // If we're saving while the panel editor is open, we need to persist those changes in the panel model + if ( + sceneState && + sceneState.editPanel?.state.vizManager && + sceneState.editPanel.state.vizManager.state.sourcePanel.resolve() === gridItem.state.body + ) { + const gridItemClone = gridItem.clone(); + if (gridItemClone.state.body instanceof VizPanel) { + sceneState.editPanel.state.vizManager.commitChangesTo(gridItemClone.state.body); + gridItem_ = gridItemClone; + } + } + + if (!(gridItem_.state.body instanceof VizPanel)) { throw new Error('DashboardGridItem body expected to be VizPanel'); } - vizPanel = gridItem.state.body; - x = gridItem.state.x ?? 0; - y = gridItem.state.y ?? 0; - w = gridItem.state.width ?? 0; - h = gridItem.state.height ?? 0; + vizPanel = gridItem_.state.body; + x = gridItem_.state.x ?? 0; + y = gridItem_.state.y ?? 0; + w = gridItem_.state.width ?? 0; + h = gridItem_.state.height ?? 0; if (!vizPanel) { throw new Error('Unsupported grid item type'); } - const panel: Panel = vizPanelToPanel(vizPanel, { x, y, h, w }, isSnapshot, gridItem); + const panel: Panel = vizPanelToPanel(vizPanel, { x, y, h, w }, isSnapshot, gridItem_); return panel; } @@ -328,9 +333,13 @@ function vizPanelDataToPanel( return panel; } -export function panelRepeaterToPanels(repeater: DashboardGridItem, isSnapshot = false): Panel[] { +export function panelRepeaterToPanels( + repeater: DashboardGridItem, + sceneState?: DashboardSceneState, + isSnapshot = false +): Panel[] { if (!isSnapshot) { - return [gridItemToPanel(repeater)]; + return [gridItemToPanel(repeater, sceneState)]; } else { if (repeater.state.body instanceof LibraryVizPanel) { const { x = 0, y = 0, width: w = 0, height: h = 0 } = repeater.state; @@ -387,7 +396,12 @@ export function panelRepeaterToPanels(repeater: DashboardGridItem, isSnapshot = } } -export function gridRowToSaveModel(gridRow: SceneGridRow, panelsArray: Array, isSnapshot = false) { +export function gridRowToSaveModel( + gridRow: SceneGridRow, + panelsArray: Array, + sceneState?: DashboardSceneState, + isSnapshot = false +) { const collapsed = Boolean(gridRow.state.isCollapsed); const rowPanel: RowPanel = { type: 'row', @@ -437,10 +451,10 @@ export function gridRowToSaveModel(gridRow: SceneGridRow, panelsArray: Array