From 6bd1041cda9b197daf699dfd519cddd5d005f513 Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Fri, 14 Feb 2025 17:42:10 +0200 Subject: [PATCH] Dashboard: Fix panel edits for repeats (#100658) --- e2e/various-suite/solo-route.spec.ts | 4 +- .../scene/DashboardDatasourceBehaviour.tsx | 5 +- .../scene/DashboardSceneUrlSync.ts | 4 +- .../layout-default/RowRepeaterBehavior.ts | 9 +- .../features/dashboard-scene/utils/utils.ts | 105 +++++++++++++++++- .../datasource/dashboard/datasource.ts | 5 +- 6 files changed, 116 insertions(+), 16 deletions(-) diff --git a/e2e/various-suite/solo-route.spec.ts b/e2e/various-suite/solo-route.spec.ts index c7f90f8cd36..415257ead7c 100644 --- a/e2e/various-suite/solo-route.spec.ts +++ b/e2e/various-suite/solo-route.spec.ts @@ -22,7 +22,7 @@ describe('Solo Route', () => { cy.contains('uplot-main-div').should('not.exist'); }); - /*it('Can view solo repeated panel in scenes', () => { + it('Can view solo repeated panel in scenes', () => { // open Panel Tests - Graph NG e2e.pages.SoloPanel.visit( 'templating-repeating-panels/templating-repeating-panels?orgId=1&from=1699934989607&to=1699956589607&panelId=panel-2-clone-0&__feature.dashboardSceneSolo=true' @@ -30,7 +30,7 @@ describe('Solo Route', () => { e2e.components.Panels.Panel.title('server=A').should('exist'); cy.contains('uplot-main-div').should('not.exist'); - });*/ + }); it('Can view solo in repeated row and panel in scenes', () => { // open Panel Tests - Graph NG diff --git a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx index 940b4077d8d..a35663eeaac 100644 --- a/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardDatasourceBehaviour.tsx @@ -5,7 +5,7 @@ import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard/constan import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { - findVizPanelByKey, + findOriginalVizPanelByKey, getDashboardSceneFor, getLibraryPanelBehavior, getQueryRunnerFor, @@ -53,7 +53,8 @@ export class DashboardDatasourceBehaviour extends SceneObjectBase child.state.key!.includes(rowKey)); + const index = allChildren.findIndex( + (child) => child instanceof SceneGridRow && getOriginalKey(child.state.key!) === getOriginalKey(rowKey) + ); if (index === -1) { throw new Error('RowRepeaterBehavior: Parent row not found in layout children'); @@ -286,7 +289,9 @@ function updateLayout(layout: SceneGridLayout, rows: SceneGridRow[], maxYOfRows: } function getLayoutChildrenFilterOutRepeatClones(layout: SceneGridLayout, rowKey: string) { - return layout.state.children.filter((child) => !isClonedKeyOf(child.state.key!, rowKey)); + return layout.state.children.filter( + (child) => !(child instanceof SceneGridRow) || !isClonedKeyOf(getLastKeyFromClone(child.state.key!), rowKey) + ); } function ensureUniqueKeys(item: SceneGridItemLike, ancestors: string) { diff --git a/public/app/features/dashboard-scene/utils/utils.ts b/public/app/features/dashboard-scene/utils/utils.ts index 5e52a1d517f..60768226dfc 100644 --- a/public/app/features/dashboard-scene/utils/utils.ts +++ b/public/app/features/dashboard-scene/utils/utils.ts @@ -21,7 +21,7 @@ import { panelMenuBehavior } from '../scene/PanelMenuBehavior'; import { DashboardGridItem } from '../scene/layout-default/DashboardGridItem'; import { DashboardLayoutManager, isDashboardLayoutManager } from '../scene/types/DashboardLayoutManager'; -import { getOriginalKey, isClonedKey } from './clone'; +import { containsCloneKey, getLastKeyFromClone, getOriginalKey, isInCloneChain } from './clone'; export const NEW_PANEL_HEIGHT = 8; export const NEW_PANEL_WIDTH = 12; @@ -68,12 +68,63 @@ function findVizPanelInternal(scene: SceneObject, key: string | undefined): VizP return true; } - // It might be possible to have the keys changed in the meantime from `panel-2` to `panel-2-clone-0` - // We need to check this as well - const originalObjectKey = !isClonedKey(objKey) ? getOriginalKey(objKey) : objKey; - const originalKey = !isClonedKey(key) ? getOriginalKey(key) : key; + if (!(obj instanceof VizPanel)) { + return false; + } + + return false; + }); + + if (panel) { + if (panel instanceof VizPanel) { + return panel; + } else { + throw new Error(`Found panel with key ${key} but it was not a VizPanel`); + } + } - if (originalObjectKey === originalKey) { + return null; +} + +export function findOriginalVizPanelByKey(scene: SceneObject, key: string | undefined): VizPanel | null { + if (!key) { + return null; + } + + let panel: VizPanel | null = findOriginalVizPanelInternal(scene, key); + + if (panel) { + return panel; + } + + // Also try to find by panel id + const id = parseInt(key, 10); + if (isNaN(id)) { + return null; + } + + const panelId = getVizPanelKeyForPanelId(id); + panel = findVizPanelInternal(scene, panelId); + + if (panel) { + return panel; + } + + panel = findOriginalVizPanelInternal(scene, panelId); + + return panel; +} + +function findOriginalVizPanelInternal(scene: SceneObject, key: string | undefined): VizPanel | null { + if (!key) { + return null; + } + + const panel = sceneGraph.findObject(scene, (obj) => { + const objKey = obj.state.key!; + + // Compare the original keys + if (objKey === key || (!isInCloneChain(objKey) && getOriginalKey(objKey) === getOriginalKey(key))) { return true; } @@ -95,6 +146,48 @@ function findVizPanelInternal(scene: SceneObject, key: string | undefined): VizP return null; } +export function findEditPanel(scene: SceneObject, key: string | undefined): VizPanel | null { + if (!key) { + return null; + } + + // First we try to find the non-cloned panel + // This means it is either in not in a repeat chain or every item in the chain is not a clone + let panel: SceneObject | null = findOriginalVizPanelByKey(scene, key); + if (!panel || !panel.state.key) { + return null; + } + + // Get the actual panel key, without any of the ancestors + const panelKey = getLastKeyFromClone(panel.state.key); + + // If the panel contains clone in the key, this means it's a repeated panel, and we need to find the original panel + if (containsCloneKey(panelKey)) { + // Get the original key of the panel that we are looking for + const originalPanelKey = getOriginalKey(panelKey); + // Start the search from the parent to avoid unnecessary checks + // The parent usually is the grid item where the referenced panel is also located + panel = sceneGraph.findObject(panel.parent ?? scene, (sceneObject) => { + if (!sceneObject.state.key || isInCloneChain(sceneObject.state.key)) { + return false; + } + + const currentLastKey = getLastKeyFromClone(sceneObject.state.key); + if (containsCloneKey(currentLastKey)) { + return false; + } + + return getOriginalKey(currentLastKey) === originalPanelKey; + }); + } + + if (!(panel instanceof VizPanel)) { + return null; + } + + return panel; +} + /** * Force re-render children. This is useful in some edge case scenarios when * children deep down the scene graph needs to be re-rendered when some parent state change. diff --git a/public/app/plugins/datasource/dashboard/datasource.ts b/public/app/plugins/datasource/dashboard/datasource.ts index b80fe5594a2..16fda375e6e 100644 --- a/public/app/plugins/datasource/dashboard/datasource.ts +++ b/public/app/plugins/datasource/dashboard/datasource.ts @@ -15,7 +15,7 @@ import { import { SceneDataProvider, SceneDataTransformer, SceneObject } from '@grafana/scenes'; import { activateSceneObjectAndParentTree, - findVizPanelByKey, + findOriginalVizPanelByKey, getVizPanelKeyForPanelId, } from 'app/features/dashboard-scene/utils/utils'; @@ -109,7 +109,8 @@ export class DashboardDatasource extends DataSourceApi { } private findSourcePanel(scene: SceneObject, panelId: number) { - return findVizPanelByKey(scene, getVizPanelKeyForPanelId(panelId)); + // We're trying to find the original panel, not a cloned one, since `panelId` alone cannot resolve clones + return findOriginalVizPanelByKey(scene, getVizPanelKeyForPanelId(panelId)); } private emitFirstLoadedDataIfMixedDS(