Skip to content

Commit 0abeaa4

Browse files
fix: catalog entity tabs order (#2425)
* fix enity tabs order * fixing formatting Signed-off-by: its-mitesh-kumar <[email protected]> * fixing lint issue Signed-off-by: its-mitesh-kumar <[email protected]> * fix(entity-tabs): moving mergeTabs to new util file Signed-off-by: its-mitesh-kumar <[email protected]> * adding unit tests Signed-off-by: its-mitesh-kumar <[email protected]> * updating dev docs Signed-off-by: its-mitesh-kumar <[email protected]> * fixing with prettier Signed-off-by: its-mitesh-kumar <[email protected]> --------- Signed-off-by: its-mitesh-kumar <[email protected]>
1 parent 76becce commit 0abeaa4

File tree

9 files changed

+91
-14
lines changed

9 files changed

+91
-14
lines changed

docs/dynamic-plugins/frontend-plugin-wiring.md

+11
Original file line numberDiff line numberDiff line change
@@ -456,13 +456,24 @@ plugins:
456456
- path: /
457457
title: General
458458
mountPoint: entity.page.overview #this can be customized too
459+
# Prioritizing tabs (higher priority appears first)
460+
- path: "/pr"
461+
title: "Changed Pull/Merge Requests"
462+
priority: 1 # Added priority field
463+
mountPoint: "entity.page.pull-requests"
464+
# Negative priority hides default tabs
465+
- path: "/"
466+
title: "Changed Overview"
467+
mountPoint: "entity.page.overview"
468+
priority: -6
459469
```
460470
461471
Each entity tab entry requires the following attributes:
462472
463473
- `path`: Specifies the sub-path route in the catalog where this tab will be available
464474
- `title`: The title that is displayed to the user
465475
- `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`.
476+
- `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.
466477

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

packages/app/config.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export interface Config {
9696
path: string;
9797
title: string;
9898
mountPoint: string;
99+
priority?: number;
99100
}[];
100101
mountPoints?: {
101102
mountPoint: string;

packages/app/src/components/DynamicRoot/DynamicRoot.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -348,14 +348,14 @@ export const DynamicRoot = ({
348348
}, []);
349349

350350
const entityTabOverrides = entityTabs.reduce<EntityTabOverrides>(
351-
(acc, { path, title, mountPoint, scope }) => {
351+
(acc, { path, title, mountPoint, scope, priority }) => {
352352
if (acc[path]) {
353353
// eslint-disable-next-line no-console
354354
console.warn(
355355
`Plugin ${scope} is not configured properly: a tab has already been configured for "${path}", ignoring entry with title: "${title}" and mountPoint: "${mountPoint}"`,
356356
);
357357
} else {
358-
acc[path] = { title, mountPoint };
358+
acc[path] = { title, mountPoint, priority };
359359
}
360360
return acc;
361361
},

packages/app/src/components/DynamicRoot/DynamicRootContext.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export type RemotePlugins = {
104104

105105
export type EntityTabOverrides = Record<
106106
string,
107-
{ title: string; mountPoint: string }
107+
{ title: string; mountPoint: string; priority?: number }
108108
>;
109109

110110
export type MountPoints = Record<string, ScalprumMountPoint[]>;

packages/app/src/components/catalog/EntityPage/DynamicEntityTab.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type DynamicEntityTabProps = {
1515
mountPoint: string;
1616
if?: (entity: Entity) => boolean;
1717
children?: React.ReactNode;
18+
priority?: number;
1819
};
1920

2021
/**
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ContextMenuAwareEntityLayout } from './ContextMenuAwareEntityLayout';
2-
import { defaultTabs, tabChildren, tabRules } from './defaultTabs';
2+
import { tabChildren, tabRules } from './defaultTabs';
33
import { dynamicEntityTab, DynamicEntityTabProps } from './DynamicEntityTab';
4+
import { mergeTabs } from './utils';
45

56
/**
67
* Displays the tabs and content for a catalog entity
@@ -16,16 +17,14 @@ export const entityPage = (
1617
) => {
1718
return (
1819
<ContextMenuAwareEntityLayout>
19-
{Object.entries({ ...defaultTabs, ...entityTabOverrides }).map(
20-
([path, config]) => {
21-
return dynamicEntityTab({
22-
...config,
23-
path,
24-
...(tabRules[path] ? tabRules[path] : {}),
25-
...(tabChildren[path] ? tabChildren[path] : {}),
26-
} as DynamicEntityTabProps);
27-
},
28-
)}
20+
{mergeTabs(entityTabOverrides).map(([path, config]) => {
21+
return dynamicEntityTab({
22+
...config,
23+
path,
24+
...(tabRules[path] ? tabRules[path] : {}),
25+
...(tabChildren[path] ? tabChildren[path] : {}),
26+
} as DynamicEntityTabProps);
27+
})}
2928
</ContextMenuAwareEntityLayout>
3029
);
3130
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { defaultTabs } from './defaultTabs';
2+
import { mergeTabs } from './utils';
3+
4+
describe('mergeTabs', () => {
5+
it('should return defaultTabs when no overrides are provided', () => {
6+
const result = mergeTabs({});
7+
const expected = Object.entries(defaultTabs);
8+
expect(result).toEqual(expected);
9+
});
10+
11+
it('should merge entityTabOverrides with defaultTabs', () => {
12+
const overrides = {
13+
'/docs': {
14+
title: 'My Documentation',
15+
mountPoint: 'entity.page.docs',
16+
priority: 2,
17+
},
18+
};
19+
const result = mergeTabs(overrides);
20+
expect(result).toContainEqual(['/docs', overrides['/docs']]);
21+
});
22+
23+
it('should remove tabs with negative priority', () => {
24+
const overrides = {
25+
'/ci': { ...defaultTabs['/ci'], priority: -1 },
26+
};
27+
const result = mergeTabs(overrides);
28+
expect(result.find(([key]) => key === '/ci')).toBeUndefined();
29+
});
30+
31+
it('should sort tabs based on priority', () => {
32+
const overrides = {
33+
'/docs': { ...defaultTabs['/docs'], priority: 5 },
34+
'/topology': {
35+
title: 'New Topology',
36+
mountPoint: 'entity.page.topology',
37+
priority: 10,
38+
},
39+
};
40+
const result = mergeTabs(overrides);
41+
expect(result[0][0]).toBe('/topology');
42+
expect(result[1][0]).toBe('/docs');
43+
});
44+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { defaultTabs } from './defaultTabs';
2+
import { DynamicEntityTabProps } from './DynamicEntityTab';
3+
4+
export const mergeTabs = (
5+
entityTabOverrides: Record<
6+
string,
7+
Omit<DynamicEntityTabProps, 'path' | 'if' | 'children'>
8+
>,
9+
) => {
10+
return (
11+
Object.entries({ ...defaultTabs, ...entityTabOverrides })
12+
.filter(([, tab]) => !(tab.priority && tab.priority < 0))
13+
.sort(([, tabA], [, tabB]) => {
14+
const priorityA = tabA.priority ?? 0;
15+
const priorityB = tabB.priority ?? 0;
16+
return priorityB - priorityA;
17+
}) || []
18+
);
19+
};

packages/app/src/utils/dynamicUI/extractDynamicConfig.ts

+2
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,15 @@ type EntityTab = {
106106
mountPoint: string;
107107
path: string;
108108
title: string;
109+
pariority?: number;
109110
};
110111

111112
type EntityTabEntry = {
112113
scope: string;
113114
mountPoint: string;
114115
path: string;
115116
title: string;
117+
priority?: number;
116118
};
117119

118120
type ThemeEntry = {

0 commit comments

Comments
 (0)