From c7efecf29a79f827d774559782218963fc4d838e Mon Sep 17 00:00:00 2001 From: Andrew Jiang Date: Tue, 2 Jul 2024 18:14:06 -0400 Subject: [PATCH] fix: algolia records should index endpoint body shape (#1103) Co-authored-by: dsinghvi --- .../commons/search-utils/src/SearchConfig.ts | 71 +++--- packages/ui/app/src/search/SearchDialog.tsx | 18 +- .../search/algolia/useAlgoliaSearchClient.ts | 15 +- .../src/search/inkeep/InkeepCustomTrigger.tsx | 10 +- .../src/search/inkeep/useInkeepSettings.ts | 10 +- .../ui/app/src/sidebar/SidebarSearchBar.tsx | 14 +- .../algolia/AlgoliaSearchRecordGenerator.ts | 230 +++++++++++++++++- .../services/algolia/getAllReferencedTypes.ts | 135 ++++++++++ 8 files changed, 429 insertions(+), 74 deletions(-) create mode 100644 servers/fdr/src/services/algolia/getAllReferencedTypes.ts diff --git a/packages/commons/search-utils/src/SearchConfig.ts b/packages/commons/search-utils/src/SearchConfig.ts index cc0e4ea1f8..eb69cd86f0 100644 --- a/packages/commons/search-utils/src/SearchConfig.ts +++ b/packages/commons/search-utils/src/SearchConfig.ts @@ -16,6 +16,7 @@ export const REGISTRY_SERVICE = new FdrClient({ const FEATURE_FLAGS = ["inkeep-enabled" as const]; export type InkeepSharedSettings = DeepReadonly<{ + replaceSearch: boolean; baseSettings: InkeepWidgetBaseSettings; aiChatSettings?: Partial; searchSettings?: Partial; @@ -39,48 +40,35 @@ export declare namespace SearchConfig { } interface Inkeep extends InkeepSharedSettings { - isAvailable: true; - type: "inkeep"; + replaceSearch: boolean; } interface Algolia { - isAvailable: true; - type: "algolia"; appId: string; searchApiKey: Unversioned | Versioned; index: string; } + interface Available { + isAvailable: true; + inkeep?: Inkeep; + algolia: Algolia; + } + interface Unavailable { isAvailable: false; } } -export type SearchConfig = SearchConfig.Inkeep | SearchConfig.Algolia | SearchConfig.Unavailable; +export type SearchConfig = SearchConfig.Available | SearchConfig.Unavailable; export interface SearchRequest { searchInfo: Algolia.SearchInfo | undefined; } -export async function getSearchConfig(domain: string, { searchInfo }: SearchRequest): Promise { - try { - const config = await getAll(FEATURE_FLAGS); - const maybeInkeep = config["inkeep-enabled"]?.[domain]; - - if (maybeInkeep?.baseSettings.integrationId != null) { - return { - isAvailable: true, - type: "inkeep", - ...maybeInkeep, - }; - } - } catch (e) { - // eslint-disable-next-line no-console - console.error("Error fetching edge config", e); - } - +async function getAlgoliaSearchConfig({ searchInfo }: SearchRequest): Promise { if (typeof searchInfo !== "object" || searchInfo.type === "legacyMultiAlgoliaIndex") { - return { isAvailable: false }; + return undefined; } const algoliaAppId = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID; @@ -95,12 +83,10 @@ export async function getSearchConfig(domain: string, { searchInfo }: SearchRequ }); if (!resp.ok) { - return { isAvailable: false }; + return undefined; } return { - isAvailable: true, - type: "algolia", appId: algoliaAppId, searchApiKey: { type: "unversioned", @@ -117,19 +103,17 @@ export async function getSearchConfig(domain: string, { searchInfo }: SearchRequ }); if (!resp.ok) { - return { isAvailable: false }; + return undefined; } values[versionId] = resp.body.searchApiKey; } if (Object.keys(values).length === 0) { - return { isAvailable: false }; + return undefined; } return { - isAvailable: true, - type: "algolia", appId: algoliaAppId, searchApiKey: { type: "versioned", @@ -139,5 +123,30 @@ export async function getSearchConfig(domain: string, { searchInfo }: SearchRequ }; } - return { isAvailable: false }; + return undefined; +} + +export async function getSearchConfig(domain: string, searchRequest: SearchRequest): Promise { + const algolia = await getAlgoliaSearchConfig(searchRequest); + if (algolia == null) { + return { isAvailable: false }; + } + + try { + const config = await getAll(FEATURE_FLAGS); + const maybeInkeep = config["inkeep-enabled"]?.[domain]; + + if (maybeInkeep?.baseSettings.integrationId != null) { + return { + isAvailable: true, + algolia, + inkeep: maybeInkeep, + }; + } + } catch (e) { + // eslint-disable-next-line no-console + console.error("Error fetching edge config", e); + } + + return { isAvailable: true, algolia }; } diff --git a/packages/ui/app/src/search/SearchDialog.tsx b/packages/ui/app/src/search/SearchDialog.tsx index 3c01f79762..c41ce5e691 100644 --- a/packages/ui/app/src/search/SearchDialog.tsx +++ b/packages/ui/app/src/search/SearchDialog.tsx @@ -32,20 +32,20 @@ export const SearchDialog: React.FC = ({ fromHeader }) => { return null; } - if (config.type === "algolia") { + if (config.inkeep == null) { return ; - } - - if (config.type === "inkeep") { + } else { return ( <> - + {config.inkeep.replaceSearch ? ( + + ) : ( + + )} ); } - - return null; }; export declare namespace SearchSidebar { @@ -69,7 +69,7 @@ export const SearchSidebar: React.FC> = ( return <>{children}; } - if (searchConfig.type === "algolia" && algoliaSearchClient != null) { + if (searchConfig.inkeep?.replaceSearch !== true && algoliaSearchClient != null) { const [searchClient, indexName] = algoliaSearchClient; return ( @@ -80,7 +80,7 @@ export const SearchSidebar: React.FC> = ( ); } - if (searchConfig.type === "inkeep") { + if (searchConfig.inkeep != null) { return ( <>
diff --git a/packages/ui/app/src/search/algolia/useAlgoliaSearchClient.ts b/packages/ui/app/src/search/algolia/useAlgoliaSearchClient.ts index 0eaeecd14d..b5cd5b5ef3 100644 --- a/packages/ui/app/src/search/algolia/useAlgoliaSearchClient.ts +++ b/packages/ui/app/src/search/algolia/useAlgoliaSearchClient.ts @@ -9,25 +9,28 @@ export function useAlgoliaSearchClient(): [SearchClient, index: string] | undefi const [searchConfig] = useSearchConfig(); return useMemo(() => { - if (!searchConfig.isAvailable || searchConfig.type !== "algolia") { + if (!searchConfig.isAvailable) { return; } - if (searchConfig.searchApiKey.type === "unversioned") { - return [algolia(searchConfig.appId, searchConfig.searchApiKey.value), searchConfig.index]; + if (searchConfig.algolia.searchApiKey.type === "unversioned") { + return [ + algolia(searchConfig.algolia.appId, searchConfig.algolia.searchApiKey.value), + searchConfig.algolia.index, + ]; } - if (searchConfig.searchApiKey.type === "versioned") { + if (searchConfig.algolia.searchApiKey.type === "versioned") { assertNonNullish( currentVersionId, "Inconsistent State: Received search info is versioned but docs are unversioned.", ); - const searchApiKey = searchConfig.searchApiKey.values[currentVersionId]; + const searchApiKey = searchConfig.algolia.searchApiKey.values[currentVersionId]; assertNonNullish( searchApiKey, `Inconsistent State: Did not receive index segment for version "${currentVersionId}". This may indicate a backend bug.`, ); - return [algolia(searchConfig.appId, searchApiKey), searchConfig.index]; + return [algolia(searchConfig.algolia.appId, searchApiKey), searchConfig.algolia.index]; } return; }, [currentVersionId, searchConfig]); diff --git a/packages/ui/app/src/search/inkeep/InkeepCustomTrigger.tsx b/packages/ui/app/src/search/inkeep/InkeepCustomTrigger.tsx index d6906ee7c5..571f0a9f46 100644 --- a/packages/ui/app/src/search/inkeep/InkeepCustomTrigger.tsx +++ b/packages/ui/app/src/search/inkeep/InkeepCustomTrigger.tsx @@ -1,26 +1,22 @@ import { useEventCallback } from "@fern-ui/react-commons"; -import { atom, useAtom } from "jotai"; +import { useAtom } from "jotai"; import dynamic from "next/dynamic"; import { ReactElement } from "react"; -import { useSearchTrigger } from "../useSearchTrigger"; +import { SEARCH_DIALOG_OPEN_ATOM } from "../../atoms/sidebar"; import useInkeepSettings from "./useInkeepSettings"; const CustomTrigger = dynamic(() => import("@inkeep/widgets").then((mod) => mod.InkeepCustomTrigger), { ssr: false, }); -export const INKEEP_TRIGGER = atom(false); - export function InkeepCustomTrigger(): ReactElement | null { const settings = useInkeepSettings(); - const [isOpen, setIsOpen] = useAtom(INKEEP_TRIGGER); + const [isOpen, setIsOpen] = useAtom(SEARCH_DIALOG_OPEN_ATOM); const handleClose = useEventCallback(() => { setIsOpen(false); }); - useSearchTrigger(setIsOpen); - if (settings == null) { return null; } diff --git a/packages/ui/app/src/search/inkeep/useInkeepSettings.ts b/packages/ui/app/src/search/inkeep/useInkeepSettings.ts index 683e52d2a4..d22d08c066 100644 --- a/packages/ui/app/src/search/inkeep/useInkeepSettings.ts +++ b/packages/ui/app/src/search/inkeep/useInkeepSettings.ts @@ -19,7 +19,7 @@ const useInkeepSettings = (): const { resolvedTheme: theme } = useTheme(); const [searchConfig] = useSearchConfig(); - if (!searchConfig.isAvailable || searchConfig.type !== "inkeep") { + if (!searchConfig.isAvailable || searchConfig.inkeep == null) { return; } @@ -37,15 +37,15 @@ const useInkeepSettings = (): }, }, }, - ...searchConfig.baseSettings, + ...searchConfig.inkeep.baseSettings, }; return { // cast readonly -> mutable types, since inkeep widget expects mutable types baseSettings: baseSettings as InkeepWidgetBaseSettings, - aiChatSettings: searchConfig.aiChatSettings as InkeepAIChatSettings | undefined, - searchSettings: searchConfig.searchSettings as InkeepSearchSettings | undefined, - modalSettings: searchConfig.modalSettings as InkeepModalSettings | undefined, + aiChatSettings: searchConfig.inkeep.aiChatSettings as InkeepAIChatSettings | undefined, + searchSettings: searchConfig.inkeep.searchSettings as InkeepSearchSettings | undefined, + modalSettings: searchConfig.inkeep.modalSettings as InkeepModalSettings | undefined, }; }; diff --git a/packages/ui/app/src/sidebar/SidebarSearchBar.tsx b/packages/ui/app/src/sidebar/SidebarSearchBar.tsx index 622b94e8d2..e1c850ce6b 100644 --- a/packages/ui/app/src/sidebar/SidebarSearchBar.tsx +++ b/packages/ui/app/src/sidebar/SidebarSearchBar.tsx @@ -1,10 +1,7 @@ -import { useEventCallback } from "@fern-ui/react-commons"; import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; import cn from "clsx"; -import { useSetAtom } from "jotai"; import { memo } from "react"; import { useOpenSearchDialog } from "../atoms/sidebar"; -import { INKEEP_TRIGGER } from "../search/inkeep/InkeepCustomTrigger"; import { useSearchConfig } from "../services/useSearchService"; export declare namespace SidebarSearchBar { @@ -19,20 +16,11 @@ export const SidebarSearchBar: React.FC = memo(function hideKeyboardShortcutHint, }) { const openSearchDialog = useOpenSearchDialog(); - const openInkeepCustomTrigger = useSetAtom(INKEEP_TRIGGER); const [searchService] = useSearchConfig(); - const handleClick = useEventCallback(() => { - if (searchService.isAvailable && searchService.type === "inkeep") { - openInkeepCustomTrigger(true); - } else { - openSearchDialog(); - } - }); - return (