diff --git a/package-lock.json b/package-lock.json index f1ce22958..d1a425243 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@data-driven-forms/react-form-renderer": "^3.22.1", "@formatjs/cli": "4.8.4", "@openshift/dynamic-plugin-sdk": "^5.0.1", + "@orama/orama": "^2.0.3", "@patternfly/patternfly": "^5.3.0", "@patternfly/quickstarts": "^5.3.0", "@patternfly/react-charts": "^7.3.0", @@ -4110,6 +4111,14 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/@orama/orama": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@orama/orama/-/orama-2.0.3.tgz", + "integrity": "sha512-8BXTrXqP+kcyIExipZyf6voB3pzGPREh1BUrIqEP7V4PJwN/SnEcLJsafyPiPFM23fPSyH9krwLrXzvisLL19A==", + "engines": { + "node": ">= 16.0.0" + } + }, "node_modules/@patternfly/patternfly": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/@patternfly/patternfly/-/patternfly-5.3.0.tgz", diff --git a/package.json b/package.json index bb3693365..82de49353 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,7 @@ "@patternfly/react-core": "^5.3.0", "@patternfly/react-icons": "^5.3.0", "@patternfly/react-tokens": "^5.3.0", + "@orama/orama": "^2.0.3", "@redhat-cloud-services/frontend-components": "^4.2.2", "@redhat-cloud-services/chrome": "^1.0.9", "@redhat-cloud-services/entitlements-client": "1.2.0", diff --git a/src/state/atoms/localSearchAtom.ts b/src/state/atoms/localSearchAtom.ts new file mode 100644 index 000000000..b00c9cc3b --- /dev/null +++ b/src/state/atoms/localSearchAtom.ts @@ -0,0 +1,93 @@ +import { atom } from 'jotai'; +import { Orama, create, insert } from '@orama/orama'; + +import { getChromeStaticPathname } from '../../utils/common'; +import axios from 'axios'; + +type IndexEntry = { + icon?: string; + title: string[]; + bundle: string[]; + bundleTitle: string[]; + id: string; + uri: string; + relative_uri: string; + poc_description_t: string; + alt_title: string[]; +}; + +type SearchEntry = { + title: string; + uri: string; + pathname: string; + description: string; + icon?: string; + id: string; + bundleTitle: string; + altTitle?: string[]; +}; + +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 (!entry.relative_uri.startsWith('/')) { + console.warn('External ink found in the index. Ignoring: ', entry.relative_uri); + return; + } + idSet.add(entry.id); + 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; +}); + +const entrySchema = { + title: 'string', + description: 'string', + altTitle: 'string[]', + descriptionMatch: 'string', + bundleTitle: 'string', + pathname: 'string', +} as const; + +async function insertEntry(db: Orama, entry: SearchEntry) { + return insert(db, { + id: entry.id, + title: entry.title, + description: entry.description, + descriptionMatch: entry.description, + altTitle: entry.altTitle ?? [], + bundleTitle: entry.bundleTitle, + pathname: entry.pathname, + }); +} + +export const asyncLocalOrama = atom(async (get) => { + const db: Orama = await create({ + schema: entrySchema, + }); + + const insertCommands = (await get(asyncSearchIndexAtom)).map(async (entry) => { + return insertEntry(db, entry); + }); + + await Promise.all(insertCommands); + return db; +}); diff --git a/src/utils/common.ts b/src/utils/common.ts index b75195c24..38d91a03f 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -354,7 +354,7 @@ export const chromeServiceStaticPathname = { }, }; -export function getChromeStaticPathname(type: 'modules' | 'navigation' | 'services') { +export function getChromeStaticPathname(type: 'modules' | 'navigation' | 'services' | 'search') { const stableEnv = isBeta() ? 'beta' : 'stable'; const prodEnv = isProd() ? 'prod' : ITLess() ? 'itless' : 'stage'; return `${CHROME_SERVICE_BASE}${chromeServiceStaticPathname[stableEnv][prodEnv]}/${type}`;