Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: catalog entity tabs order #2425

Merged
11 changes: 11 additions & 0 deletions docs/dynamic-plugins/frontend-plugin-wiring.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,13 +456,24 @@ plugins:
- path: /
title: General
mountPoint: entity.page.overview #this can be customized too
# Prioritizing tabs (higher priority appears first)
- path: "/pr"
title: "Changed Pull/Merge Requests"
priority: 1 # Added priority field
mountPoint: "entity.page.pull-requests"
# Negative priority hides default tabs
- path: "/"
title: "Changed Overview"
mountPoint: "entity.page.overview"
priority: -6
```

Each entity tab entry requires the following attributes:

- `path`: Specifies the sub-path route in the catalog where this tab will be available
- `title`: The title that is displayed to the user
- `mountPoint`: The base mount point name that will be available on the tab. This name will be expanded to create two mount points per tab, one appended with `/context` and the second appended with `/cards`.
- `priority` **(optional)**: Determines the order of tabs. **Tabs with higher priority values appear first** . You can set a **negative priority to hide default tabs**. If priority is omitted , no special ordering is applied.

Dynamic frontend plugins can then be configured to target the mount points exposed by the `entityTabs` configuration.

Expand Down
1 change: 1 addition & 0 deletions packages/app/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface Config {
path: string;
title: string;
mountPoint: string;
priority?: number;
}[];
mountPoints?: {
mountPoint: string;
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/components/DynamicRoot/DynamicRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,14 +348,14 @@ export const DynamicRoot = ({
}, []);

const entityTabOverrides = entityTabs.reduce<EntityTabOverrides>(
(acc, { path, title, mountPoint, scope }) => {
(acc, { path, title, mountPoint, scope, priority }) => {
if (acc[path]) {
// eslint-disable-next-line no-console
console.warn(
`Plugin ${scope} is not configured properly: a tab has already been configured for "${path}", ignoring entry with title: "${title}" and mountPoint: "${mountPoint}"`,
);
} else {
acc[path] = { title, mountPoint };
acc[path] = { title, mountPoint, priority };
}
return acc;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export type RemotePlugins = {

export type EntityTabOverrides = Record<
string,
{ title: string; mountPoint: string }
{ title: string; mountPoint: string; priority?: number }
>;

export type MountPoints = Record<string, ScalprumMountPoint[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type DynamicEntityTabProps = {
mountPoint: string;
if?: (entity: Entity) => boolean;
children?: React.ReactNode;
priority?: number;
};

/**
Expand Down
21 changes: 10 additions & 11 deletions packages/app/src/components/catalog/EntityPage/EntityPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ContextMenuAwareEntityLayout } from './ContextMenuAwareEntityLayout';
import { defaultTabs, tabChildren, tabRules } from './defaultTabs';
import { tabChildren, tabRules } from './defaultTabs';
import { dynamicEntityTab, DynamicEntityTabProps } from './DynamicEntityTab';
import { mergeTabs } from './utils';

/**
* Displays the tabs and content for a catalog entity
Expand All @@ -16,16 +17,14 @@ export const entityPage = (
) => {
return (
<ContextMenuAwareEntityLayout>
{Object.entries({ ...defaultTabs, ...entityTabOverrides }).map(
([path, config]) => {
return dynamicEntityTab({
...config,
path,
...(tabRules[path] ? tabRules[path] : {}),
...(tabChildren[path] ? tabChildren[path] : {}),
} as DynamicEntityTabProps);
},
)}
{mergeTabs(entityTabOverrides).map(([path, config]) => {
return dynamicEntityTab({
...config,
path,
...(tabRules[path] ? tabRules[path] : {}),
...(tabChildren[path] ? tabChildren[path] : {}),
} as DynamicEntityTabProps);
})}
</ContextMenuAwareEntityLayout>
);
};
44 changes: 44 additions & 0 deletions packages/app/src/components/catalog/EntityPage/utils.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { defaultTabs } from './defaultTabs';
import { mergeTabs } from './utils';

describe('mergeTabs', () => {
it('should return defaultTabs when no overrides are provided', () => {
const result = mergeTabs({});
const expected = Object.entries(defaultTabs);
expect(result).toEqual(expected);
});

it('should merge entityTabOverrides with defaultTabs', () => {
const overrides = {
'/docs': {
title: 'My Documentation',
mountPoint: 'entity.page.docs',
priority: 2,
},
};
const result = mergeTabs(overrides);
expect(result).toContainEqual(['/docs', overrides['/docs']]);
});

it('should remove tabs with negative priority', () => {
const overrides = {
'/ci': { ...defaultTabs['/ci'], priority: -1 },
};
const result = mergeTabs(overrides);
expect(result.find(([key]) => key === '/ci')).toBeUndefined();
});

it('should sort tabs based on priority', () => {
const overrides = {
'/docs': { ...defaultTabs['/docs'], priority: 5 },
'/topology': {
title: 'New Topology',
mountPoint: 'entity.page.topology',
priority: 10,
},
};
const result = mergeTabs(overrides);
expect(result[0][0]).toBe('/topology');
expect(result[1][0]).toBe('/docs');
});
});
19 changes: 19 additions & 0 deletions packages/app/src/components/catalog/EntityPage/utils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { defaultTabs } from './defaultTabs';
import { DynamicEntityTabProps } from './DynamicEntityTab';

export const mergeTabs = (
entityTabOverrides: Record<
string,
Omit<DynamicEntityTabProps, 'path' | 'if' | 'children'>
>,
) => {
return (
Object.entries({ ...defaultTabs, ...entityTabOverrides })
.filter(([, tab]) => !(tab.priority && tab.priority < 0))
.sort(([, tabA], [, tabB]) => {
const priorityA = tabA.priority ?? 0;
const priorityB = tabB.priority ?? 0;
return priorityB - priorityA;
}) || []
);
};
2 changes: 2 additions & 0 deletions packages/app/src/utils/dynamicUI/extractDynamicConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ type EntityTab = {
mountPoint: string;
path: string;
title: string;
pariority?: number;
};

type EntityTabEntry = {
scope: string;
mountPoint: string;
path: string;
title: string;
priority?: number;
};

type ThemeEntry = {
Expand Down
Loading