From 90ba9c14d3198cefa54d9c2c3740131191c37b08 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 16 Dec 2024 10:22:00 +0100 Subject: [PATCH 1/5] chore(feo): move types to a separate file --- packages/config-utils/src/feo/feo-types.ts | 46 +++++++++++++ .../{ => feo}/navigation-interceptor.test.ts | 35 +++++----- .../src/{ => feo}/navigation-interceptor.ts | 65 +++---------------- 3 files changed, 73 insertions(+), 73 deletions(-) create mode 100644 packages/config-utils/src/feo/feo-types.ts rename packages/config-utils/src/{ => feo}/navigation-interceptor.test.ts (95%) rename packages/config-utils/src/{ => feo}/navigation-interceptor.ts (81%) diff --git a/packages/config-utils/src/feo/feo-types.ts b/packages/config-utils/src/feo/feo-types.ts new file mode 100644 index 000000000..cf27380c3 --- /dev/null +++ b/packages/config-utils/src/feo/feo-types.ts @@ -0,0 +1,46 @@ +export type SegmentRef = { + segmentId: string; + frontendName: string; +}; + +export type DirectNavItem = { + id?: string; + frontendRef?: string; + href?: string; + title?: string; + expandable?: boolean; + // should be removed + appId?: string; + routes?: DirectNavItem[]; + navItems?: DirectNavItem[]; + bundleSegmentRef?: string; + segmentRef?: SegmentRef; + segmentId?: string; +}; + +export type Nav = { + title?: string; + id: string; + navItems: DirectNavItem[]; +}; + +export type BundleSegment = { + segmentId: string; + bundleId: string; + position: number; + navItems: DirectNavItem[]; +}; + +export type CRDObject = { + metadata: { + name: string; + }; + spec: { + bundleSegments?: BundleSegment[]; + navigationSegments?: DirectNavItem[]; + }; +}; + +export type FrontendCRD = { + objects: CRDObject[]; +}; diff --git a/packages/config-utils/src/navigation-interceptor.test.ts b/packages/config-utils/src/feo/navigation-interceptor.test.ts similarity index 95% rename from packages/config-utils/src/navigation-interceptor.test.ts rename to packages/config-utils/src/feo/navigation-interceptor.test.ts index f24b60a63..579d6d3f4 100644 --- a/packages/config-utils/src/navigation-interceptor.test.ts +++ b/packages/config-utils/src/feo/navigation-interceptor.test.ts @@ -1,16 +1,17 @@ -import navigationInterceptor, { FrontendCRD, Nav, NavItem, SegmentRef } from './navigation-interceptor'; +import { DirectNavItem, FrontendCRD, Nav, SegmentRef } from './feo-types'; +import navigationInterceptor from './navigation-interceptor'; describe('NavigationInterceptor', () => { describe('bundle segments', () => { const bundleName = 'testing-bundle'; const defaultFrontendName = 'testing-frontend'; const bundleSegmentName = 'testing-bundle-segment'; - const baseNavItem: NavItem = { + const baseNavItem: DirectNavItem = { id: 'link-one', href: '/link-one', title: 'Link one', }; - function createLocalCRD({ bundleSegmentRef, frontendRef, ...navItem }: NavItem, frontendName: string): FrontendCRD { + function createLocalCRD({ bundleSegmentRef, frontendRef, ...navItem }: DirectNavItem, frontendName: string): FrontendCRD { return { objects: [ { @@ -31,18 +32,18 @@ describe('NavigationInterceptor', () => { ], }; } - function createRemoteNav(navItem: NavItem): Nav { + function createRemoteNav(navItem: DirectNavItem): Nav { return { id: bundleName, title: bundleName, navItems: [navItem], }; } - function createExpectedNavItems(navItem: NavItem): NavItem[] { + function createExpectedNavItems(navItem: DirectNavItem): DirectNavItem[] { return [navItem]; } function crateTestData( - navItem: NavItem, + navItem: DirectNavItem, { shouldChange, isNestedRoute, @@ -51,7 +52,7 @@ describe('NavigationInterceptor', () => { }: { shouldChange?: boolean; isNestedRoute?: boolean; isNestedNav?: boolean; frontendName?: string } = {} ) { const internalFrontendName = frontendName ?? defaultFrontendName; - let internalNavItem: NavItem = { ...navItem }; + let internalNavItem: DirectNavItem = { ...navItem }; internalNavItem.bundleSegmentRef = bundleSegmentName; internalNavItem.frontendRef = internalFrontendName; if (isNestedRoute) { @@ -88,7 +89,7 @@ describe('NavigationInterceptor', () => { ], }; } - let changedNavItem: NavItem; + let changedNavItem: DirectNavItem; if (shouldChange) { if (isNestedRoute) { changedNavItem = { @@ -180,7 +181,7 @@ describe('NavigationInterceptor', () => { frontendName: defaultFrontendName, segmentId: navSegmentId, }; - const baseNavItem: NavItem = { + const baseNavItem: DirectNavItem = { id: 'link-one', href: '/link-one', title: 'Link one', @@ -219,7 +220,7 @@ describe('NavigationInterceptor', () => { navItems: [{ ...baseNavItem, segmentRef: baseSegmentRef, frontendRef: defaultFrontendName, bundleSegmentRef: bundleSegmentName }], }; - const expectedResult: NavItem[] = [ + const expectedResult: DirectNavItem[] = [ { ...baseNavItem, title: 'Link one changed', @@ -239,7 +240,7 @@ describe('NavigationInterceptor', () => { frontendName: defaultFrontendName, segmentId: navSegmentId, }; - const baseNavItems: NavItem[] = [ + const baseNavItems: DirectNavItem[] = [ { id: 'link-one', href: '/link-one', @@ -303,7 +304,7 @@ describe('NavigationInterceptor', () => { ], }; - const expectedResult: NavItem[] = [ + const expectedResult: DirectNavItem[] = [ { title: 'persistent item', href: '/persistent', @@ -328,7 +329,7 @@ describe('NavigationInterceptor', () => { frontendName: defaultFrontendName, segmentId: navSegmentId, }; - const baseNavItems: NavItem[] = [ + const baseNavItems: DirectNavItem[] = [ { id: 'link-one', href: '/link-one', @@ -373,7 +374,7 @@ describe('NavigationInterceptor', () => { navItems: [{ ...baseNavItems[0], segmentRef: baseSegmentRef, frontendRef: defaultFrontendName, bundleSegmentRef: bundleSegmentName }], }; - const expectedResult: NavItem[] = baseNavItems.map(({ title, ...navItem }) => ({ + const expectedResult: DirectNavItem[] = baseNavItems.map(({ title, ...navItem }) => ({ ...navItem, title: `${title} changed`, })); @@ -391,7 +392,7 @@ describe('NavigationInterceptor', () => { frontendName: defaultFrontendName, segmentId: navSegmentId, }; - const baseNavItems: NavItem[] = [ + const baseNavItems: DirectNavItem[] = [ { id: 'link-one', href: '/link-one', @@ -441,7 +442,7 @@ describe('NavigationInterceptor', () => { })), }; - const expectedResult: NavItem[] = [{ ...baseNavItems[0], title: `${baseNavItems[0].title} changed` }]; + const expectedResult: DirectNavItem[] = [{ ...baseNavItems[0], title: `${baseNavItems[0].title} changed` }]; const result = navigationInterceptor(frontendCRD, remoteNav, bundleName); expect(result).toEqual(expectedResult); @@ -601,7 +602,7 @@ describe('NavigationInterceptor', () => { ], }; - const expectedResult: NavItem[] = [ + const expectedResult: DirectNavItem[] = [ { title: 'Link one', href: '/link-one', diff --git a/packages/config-utils/src/navigation-interceptor.ts b/packages/config-utils/src/feo/navigation-interceptor.ts similarity index 81% rename from packages/config-utils/src/navigation-interceptor.ts rename to packages/config-utils/src/feo/navigation-interceptor.ts index 916bc739d..c31ad9ccc 100644 --- a/packages/config-utils/src/navigation-interceptor.ts +++ b/packages/config-utils/src/feo/navigation-interceptor.ts @@ -1,53 +1,6 @@ -export type SegmentRef = { - segmentId: string; - frontendName: string; -}; - -type DirectNavItem = { - id?: string; - frontendRef?: string; - href?: string; - title?: string; - expandable?: boolean; - // should be removed - appId?: string; - routes?: NavItem[]; - navItems?: NavItem[]; - bundleSegmentRef?: string; - segmentRef?: SegmentRef; - segmentId?: string; -}; - -export type NavItem = DirectNavItem; - -export type Nav = { - title?: string; - id: string; - navItems: NavItem[]; -}; - -type BundleSegment = { - segmentId: string; - bundleId: string; - position: number; - navItems: NavItem[]; -}; - -type CRDObject = { - metadata: { - name: string; - }; - spec: { - bundleSegments?: BundleSegment[]; - navigationSegments?: DirectNavItem[]; - }; -}; - -export type FrontendCRD = { - objects: CRDObject[]; -}; +import { BundleSegment, DirectNavItem, FrontendCRD, Nav, SegmentRef } from './feo-types'; -function hasSegmentRef(item: NavItem): item is Omit & { segmentRef: SegmentRef } { +function hasSegmentRef(item: DirectNavItem): item is Omit & { segmentRef: SegmentRef } { return typeof item?.segmentRef?.segmentId === 'string' && typeof item?.segmentRef?.frontendName === 'string'; } @@ -63,7 +16,7 @@ const getBundleSegments = (segmentCache: typeof bundleSegmentsCache, bundleId: s }, {}); }; -function findMatchingSegmentItem(navItems: NavItem[], matchId: string): NavItem | undefined { +function findMatchingSegmentItem(navItems: DirectNavItem[], matchId: string): DirectNavItem | undefined { let match = navItems.find((item) => { if (!hasSegmentRef(item)) { return item.id === matchId; @@ -92,7 +45,7 @@ function handleNestedNav( nSegmentCache: typeof navSegmentCache, bundleId: string, currentFrontendName: string -): NavItem { +): DirectNavItem { const { routes, navItems, ...segmentItem } = segmentMatch; let parsedRoutes = originalNavItem.routes; let parsedNavItems = originalNavItem.navItems; @@ -112,13 +65,13 @@ function handleNestedNav( }; } -function findNavItemsFirstSegmentIndex(navItems: NavItem[], frontendName: string) { +function findNavItemsFirstSegmentIndex(navItems: DirectNavItem[], frontendName: string) { return navItems.findIndex((item) => { return hasSegmentRef(item) && item.segmentRef.frontendName === frontendName; }); } -function findSegmentSequenceLength(navItems: NavItem[], sequenceStartIndex: number, sementId: string, frontendName: string) { +function findSegmentSequenceLength(navItems: DirectNavItem[], sequenceStartIndex: number, sementId: string, frontendName: string) { let finalIndex = sequenceStartIndex; for (let i = sequenceStartIndex; i < navItems.length; i += 1) { const item = navItems[i]; @@ -138,12 +91,12 @@ function findSegmentSequenceLength(navItems: NavItem[], sequenceStartIndex: numb } function parseNavItems( - navItems: NavItem[], + navItems: DirectNavItem[], bSegmentCache: typeof bundleSegmentsCache, nSegmentCache: typeof navSegmentCache, bundleId: string, currentFrontendName: string -): NavItem[] { +): DirectNavItem[] { const relevantSegments = getBundleSegments(bSegmentCache, bundleId); const res = navItems.map((navItem) => { if (!hasSegmentRef(navItem) && navItem.id) { @@ -195,7 +148,7 @@ function parseNavItems( // replaces changed nav items, local data overrides the remote data const substituteLocalNav = (frontendCRD: FrontendCRD, nav: Nav, bundleName: string) => { - let res: NavItem[] = []; + let res: DirectNavItem[] = []; const bundleSegmentsCache: { [bundleSegmentId: string]: BundleSegment } = {}; const navSegmentCache: { [navSegmentId: string]: DirectNavItem } = {}; frontendCRD.objects.forEach((obj) => { From 0bf16ff3a3ae12ee12e556e3290a9dab219af52e Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 16 Dec 2024 10:42:25 +0100 Subject: [PATCH 2/5] feat(feo): add module registry interceptor --- packages/config-utils/src/feo/feo-types.ts | 57 +++++++++++++++++ .../src/feo/module-interceptor.test.ts | 61 +++++++++++++++++++ .../src/feo/module-interceptor.ts | 11 ++++ .../src/feo/navigation-interceptor.test.ts | 18 ++++++ 4 files changed, 147 insertions(+) create mode 100644 packages/config-utils/src/feo/module-interceptor.test.ts create mode 100644 packages/config-utils/src/feo/module-interceptor.ts diff --git a/packages/config-utils/src/feo/feo-types.ts b/packages/config-utils/src/feo/feo-types.ts index cf27380c3..c1efcb34c 100644 --- a/packages/config-utils/src/feo/feo-types.ts +++ b/packages/config-utils/src/feo/feo-types.ts @@ -1,3 +1,59 @@ +export type SupportCaseData = { + version: string; + product: string; +}; + +export type ChromeGlobalModuleConfig = { + supportCaseData?: SupportCaseData; + ssoScopes?: string[]; +}; + +export type ChromePermissions = { + method: string; + apps?: string[]; + args?: unknown[]; +}; + +export type ChromeEntryModuleRoute = { + pathname: string; + exact?: boolean; + props?: object; + supportCaseData?: SupportCaseData; + permissions?: ChromePermissions; +}; + +export type ChromeEntryModule = { + id: string; + module: string; + routes: ChromeEntryModuleRoute[]; +}; + +type ChromeModuleAnalytics = { + APIKey: string; +}; + +export type ChromeModule = { + manifestLocation: string; + defaultDocumentTitle?: string; + /** + * @deprecated + * use `moduleConfig` instead + */ + config?: object; + moduleConfig?: ChromeGlobalModuleConfig; + modules?: ChromeEntryModule[]; + /** + * @deprecated + * Use feo generated resources to get permitted modules + */ + isFedramp?: boolean; + analytics?: ChromeModuleAnalytics; +}; + +export type ChromeModuleRegistry = { + [moduleName: string]: ChromeModule; +}; + export type SegmentRef = { segmentId: string; frontendName: string; @@ -38,6 +94,7 @@ export type CRDObject = { spec: { bundleSegments?: BundleSegment[]; navigationSegments?: DirectNavItem[]; + module: ChromeModule; }; }; diff --git a/packages/config-utils/src/feo/module-interceptor.test.ts b/packages/config-utils/src/feo/module-interceptor.test.ts new file mode 100644 index 000000000..9b5cc131e --- /dev/null +++ b/packages/config-utils/src/feo/module-interceptor.test.ts @@ -0,0 +1,61 @@ +import { ChromeModule, ChromeModuleRegistry, FrontendCRD } from './feo-types'; +import moduleInterceptor from './module-interceptor'; + +describe('module-interceptor', () => { + it('should replace existing entry in moduleRegistry with new entry', () => { + const moduleName = 'module-name'; + const newEntry: ChromeModule = { + manifestLocation: 'new-location', + }; + const frontendCRD: FrontendCRD = { + objects: [ + { + metadata: { + name: moduleName, + }, + spec: { + module: newEntry, + }, + }, + ], + }; + const remoteModuleRegistry: ChromeModuleRegistry = { + [moduleName]: { + manifestLocation: 'old-location', + }, + }; + const expectedResult: ChromeModuleRegistry = { + [moduleName]: newEntry, + }; + + const result = moduleInterceptor(remoteModuleRegistry, frontendCRD); + expect(result).toEqual(expectedResult); + }); + + it('should add new entry to moduleRegistry', () => { + const moduleName = 'module-name'; + const newEntry: ChromeModule = { + manifestLocation: 'new-location', + }; + const frontendCRD: FrontendCRD = { + objects: [ + { + metadata: { + name: moduleName, + }, + spec: { + module: newEntry, + }, + }, + ], + }; + const remoteModuleRegistry: ChromeModuleRegistry = {}; + + const expectedResult: ChromeModuleRegistry = { + [moduleName]: newEntry, + }; + + const result = moduleInterceptor(remoteModuleRegistry, frontendCRD); + expect(result).toEqual(expectedResult); + }); +}); diff --git a/packages/config-utils/src/feo/module-interceptor.ts b/packages/config-utils/src/feo/module-interceptor.ts new file mode 100644 index 000000000..49b801a04 --- /dev/null +++ b/packages/config-utils/src/feo/module-interceptor.ts @@ -0,0 +1,11 @@ +import { ChromeModuleRegistry, FrontendCRD } from './feo-types'; + +function moduleInterceptor(moduleRegistry: ChromeModuleRegistry, frontendCRD: FrontendCRD): ChromeModuleRegistry { + const moduleName = frontendCRD.objects[0].metadata.name; + return { + ...moduleRegistry, + [moduleName]: frontendCRD.objects[0].spec.module, + }; +} + +export default moduleInterceptor; diff --git a/packages/config-utils/src/feo/navigation-interceptor.test.ts b/packages/config-utils/src/feo/navigation-interceptor.test.ts index 579d6d3f4..e022e7e26 100644 --- a/packages/config-utils/src/feo/navigation-interceptor.test.ts +++ b/packages/config-utils/src/feo/navigation-interceptor.test.ts @@ -19,6 +19,9 @@ describe('NavigationInterceptor', () => { name: frontendName, }, spec: { + module: { + manifestLocation: 'http://localhost:3000/manifest.json', + }, bundleSegments: [ { bundleId: bundleName, @@ -195,6 +198,9 @@ describe('NavigationInterceptor', () => { name: defaultFrontendName, }, spec: { + module: { + manifestLocation: 'http://localhost:3000/manifest.json', + }, bundleSegments: [ { bundleId: bundleName, @@ -260,6 +266,9 @@ describe('NavigationInterceptor', () => { name: defaultFrontendName, }, spec: { + module: { + manifestLocation: 'http://localhost:3000/manifest.json', + }, bundleSegments: [ { bundleId: bundleName, @@ -349,6 +358,9 @@ describe('NavigationInterceptor', () => { name: defaultFrontendName, }, spec: { + module: { + manifestLocation: 'http://localhost:3000/manifest.json', + }, bundleSegments: [ { bundleId: bundleName, @@ -412,6 +424,9 @@ describe('NavigationInterceptor', () => { name: defaultFrontendName, }, spec: { + module: { + manifestLocation: 'http://localhost:3000/manifest.json', + }, bundleSegments: [ { bundleId: bundleName, @@ -476,6 +491,9 @@ describe('NavigationInterceptor', () => { name: frontendName, }, spec: { + module: { + manifestLocation: 'http://localhost:3000/manifest.json', + }, navigationSegments: [ { segmentId: segmentOneId, From c228b1917389203cc80832d19bccace94ae8b926 Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 16 Dec 2024 11:16:02 +0100 Subject: [PATCH 3/5] feat(feo): add search index interceptor --- navnotes.md | 6 ++ packages/config-utils/src/feo/feo-types.ts | 11 ++++ .../src/feo/search-interceptor.test.ts | 58 +++++++++++++++++++ .../src/feo/search-interceptor.ts | 9 +++ 4 files changed, 84 insertions(+) create mode 100644 packages/config-utils/src/feo/search-interceptor.test.ts create mode 100644 packages/config-utils/src/feo/search-interceptor.ts diff --git a/navnotes.md b/navnotes.md index 407a89a71..6638a86cb 100644 --- a/navnotes.md +++ b/navnotes.md @@ -28,3 +28,9 @@ Same as `bundleSegmentRef`, but for global segments. ### frontendRef Required to match nav item in bundle to current app + +# Search interceptor notes + +## frontendRef + +search entries need a `frontendRef` attribute. Without the attribute, we can modify/add frontend entries, but we can't remove them diff --git a/packages/config-utils/src/feo/feo-types.ts b/packages/config-utils/src/feo/feo-types.ts index c1efcb34c..543c5d661 100644 --- a/packages/config-utils/src/feo/feo-types.ts +++ b/packages/config-utils/src/feo/feo-types.ts @@ -54,6 +54,16 @@ export type ChromeModuleRegistry = { [moduleName: string]: ChromeModule; }; +export type ChromeStaticSearchEntry = { + frontendRef: string; + id: string; + href: string; + title: string; + description: string; + alt_title?: string[]; + isExternal?: boolean; +}; + export type SegmentRef = { segmentId: string; frontendName: string; @@ -95,6 +105,7 @@ export type CRDObject = { bundleSegments?: BundleSegment[]; navigationSegments?: DirectNavItem[]; module: ChromeModule; + searchEntries?: ChromeStaticSearchEntry[]; }; }; diff --git a/packages/config-utils/src/feo/search-interceptor.test.ts b/packages/config-utils/src/feo/search-interceptor.test.ts new file mode 100644 index 000000000..fb69a5d46 --- /dev/null +++ b/packages/config-utils/src/feo/search-interceptor.test.ts @@ -0,0 +1,58 @@ +import { ChromeStaticSearchEntry, FrontendCRD } from './feo-types'; +import searchInterceptor from './search-interceptor'; + +describe('SearchInterceptor', () => { + it('should replace search entries with the ones from the frontendCRD', () => { + const frontendName = 'frontendName'; + const frontendCRD: FrontendCRD = { + objects: [ + { + metadata: { + name: frontendName, + }, + spec: { + module: { + manifestLocation: 'location', + }, + searchEntries: [ + { + frontendRef: frontendName, + id: 'id-1', + href: 'href-1', + title: 'title-1', + description: 'description-1', + }, + { + frontendRef: frontendName, + id: 'id-1', + href: 'href-1', + title: 'title-1', + description: 'description-1', + }, + ], + }, + }, + ], + }; + const remoteSearchEntries: ChromeStaticSearchEntry[] = [ + { + frontendRef: 'otherFrontend', + id: 'otherFrontend', + href: 'otherFrontend', + title: 'otherFrontend', + description: 'otherFrontend', + }, + { + frontendRef: frontendName, + id: frontendName, + href: frontendName, + title: frontendName, + description: frontendName, + }, + ]; + + const expectedSearchEntries: ChromeStaticSearchEntry[] = [remoteSearchEntries[0], ...(frontendCRD.objects[0].spec.searchEntries ?? [])]; + const result = searchInterceptor(remoteSearchEntries, frontendCRD); + expect(result).toEqual(expectedSearchEntries); + }); +}); diff --git a/packages/config-utils/src/feo/search-interceptor.ts b/packages/config-utils/src/feo/search-interceptor.ts new file mode 100644 index 000000000..fd7b4a774 --- /dev/null +++ b/packages/config-utils/src/feo/search-interceptor.ts @@ -0,0 +1,9 @@ +import { ChromeStaticSearchEntry, FrontendCRD } from './feo-types'; + +function searchInterceptor(staticSearchIndex: ChromeStaticSearchEntry[], frontendCRD: FrontendCRD): ChromeStaticSearchEntry[] { + const frontendRef = frontendCRD.objects[0].metadata.name; + const result = staticSearchIndex.filter((entry) => entry.frontendRef !== frontendRef); + return [...result, ...(frontendCRD.objects[0].spec.searchEntries ?? [])]; +} + +export default searchInterceptor; From cd2c515e5c958c55c5baca534b5c91c520f9d53b Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 16 Dec 2024 12:25:11 +0100 Subject: [PATCH 4/5] feat(feo): add service tiles interceptor --- navnotes.md | 6 + packages/config-utils/src/feo/feo-types.ts | 17 +++ .../src/feo/service-tiles-interceptor.test.ts | 127 ++++++++++++++++++ .../src/feo/service-tiles-interceptor.ts | 42 ++++++ 4 files changed, 192 insertions(+) create mode 100644 packages/config-utils/src/feo/service-tiles-interceptor.test.ts create mode 100644 packages/config-utils/src/feo/service-tiles-interceptor.ts diff --git a/navnotes.md b/navnotes.md index 6638a86cb..3facf7a15 100644 --- a/navnotes.md +++ b/navnotes.md @@ -34,3 +34,9 @@ Required to match nav item in bundle to current app ## frontendRef search entries need a `frontendRef` attribute. Without the attribute, we can modify/add frontend entries, but we can't remove them + +# Service tiles interceptor + +## frontendRef + +Service tile entries need a `frontendRef` attribute. Without the attribute, we can modify/add frontend entries, but we can't remove them diff --git a/packages/config-utils/src/feo/feo-types.ts b/packages/config-utils/src/feo/feo-types.ts index 543c5d661..b2e3846ee 100644 --- a/packages/config-utils/src/feo/feo-types.ts +++ b/packages/config-utils/src/feo/feo-types.ts @@ -97,6 +97,22 @@ export type BundleSegment = { navItems: DirectNavItem[]; }; +export type ServiceTile = { + section: string; + group: string; + id: string; + frontendRef: string; +}; + +export type ServiceGroup = { + id: string; + tiles: ServiceTile[]; +}; + +export type ServiceCategory = { + id: string; + groups: ServiceGroup[]; +}; export type CRDObject = { metadata: { name: string; @@ -106,6 +122,7 @@ export type CRDObject = { navigationSegments?: DirectNavItem[]; module: ChromeModule; searchEntries?: ChromeStaticSearchEntry[]; + serviceTiles?: ServiceTile[]; }; }; diff --git a/packages/config-utils/src/feo/service-tiles-interceptor.test.ts b/packages/config-utils/src/feo/service-tiles-interceptor.test.ts new file mode 100644 index 000000000..95baf053a --- /dev/null +++ b/packages/config-utils/src/feo/service-tiles-interceptor.test.ts @@ -0,0 +1,127 @@ +import { FrontendCRD, ServiceCategory } from './feo-types'; +import serviceTilesInterceptor from './service-tiles-interceptor'; + +describe('Service tiles interceptor', () => { + it('should replace service tiles with the ones from the frontendCRD', () => { + const frontendName = 'frontendName'; + const frontendCrd: FrontendCRD = { + objects: [ + { + metadata: { + name: frontendName, + }, + spec: { + module: { + manifestLocation: 'location', + }, + serviceTiles: [ + { + section: 'section-1', + group: 'group-1', + id: 'id-1', + frontendRef: frontendName, + }, + { + section: 'section-1', + group: 'group-1', + id: 'id-2', + frontendRef: frontendName, + }, + { + section: 'section-2', + group: 'group-1', + id: 'id-3', + frontendRef: frontendName, + }, + ], + }, + }, + ], + }; + const remoteServiceTiles: ServiceCategory[] = [ + { + id: 'section-1', + groups: [ + { + id: 'group-1', + tiles: [ + { + section: 'section-1', + group: 'group-1', + id: 'otherFrontend', + frontendRef: 'otherFrontend', + }, + { + section: 'section-1', + group: 'group-1', + id: 'id-2', + frontendRef: frontendName, + }, + ], + }, + ], + }, + { + id: 'section-2', + groups: [ + { + id: 'group-1', + tiles: [ + { + section: 'section-2', + group: 'group-1', + id: 'otherFrontend', + frontendRef: 'otherFrontend', + }, + ], + }, + ], + }, + ]; + const expectedServiceTiles: ServiceCategory[] = [ + { + id: 'section-1', + groups: [ + { + id: 'group-1', + tiles: [ + remoteServiceTiles[0].groups[0].tiles[0], + { + section: 'section-1', + group: 'group-1', + id: 'id-1', + frontendRef: frontendName, + }, + { + section: 'section-1', + group: 'group-1', + id: 'id-2', + frontendRef: frontendName, + }, + ], + }, + ], + }, + { + id: 'section-2', + groups: [ + { + id: 'group-1', + tiles: [ + remoteServiceTiles[1].groups[0].tiles[0], + { + section: 'section-2', + group: 'group-1', + id: 'id-3', + frontendRef: frontendName, + }, + ], + }, + ], + }, + ]; + + const result = serviceTilesInterceptor(remoteServiceTiles, frontendCrd); + expect(result).toEqual(expectedServiceTiles); + }); +}); diff --git a/packages/config-utils/src/feo/service-tiles-interceptor.ts b/packages/config-utils/src/feo/service-tiles-interceptor.ts new file mode 100644 index 000000000..c2793cee3 --- /dev/null +++ b/packages/config-utils/src/feo/service-tiles-interceptor.ts @@ -0,0 +1,42 @@ +import { FrontendCRD, ServiceCategory, ServiceTile } from './feo-types'; + +function serviceTilesInterceptor(serviceCategories: ServiceCategory[], frontendCrd: FrontendCRD): ServiceCategory[] { + const frontendRef = frontendCrd.objects[0].metadata.name; + let result = [...serviceCategories]; + + const frontendCategories = + frontendCrd.objects[0].spec.serviceTiles?.reduce<{ + [section: string]: { [group: string]: ServiceTile[] }; + }>((acc, tile) => { + const section = tile.section; + const group = tile.group; + if (!acc[section]) { + acc[section] = {}; + } + + if (!acc[section][group]) { + acc[section][group] = []; + } + + acc[section][group].push({ ...tile }); + return acc; + }, {}) ?? {}; + + result = result.map((category) => { + const newGroups = category.groups.map((group) => { + const newTiles = group.tiles.filter((tile) => tile.frontendRef !== frontendRef); + return { + ...group, + tiles: [...newTiles, ...(frontendCategories[category.id]?.[group.id] ?? [])], + }; + }); + return { + ...category, + groups: newGroups, + }; + }); + + return result; +} + +export default serviceTilesInterceptor; From fe911996498b9d7a36956fbf622bd4c9895f11fc Mon Sep 17 00:00:00 2001 From: Martin Marosi Date: Mon, 16 Dec 2024 12:38:39 +0100 Subject: [PATCH 5/5] feat(feo): add widget rehistry interceptor --- navnotes.md | 6 ++++ packages/config-utils/src/feo/feo-types.ts | 8 +++++ .../feo/widget-registry-interceptor.test.ts | 35 +++++++++++++++++++ .../src/feo/widget-registry-interceptor.ts | 10 ++++++ 4 files changed, 59 insertions(+) create mode 100644 packages/config-utils/src/feo/widget-registry-interceptor.test.ts create mode 100644 packages/config-utils/src/feo/widget-registry-interceptor.ts diff --git a/navnotes.md b/navnotes.md index 3facf7a15..289e27af0 100644 --- a/navnotes.md +++ b/navnotes.md @@ -40,3 +40,9 @@ search entries need a `frontendRef` attribute. Without the attribute, we can mod ## frontendRef Service tile entries need a `frontendRef` attribute. Without the attribute, we can modify/add frontend entries, but we can't remove them + +# Widget registry interceptor + +## frontendRef + +Widget registry entries need a `frontendRef` attribute. Without the attribute, we can modify/add frontend entries, but we can't remove them diff --git a/packages/config-utils/src/feo/feo-types.ts b/packages/config-utils/src/feo/feo-types.ts index b2e3846ee..3cac5f580 100644 --- a/packages/config-utils/src/feo/feo-types.ts +++ b/packages/config-utils/src/feo/feo-types.ts @@ -113,6 +113,13 @@ export type ServiceCategory = { id: string; groups: ServiceGroup[]; }; + +export type ChromeWidgetEntry = { + scope: string; + module: string; + frontendRef: string; +}; + export type CRDObject = { metadata: { name: string; @@ -123,6 +130,7 @@ export type CRDObject = { module: ChromeModule; searchEntries?: ChromeStaticSearchEntry[]; serviceTiles?: ServiceTile[]; + widgetRegistry?: ChromeWidgetEntry[]; }; }; diff --git a/packages/config-utils/src/feo/widget-registry-interceptor.test.ts b/packages/config-utils/src/feo/widget-registry-interceptor.test.ts new file mode 100644 index 000000000..97120ee8b --- /dev/null +++ b/packages/config-utils/src/feo/widget-registry-interceptor.test.ts @@ -0,0 +1,35 @@ +import { FrontendCRD } from './feo-types'; +import widgetRegistryInterceptor from './widget-registry-interceptor'; + +describe('Widget registry interceptor', () => { + it('should replace the widget registry with the one from the server', () => { + const frontendName = 'name'; + const widgetEntries = [ + { module: 'module1', scope: 'scope1', frontendRef: frontendName }, + { module: 'module1', scope: 'scope2', frontendRef: frontendName }, + { module: 'module2', scope: 'scope1', frontendRef: 'foo' }, + ]; + const frontendCrd: FrontendCRD = { + objects: [ + { + metadata: { + name: 'name', + }, + spec: { + module: { + manifestLocation: 'location', + }, + widgetRegistry: [{ module: 'module1', scope: 'scope1', frontendRef: frontendName }], + }, + }, + ], + }; + + const result = widgetRegistryInterceptor(widgetEntries, frontendCrd); + + expect(result).toEqual([ + { module: 'module2', scope: 'scope1', frontendRef: 'foo' }, + { module: 'module1', scope: 'scope1', frontendRef: frontendName }, + ]); + }); +}); diff --git a/packages/config-utils/src/feo/widget-registry-interceptor.ts b/packages/config-utils/src/feo/widget-registry-interceptor.ts new file mode 100644 index 000000000..fc62c2917 --- /dev/null +++ b/packages/config-utils/src/feo/widget-registry-interceptor.ts @@ -0,0 +1,10 @@ +import { ChromeWidgetEntry, FrontendCRD } from './feo-types'; + +function widgetRegistryInterceptor(widgetEntries: ChromeWidgetEntry[], frontendCrd: FrontendCRD): ChromeWidgetEntry[] { + const frontendName = frontendCrd.objects[0].metadata.name; + const result = widgetEntries.filter((entry) => entry.frontendRef !== frontendName); + + return [...result, ...(frontendCrd.objects[0].spec.widgetRegistry ?? [])]; +} + +export default widgetRegistryInterceptor;