diff --git a/src/state/atoms/localSearchAtom.ts b/src/state/atoms/localSearchAtom.ts index 7e89c5669..f2e5df8a0 100644 --- a/src/state/atoms/localSearchAtom.ts +++ b/src/state/atoms/localSearchAtom.ts @@ -1,7 +1,7 @@ import { atom } from 'jotai'; import { Orama, create, insert } from '@orama/orama'; -import { getChromeStaticPathname } from '../../utils/common'; +import { GENERATED_SEARCH_FLAG, getChromeStaticPathname } from '../../utils/common'; import axios from 'axios'; import { NavItemPermission } from '../../@types/types'; @@ -29,37 +29,73 @@ type SearchEntry = { altTitle?: string[]; }; +type GeneratedSearchIndexResponse = { + alt_title?: string[]; + id: string; + href: string; + title: string; + description?: string; +}; + export const SearchPermissions = new Map(); export const SearchPermissionsCache = new Map(); const asyncSearchIndexAtom = atom(async () => { const staticPath = getChromeStaticPathname('search'); - const { data: rawIndex } = await axios.get(`${staticPath}/search-index.json`); const searchIndex: SearchEntry[] = []; const idSet = new Set(); - rawIndex.forEach((entry) => { - if (idSet.has(entry.id)) { - console.warn('Duplicate id found in index', entry.id); - return; - } + if (localStorage.getItem(GENERATED_SEARCH_FLAG) === 'true') { + // parse data from generated search index + const { data: rawIndex } = await axios.get(`/api/chrome-service/v1/static/search-index-generated.json`); + rawIndex.forEach((entry) => { + if (idSet.has(entry.id)) { + console.warn('Duplicate id found in index', entry.id); + return; + } - if (!entry.relative_uri.startsWith('/')) { - console.warn('External ink found in the index. Ignoring: ', entry.relative_uri); - return; - } - idSet.add(entry.id); - SearchPermissions.set(entry.id, entry.permissions ?? []); - searchIndex.push({ - title: entry.title[0], - uri: entry.uri, - pathname: entry.relative_uri, - description: entry.poc_description_t || entry.relative_uri, - icon: entry.icon, - id: entry.id, - bundleTitle: entry.bundleTitle[0], - altTitle: entry.alt_title, + if (!entry.href.startsWith('/')) { + console.warn('External ink found in the index. Ignoring: ', entry.href); + return; + } + idSet.add(entry.id); + SearchPermissions.set(entry.id, []); + searchIndex.push({ + title: entry.title, + uri: entry.href, + pathname: entry.href, + description: entry.description ?? entry.href, + icon: undefined, + id: entry.id, + bundleTitle: entry.title, + altTitle: entry.alt_title, + }); }); - }); + } else { + const { data: rawIndex } = await axios.get(`${staticPath}/search-index.json`); + rawIndex.forEach((entry) => { + if (idSet.has(entry.id)) { + console.warn('Duplicate id found in index', entry.id); + return; + } + + if (!entry.relative_uri.startsWith('/')) { + console.warn('External ink found in the index. Ignoring: ', entry.relative_uri); + return; + } + idSet.add(entry.id); + SearchPermissions.set(entry.id, entry.permissions ?? []); + searchIndex.push({ + title: entry.title[0], + uri: entry.uri, + pathname: entry.relative_uri, + description: entry.poc_description_t || entry.relative_uri, + icon: entry.icon, + id: entry.id, + bundleTitle: entry.bundleTitle[0], + altTitle: entry.alt_title, + }); + }); + } return searchIndex; }); diff --git a/src/utils/common.ts b/src/utils/common.ts index f4fd87ad8..447a696ac 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -348,6 +348,8 @@ const fedModulesheaders = { Expires: '0', }; +export const GENERATED_SEARCH_FLAG = '@chrome:generated-search-index'; + // FIXME: Remove once qaprodauth is dealt with // can't use /beta because it will ge redirected by Akamai to /preview and we don't have any assets there\\ // Always use stable @@ -356,10 +358,14 @@ const loadCSCFedModules = () => headers: fedModulesheaders, }); -export const loadFedModules = async () => - Promise.all([ +export const loadFedModules = async () => { + const fedModulesPath = + localStorage.getItem(GENERATED_SEARCH_FLAG) === 'true' + ? '/api/chrome-service/v1/static/fed-modules-generated.json' + : `${getChromeStaticPathname('modules')}/fed-modules.json`; + return Promise.all([ axios - .get(`${getChromeStaticPathname('modules')}/fed-modules.json`, { + .get(fedModulesPath, { headers: fedModulesheaders, }) .catch(loadCSCFedModules), @@ -370,6 +376,7 @@ export const loadFedModules = async () => } return staticConfig; }); +}; export const generateRoutesList = (modules: { [key: string]: ChromeModule }) => Object.entries(modules) diff --git a/src/utils/fetchNavigationFiles.ts b/src/utils/fetchNavigationFiles.ts index a570b1aec..3f40389f5 100644 --- a/src/utils/fetchNavigationFiles.ts +++ b/src/utils/fetchNavigationFiles.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import { BundleNavigation, NavItem, Navigation } from '../@types/types'; import { Required } from 'utility-types'; import { itLessBundles, requiredBundles } from '../components/AppFilter/useAppFilter'; -import { ITLess, getChromeStaticPathname } from './common'; +import { GENERATED_SEARCH_FLAG, ITLess, getChromeStaticPathname } from './common'; export function isBundleNavigation(item: unknown): item is BundleNavigation { return typeof item !== 'undefined'; @@ -38,6 +38,12 @@ const filesCache: { }; const fetchNavigationFiles = async () => { + if (localStorage.getItem(GENERATED_SEARCH_FLAG) === 'true') { + // aggregate data call + const { data: aggregateData } = await axios.get('/api/chrome-service/v1/static/bundles-generated.json'); + const bundleNavigation = aggregateData.filter(isBundleNavigation); + return bundleNavigation; + } const bundles = ITLess() ? itLessBundles : requiredBundles; if (filesCache.ready && filesCache.expires > Date.now()) { return filesCache.data; diff --git a/src/utils/useNavigation.ts b/src/utils/useNavigation.ts index 463343670..72843969b 100644 --- a/src/utils/useNavigation.ts +++ b/src/utils/useNavigation.ts @@ -2,13 +2,14 @@ import axios from 'axios'; import { useAtomValue, useSetAtom } from 'jotai'; import { useContext, useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { BLOCK_CLEAR_GATEWAY_ERROR, getChromeStaticPathname } from './common'; +import { BLOCK_CLEAR_GATEWAY_ERROR, GENERATED_SEARCH_FLAG, getChromeStaticPathname } from './common'; import { evaluateVisibility } from './isNavItemVisible'; import { QuickStartContext } from '@patternfly/quickstarts'; import { useFlagsStatus } from '@unleash/proxy-client-react'; import { BundleNavigation, NavItem, Navigation } from '../@types/types'; import { clearGatewayErrorAtom } from '../state/atoms/gatewayErrorAtom'; import { navigationAtom, setNavigationSegmentAtom } from '../state/atoms/navigationAtom'; +import fetchNavigationFiles from './fetchNavigationFiles'; function cleanNavItemsHref(navItem: NavItem) { const result = { ...navItem }; @@ -100,11 +101,48 @@ const useNavigation = () => { }); }; + async function handleNavigationResponse(data: BundleNavigation) { + let observer: MutationObserver | undefined; + if (observer && typeof observer.disconnect === 'function') { + observer.disconnect(); + } + + try { + const navItems = await Promise.all(data.navItems.map(cleanNavItemsHref).map(evaluateVisibility)); + const schema: any = { + ...data, + navItems, + }; + observer = registerLocationObserver(pathname, schema); + observer.observe(document.querySelector('body')!, { + childList: true, + subtree: true, + }); + } catch (error) { + // Hide nav if an error was encountered. Can happen for non-existing navigation files. + setNoNav(true); + } + } + useEffect(() => { let observer: MutationObserver | undefined; // reset no nav flag setNoNav(false); - if (currentNamespace && (flagsReady || flagsError)) { + if (localStorage.getItem(GENERATED_SEARCH_FLAG) === 'true' && currentNamespace && (flagsReady || flagsError)) { + fetchNavigationFiles() + .then((bundles) => { + const bundle = bundles.find((b) => b.id === currentNamespace); + if (!bundle) { + setNoNav(true); + return; + } + + return handleNavigationResponse(bundle); + }) + .catch(() => { + setNoNav(true); + }); + } else if (currentNamespace && (flagsReady || flagsError)) { axios .get(`${getChromeStaticPathname('navigation')}/${currentNamespace}-navigation.json`) // fallback static CSC for EE env @@ -112,26 +150,7 @@ const useNavigation = () => { return axios.get(`/config/chrome/${currentNamespace}-navigation.json?ts=${Date.now()}`); }) .then(async (response) => { - if (observer && typeof observer.disconnect === 'function') { - observer.disconnect(); - } - - const data = response.data; - try { - const navItems = await Promise.all(data.navItems.map(cleanNavItemsHref).map(evaluateVisibility)); - const schema = { - ...data, - navItems, - }; - observer = registerLocationObserver(pathname, schema); - observer.observe(document.querySelector('body')!, { - childList: true, - subtree: true, - }); - } catch (error) { - // Hide nav if an error was encountered. Can happen for non-existing navigation files. - setNoNav(true); - } + return handleNavigationResponse(response.data); }) .catch(() => { setNoNav(true);