Address review feedback

- Rename 'alpha' to 'title' for content order enum value
- Inline EntityContentGroupDefinition type into EntityContentGroupDefinitions
- Rename defaultContentOrder prop to contentOrder
- Apply content ordering to ungrouped tabs as well
- Fix no-nested-ternary lint error

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-26 00:32:23 +01:00
parent 4d588942d5
commit deb4476146
11 changed files with 56 additions and 49 deletions
@@ -2,4 +2,4 @@
'@backstage/plugin-catalog': minor
---
Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `contentOrder` option (default `alpha`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides.
Added support for group alias IDs and configurable content ordering on the entity page. Groups can now declare `aliases` so that content targeting an aliased group is included in the group. A new `contentOrder` option (default `title`) controls how content items within each group are sorted, with support for both a page-level default and per-group overrides.
+2 -2
View File
@@ -48,8 +48,8 @@ app:
- page:catalog/entity:
config:
showNavItemIcons: true
# default content order for all groups, can be 'alpha' or 'natural'
# contentOrder: alpha
# default content order for all groups, can be 'title' or 'natural'
# contentOrder: title
groups:
# placing a tab at the beginning
- overview:
+6 -9
View File
@@ -339,18 +339,15 @@ export const EntityContentBlueprint: ExtensionBlueprint<{
};
}>;
// @alpha (undocumented)
export type EntityContentGroupDefinition = {
title: string;
icon?: string | ReactElement;
aliases?: string[];
contentOrder?: 'alpha' | 'natural';
};
// @alpha (undocumented)
export type EntityContentGroupDefinitions = Record<
string,
EntityContentGroupDefinition
{
title: string;
icon?: string | ReactElement;
aliases?: string[];
contentOrder?: 'title' | 'natural';
}
>;
// @alpha (undocumented)
@@ -41,20 +41,17 @@ export const entityFilterExpressionDataRef =
id: 'catalog.entity-filter-expression',
});
/** @alpha */
export type EntityContentGroupDefinition = {
title: string;
icon?: string | ReactElement;
/** Other group IDs that should be treated as aliases for this group. */
aliases?: string[];
/** How to sort the content items within this group. Overrides the page-level default. */
contentOrder?: 'alpha' | 'natural';
};
/** @alpha */
export type EntityContentGroupDefinitions = Record<
string,
EntityContentGroupDefinition
{
title: string;
icon?: string | ReactElement;
/** Other group IDs that should be treated as aliases for this group. */
aliases?: string[];
/** How to sort the content items within this group. Overrides the page-level default. */
contentOrder?: 'title' | 'natural';
}
>;
/**
@@ -24,7 +24,6 @@ export { EntityHeaderBlueprint } from './EntityHeaderBlueprint';
export {
defaultEntityContentGroups,
defaultEntityContentGroupDefinitions,
type EntityContentGroupDefinition,
type EntityContentGroupDefinitions,
} from './extensionData';
export type { EntityCardType } from './extensionData';
+4 -4
View File
@@ -1097,12 +1097,12 @@ const _default: OverridableFrontendPlugin<
{
title: string;
icon?: string | undefined;
contentOrder?: 'alpha' | 'natural' | undefined;
contentOrder?: 'title' | 'natural' | undefined;
aliases?: string[] | undefined;
}
>[]
| undefined;
contentOrder: 'alpha' | 'natural';
contentOrder: 'title' | 'natural';
showNavItemIcons: boolean;
path: string | undefined;
title: string | undefined;
@@ -1114,13 +1114,13 @@ const _default: OverridableFrontendPlugin<
{
title: string;
icon?: string | undefined;
contentOrder?: 'alpha' | 'natural' | undefined;
contentOrder?: 'title' | 'natural' | undefined;
aliases?: string[] | undefined;
}
>[]
| undefined;
contentOrder?: 'title' | 'natural' | undefined;
showNavItemIcons?: boolean | undefined;
contentOrder?: 'alpha' | 'natural' | undefined;
title?: string | undefined;
path?: string | undefined;
};
@@ -80,7 +80,7 @@ export interface EntityLayoutProps {
*/
parentEntityRelations?: string[];
groupDefinitions: EntityContentGroupDefinitions;
defaultContentOrder?: 'alpha' | 'natural';
contentOrder?: 'title' | 'natural';
showNavItemIcons?: boolean;
}
@@ -111,7 +111,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
NotFoundComponent,
parentEntityRelations,
groupDefinitions,
defaultContentOrder,
contentOrder,
showNavItemIcons,
} = props;
const { kind } = useRouteRefParams(entityRouteRef);
@@ -166,7 +166,7 @@ export const EntityLayout = (props: EntityLayoutProps) => {
<EntityTabs
routes={routes}
groupDefinitions={groupDefinitions}
defaultContentOrder={defaultContentOrder}
contentOrder={contentOrder}
showIcons={showNavItemIcons}
/>
)}
@@ -72,12 +72,12 @@ export function useSelectedSubRoute(subRoutes: SubRoute[]): {
type EntityTabsProps = {
routes: SubRoute[];
groupDefinitions: EntityContentGroupDefinitions;
defaultContentOrder?: 'alpha' | 'natural';
contentOrder?: 'title' | 'natural';
showIcons?: boolean;
};
export function EntityTabs(props: EntityTabsProps) {
const { routes, groupDefinitions, defaultContentOrder, showIcons } = props;
const { routes, groupDefinitions, contentOrder, showIcons } = props;
const { index, route, element } = useSelectedSubRoute(routes);
@@ -108,7 +108,7 @@ export function EntityTabs(props: EntityTabsProps) {
selectedIndex={index}
showIcons={showIcons}
groupDefinitions={groupDefinitions}
defaultContentOrder={defaultContentOrder}
contentOrder={contentOrder}
/>
<EntityTabsPanel>
<Helmet title={route?.title} />
@@ -78,11 +78,25 @@ type TabGroup = {
type EntityTabsListProps = {
tabs: Tab[];
groupDefinitions: EntityContentGroupDefinitions;
defaultContentOrder?: 'alpha' | 'natural';
contentOrder?: 'title' | 'natural';
showIcons?: boolean;
selectedIndex?: number;
};
function resolveGroupId(
tabGroup: string | undefined,
groupDefinitions: EntityContentGroupDefinitions,
aliasToGroup: Record<string, string>,
): string | undefined {
if (!tabGroup) {
return undefined;
}
if (groupDefinitions[tabGroup]) {
return tabGroup;
}
return aliasToGroup[tabGroup];
}
export function EntityTabsList(props: EntityTabsListProps) {
const styles = useStyles();
const { t } = useTranslationRef(catalogTranslationRef);
@@ -92,7 +106,7 @@ export function EntityTabsList(props: EntityTabsListProps) {
selectedIndex = 0,
showIcons,
groupDefinitions,
defaultContentOrder = 'alpha',
contentOrder = 'title',
} = props;
const aliasToGroup = useMemo(
@@ -108,11 +122,11 @@ export function EntityTabsList(props: EntityTabsListProps) {
const groups = useMemo(() => {
const byKey = items.reduce((result, tab) => {
const resolvedGroupId = tab.group
? groupDefinitions[tab.group]
? tab.group
: aliasToGroup[tab.group]
: undefined;
const resolvedGroupId = resolveGroupId(
tab.group,
groupDefinitions,
aliasToGroup,
);
const group = resolvedGroupId
? groupDefinitions[resolvedGroupId]
: undefined;
@@ -143,8 +157,8 @@ export function EntityTabsList(props: EntityTabsListProps) {
for (const [id, tabGroup] of sorted) {
const groupDef = groupDefinitions[id];
const order = groupDef?.contentOrder ?? defaultContentOrder;
if (order === 'alpha') {
const order = groupDef?.contentOrder ?? contentOrder;
if (order === 'title') {
tabGroup.items.sort((a, b) =>
a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }),
);
@@ -152,7 +166,7 @@ export function EntityTabsList(props: EntityTabsListProps) {
}
return sorted;
}, [items, groupDefinitions, aliasToGroup, defaultContentOrder]);
}, [items, groupDefinitions, aliasToGroup, contentOrder]);
const selectedItem = items[selectedIndex];
return (
+2 -2
View File
@@ -482,7 +482,7 @@ describe('Entity page', () => {
);
});
it('Should sort content alphabetically by default', async () => {
it('Should sort content by title by default', async () => {
const tester = createExtensionTester(
Object.assign({ namespace: 'catalog' }, catalogEntityPage),
)
@@ -564,7 +564,7 @@ describe('Entity page', () => {
Object.assign({ namespace: 'catalog' }, catalogEntityPage),
{
config: {
contentOrder: 'alpha',
contentOrder: 'title',
groups: [
{
documentation: {
+3 -3
View File
@@ -110,13 +110,13 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({
title: z.string(),
icon: z.string().optional(),
aliases: z.array(z.string()).optional(),
contentOrder: z.enum(['alpha', 'natural']).optional(),
contentOrder: z.enum(['title', 'natural']).optional(),
}),
),
)
.optional(),
contentOrder: z =>
z.enum(['alpha', 'natural']).optional().default('alpha'),
z.enum(['title', 'natural']).optional().default('title'),
showNavItemIcons: z => z.boolean().optional().default(false),
},
},
@@ -178,7 +178,7 @@ export const catalogEntityPage = PageBlueprint.makeWithOverrides({
header={header}
contextMenuItems={filteredMenuItems}
groupDefinitions={groupDefinitions}
defaultContentOrder={config.contentOrder}
contentOrder={config.contentOrder}
showNavItemIcons={config.showNavItemIcons}
>
{inputs.contents.map(output => (