From 293f514854294555670e76f910ed63c7c5514a24 Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Thu, 13 Feb 2025 11:58:28 +0200 Subject: [PATCH] Dashboard: Fix removing row repeats having indexes ending with 0 (#100487) --- .../RowRepeaterBehavior.test.tsx | 45 +++++++++++++++++++ .../RowItemRepeaterBehavior.test.tsx | 45 +++++++++++++++++++ .../dashboard-scene/utils/clone.test.ts | 14 ++++++ .../features/dashboard-scene/utils/clone.ts | 2 +- 4 files changed, 105 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.test.tsx b/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.test.tsx index 2e728f25a39..843f58b5640 100644 --- a/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/layout-default/RowRepeaterBehavior.test.tsx @@ -160,6 +160,51 @@ describe('RowRepeaterBehavior', () => { }); }); + describe('Given scene with variable with 15 values', () => { + let scene: DashboardScene, grid: SceneGridLayout; + let gridStateUpdates: unknown[]; + + beforeEach(async () => { + ({ scene, grid } = buildScene({ variableQueryTime: 0 }, [ + { label: 'A', value: 'A1' }, + { label: 'B', value: 'B1' }, + { label: 'C', value: 'C1' }, + { label: 'D', value: 'D1' }, + { label: 'E', value: 'E1' }, + { label: 'F', value: 'F1' }, + { label: 'G', value: 'G1' }, + { label: 'H', value: 'H1' }, + { label: 'I', value: 'I1' }, + { label: 'J', value: 'J1' }, + { label: 'K', value: 'K1' }, + { label: 'L', value: 'L1' }, + { label: 'M', value: 'M1' }, + { label: 'N', value: 'N1' }, + { label: 'O', value: 'O1' }, + ])); + + gridStateUpdates = []; + grid.subscribeToState((state) => gridStateUpdates.push(state)); + + activateFullSceneTree(scene); + await new Promise((r) => setTimeout(r, 1)); + }); + + it('Should handle second repeat cycle and update remove old repeats', async () => { + // should have 15 repeated rows (and the panel above + the row at the bottom) + expect(grid.state.children.length).toBe(17); + + // trigger another repeat cycle by changing the variable + const variable = scene.state.$variables!.state.variables[0] as TestVariable; + variable.changeValueTo(['B1', 'C1']); + + 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); + }); + }); + describe('Given scene empty row', () => { let scene: DashboardScene; let grid: SceneGridLayout; diff --git a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRepeaterBehavior.test.tsx b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRepeaterBehavior.test.tsx index d152fed3a64..ec5b4710089 100644 --- a/public/app/features/dashboard-scene/scene/layout-rows/RowItemRepeaterBehavior.test.tsx +++ b/public/app/features/dashboard-scene/scene/layout-rows/RowItemRepeaterBehavior.test.tsx @@ -104,6 +104,51 @@ describe('RowItemRepeaterBehavior', () => { }); }); + describe('Given scene with variable with 15 values', () => { + let scene: DashboardScene, layout: RowsLayoutManager; + let layoutStateUpdates: unknown[]; + + beforeEach(async () => { + ({ scene, layout } = buildScene({ variableQueryTime: 0 }, [ + { label: 'A', value: 'A1' }, + { label: 'B', value: 'B1' }, + { label: 'C', value: 'C1' }, + { label: 'D', value: 'D1' }, + { label: 'E', value: 'E1' }, + { label: 'F', value: 'F1' }, + { label: 'G', value: 'G1' }, + { label: 'H', value: 'H1' }, + { label: 'I', value: 'I1' }, + { label: 'J', value: 'J1' }, + { label: 'K', value: 'K1' }, + { label: 'L', value: 'L1' }, + { label: 'M', value: 'M1' }, + { label: 'N', value: 'N1' }, + { label: 'O', value: 'O1' }, + ])); + + layoutStateUpdates = []; + layout.subscribeToState((state) => layoutStateUpdates.push(state)); + + activateFullSceneTree(scene); + await new Promise((r) => setTimeout(r, 1)); + }); + + it('Should handle second repeat cycle and update remove old repeats', async () => { + // should have 15 repeated rows (and the panel above) + expect(layout.state.rows.length).toBe(16); + + // trigger another repeat cycle by changing the variable + const variable = scene.state.$variables!.state.variables[0] as TestVariable; + variable.changeValueTo(['B1', 'C1']); + + await new Promise((r) => setTimeout(r, 1)); + + // should now only have 2 repeated rows (and the panel above) + expect(layout.state.rows.length).toBe(3); + }); + }); + describe('Given a scene with empty variable', () => { it('Should preserve repeat row', async () => { const { scene, layout } = buildScene({ variableQueryTime: 0 }, []); diff --git a/public/app/features/dashboard-scene/utils/clone.test.ts b/public/app/features/dashboard-scene/utils/clone.test.ts index 28441c4dbe1..58dcef6fb38 100644 --- a/public/app/features/dashboard-scene/utils/clone.test.ts +++ b/public/app/features/dashboard-scene/utils/clone.test.ts @@ -48,6 +48,20 @@ describe('clone', () => { expect(isClonedKey('tab-clone-1/row-clone-2/panel')).toBe(false); expect(isClonedKey('row-clone-1/panel')).toBe(false); }); + + it('should properly handle indexes containing 0', () => { + expect(isClonedKey('tab-clone-0/row-clone-1/panel-clone-0')).toBe(false); + expect(isClonedKey('row-clone-0/panel-clone-0')).toBe(false); + expect(isClonedKey('panel-clone-0')).toBe(false); + + expect(isClonedKey('tab-clone-0/row-clone-1/panel-clone-101')).toBe(true); + expect(isClonedKey('row-clone-0/panel-clone-101')).toBe(true); + expect(isClonedKey('panel-clone-1010')).toBe(true); + + expect(isClonedKey('tab-clone-0/row-clone-1/panel-clone-10')).toBe(true); + expect(isClonedKey('row-clone-0/panel-clone-100')).toBe(true); + expect(isClonedKey('panel-clone-1000')).toBe(true); + }); }); describe('isClonedKeyOf', () => { diff --git a/public/app/features/dashboard-scene/utils/clone.ts b/public/app/features/dashboard-scene/utils/clone.ts index 4e5d2b79fab..05d9459e0e2 100644 --- a/public/app/features/dashboard-scene/utils/clone.ts +++ b/public/app/features/dashboard-scene/utils/clone.ts @@ -1,7 +1,7 @@ const CLONE_KEY = '-clone-'; const CLONE_SEPARATOR = '/'; -const CLONED_KEY_REGEX = new RegExp(`${CLONE_KEY}[1-9]+$`); +const CLONED_KEY_REGEX = new RegExp(`${CLONE_KEY}[1-9][0-9]*$`); const ORIGINAL_REGEX = new RegExp(`${CLONE_KEY}\\d+$`); /**