From b29e8ccb457f53dc920132f432d7b8c2a30db5e4 Mon Sep 17 00:00:00 2001 From: Kristina Demeshchik Date: Fri, 12 Dec 2025 13:14:56 -0500 Subject: [PATCH] Dashboards: Generate default tab title when converting rows with empty titles to tabs (#115256) Generate default title for empty row titles --- .../layout-tabs/TabsLayoutManager.test.tsx | 53 +++++++++++++++++++ .../scene/layout-tabs/TabsLayoutManager.tsx | 10 +++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.test.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.test.tsx index 3d1fd828042..0502f0d62e8 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.test.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.test.tsx @@ -262,4 +262,57 @@ describe('TabsLayoutManager', () => { expect(manager.getVizPanels().length).toBe(1); }); }); + + describe('createFromLayout', () => { + it('should convert rows with titles to tabs', () => { + const rowsLayout = new RowsLayoutManager({ + rows: [new RowItem({ title: 'Row 1' }), new RowItem({ title: 'Row 2' })], + }); + + const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout); + + expect(tabsManager.state.tabs).toHaveLength(2); + expect(tabsManager.state.tabs[0].state.title).toBe('Row 1'); + expect(tabsManager.state.tabs[1].state.title).toBe('Row 2'); + }); + + it('should use default title when row has empty title', () => { + const rowsLayout = new RowsLayoutManager({ + rows: [new RowItem({ title: '' })], + }); + + const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout); + + expect(tabsManager.state.tabs).toHaveLength(1); + expect(tabsManager.state.tabs[0].state.title).toBe('New tab'); + }); + + it('should generate unique titles for multiple rows with empty titles', () => { + const rowsLayout = new RowsLayoutManager({ + rows: [new RowItem({ title: '' }), new RowItem({ title: '' }), new RowItem({ title: '' })], + }); + + const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout); + + expect(tabsManager.state.tabs).toHaveLength(3); + expect(tabsManager.state.tabs[0].state.title).toBe('New tab'); + expect(tabsManager.state.tabs[1].state.title).toBe('New tab 1'); + expect(tabsManager.state.tabs[2].state.title).toBe('New tab 2'); + }); + + it('should generate unique titles when mixing empty and existing titles', () => { + const rowsLayout = new RowsLayoutManager({ + rows: [ + new RowItem({ title: 'New row' }), // existing title that matches default + new RowItem({ title: '' }), // empty, should get unique title + ], + }); + + const tabsManager = TabsLayoutManager.createFromLayout(rowsLayout); + + expect(tabsManager.state.tabs).toHaveLength(2); + expect(tabsManager.state.tabs[0].state.title).toBe('New row'); + expect(tabsManager.state.tabs[1].state.title).toBe('New tab'); + }); + }); }); diff --git a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx index 79d027b1a02..4e08b56e54f 100644 --- a/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx +++ b/public/app/features/dashboard-scene/scene/layout-tabs/TabsLayoutManager.tsx @@ -410,6 +410,10 @@ export class TabsLayoutManager extends SceneObjectBase i let tabs: TabItem[] = []; if (layout instanceof RowsLayoutManager) { + const existingNames = new Set( + layout.state.rows.map((row) => row.state.title).filter((title): title is string => !!title) + ); + for (const row of layout.state.rows) { if (row.state.repeatSourceKey) { continue; @@ -420,10 +424,14 @@ export class TabsLayoutManager extends SceneObjectBase i // We need to clear the target since we don't want to point the original row anymore (if it was set) conditionalRendering?.setTarget(undefined); + const newTitle = + row.state.title || generateUniqueTitle(t('dashboard.tabs-layout.tab.new', 'New tab'), existingNames); + existingNames.add(newTitle); + tabs.push( new TabItem({ layout: row.state.layout.clone(), - title: row.state.title, + title: newTitle, conditionalRendering, repeatByVariable: row.state.repeatByVariable, })