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/.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
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 = [
{
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]) => (