From 07ba74684a5f0c7e416aab127ea3f23f0f7feae9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Feb 2026 14:50:20 +0100 Subject: [PATCH 1/3] catalog: fix entity page tab group ordering The entity page tab groups were rendered in extension tree order rather than respecting the order defined in the groups configuration. This sorts the rendered groups based on the key order from groupDefinitions. Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .changeset/fix-entity-tab-group-ordering.md | 5 +++ .../components/EntityTabs/EntityTabsList.tsx | 44 ++++++++++++------- 2 files changed, 34 insertions(+), 15 deletions(-) create mode 100644 .changeset/fix-entity-tab-group-ordering.md diff --git a/.changeset/fix-entity-tab-group-ordering.md b/.changeset/fix-entity-tab-group-ordering.md new file mode 100644 index 0000000000..6fab3f77af --- /dev/null +++ b/.changeset/fix-entity-tab-group-ordering.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Fixed entity page tab groups not respecting the ordering from the `groups` configuration. diff --git a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsList.tsx b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsList.tsx index d77cc5e444..9267e083f5 100644 --- a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsList.tsx +++ b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabsList.tsx @@ -88,20 +88,34 @@ export function EntityTabsList(props: EntityTabsListProps) { const { tabs: items, selectedIndex = 0, showIcons, groupDefinitions } = props; - const groups = useMemo( - () => - items.reduce((result, tab) => { - const group = tab.group ? groupDefinitions[tab.group] : undefined; - const groupOrId = group && tab.group ? tab.group : tab.id; - result[groupOrId] = result[groupOrId] ?? { - group, - items: [], - }; - result[groupOrId].items.push(tab); - return result; - }, {} as Record), - [items, groupDefinitions], - ); + const groups = useMemo(() => { + const byKey = items.reduce((result, tab) => { + const group = tab.group ? groupDefinitions[tab.group] : undefined; + const groupOrId = group && tab.group ? tab.group : tab.id; + result[groupOrId] = result[groupOrId] ?? { + group, + items: [], + }; + result[groupOrId].items.push(tab); + return result; + }, {} as Record); + + const groupOrder = Object.keys(groupDefinitions); + return Object.entries(byKey).sort(([a], [b]) => { + const ai = groupOrder.indexOf(a); + const bi = groupOrder.indexOf(b); + if (ai !== -1 && bi !== -1) { + return ai - bi; + } + if (ai !== -1) { + return -1; + } + if (bi !== -1) { + return 1; + } + return 0; + }); + }, [items, groupDefinitions]); const selectedItem = items[selectedIndex]; return ( @@ -115,7 +129,7 @@ export function EntityTabsList(props: EntityTabsListProps) { aria-label={t('entityTabs.tabsAriaLabel')} value={selectedItem?.group ?? selectedItem?.id} > - {Object.entries(groups).map(([id, tabGroup]) => ( + {groups.map(([id, tabGroup]) => ( Date: Wed, 25 Feb 2026 14:51:28 +0100 Subject: [PATCH 2/3] patches: add patch for #33004 Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .patches/pr-33004.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 .patches/pr-33004.txt diff --git a/.patches/pr-33004.txt b/.patches/pr-33004.txt new file mode 100644 index 0000000000..5594a76e6b --- /dev/null +++ b/.patches/pr-33004.txt @@ -0,0 +1 @@ +Fixes entity page tab groups not respecting the ordering from the groups configuration. \ No newline at end of file From f0fc4ab8de07b9befec93878aba325703fc13886 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 25 Feb 2026 15:05:30 +0100 Subject: [PATCH 3/3] catalog: add tests for entity tab group ordering Signed-off-by: Patrik Oldsberg Co-authored-by: Cursor --- .../components/EntityTabs/EntityTabs.test.tsx | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.test.tsx b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.test.tsx index 3bca08288f..a85d795cd8 100644 --- a/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.test.tsx +++ b/plugins/catalog/src/alpha/components/EntityTabs/EntityTabs.test.tsx @@ -16,8 +16,10 @@ import { screen } from '@testing-library/react'; import { useSelectedSubRoute } from './EntityTabs'; +import { EntityTabsList } from './EntityTabsList'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import { render } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/frontend-test-utils'; function TestSubRouteHook(props: { subRoutes: Array<{ @@ -37,6 +39,83 @@ function TestSubRouteHook(props: { ); } +describe('EntityTabsList', () => { + it('should render groups in the order defined by groupDefinitions', () => { + const tabs = [ + { id: '/cicd', label: 'CI/CD', path: 'cicd', group: 'cicd' }, + { + id: '/overview', + label: 'Overview', + path: 'overview', + group: 'overview', + }, + { + id: '/techdocs', + label: 'TechDocs', + path: 'techdocs', + group: 'techdocs', + }, + ]; + + const groupDefinitions = { + overview: { title: 'Overview' }, + techdocs: { title: 'TechDocs' }, + cicd: { title: 'CI/CD' }, + }; + + renderInTestApp( + , + ); + + const tabElements = screen.getAllByRole('tab'); + expect(tabElements).toHaveLength(3); + expect(tabElements[0]).toHaveTextContent('Overview'); + expect(tabElements[1]).toHaveTextContent('TechDocs'); + expect(tabElements[2]).toHaveTextContent('CI/CD'); + }); + + it('should place ungrouped tabs after defined groups', () => { + const tabs = [ + { id: '/standalone', label: 'Standalone', path: 'standalone' }, + { + id: '/overview', + label: 'Overview', + path: 'overview', + group: 'overview', + }, + { + id: '/techdocs', + label: 'TechDocs', + path: 'techdocs', + group: 'techdocs', + }, + ]; + + const groupDefinitions = { + overview: { title: 'Overview' }, + techdocs: { title: 'TechDocs' }, + }; + + renderInTestApp( + , + ); + + const tabElements = screen.getAllByRole('tab'); + expect(tabElements).toHaveLength(3); + expect(tabElements[0]).toHaveTextContent('Overview'); + expect(tabElements[1]).toHaveTextContent('TechDocs'); + expect(tabElements[2]).toHaveTextContent('Standalone'); + }); +}); + describe('EntityTabs', () => { const subRoutes = [ {