Skip to content

Commit

Permalink
feat: launchdarkly context + chore: refactor edge functions (#1604)
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity authored Oct 8, 2024
1 parent 488e327 commit 365c1e6
Show file tree
Hide file tree
Showing 98 changed files with 887 additions and 535 deletions.
2 changes: 1 addition & 1 deletion packages/commons/fdr-utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"include": ["./src/**/*"],
"references": [
{
"path": "../../fdr-sdk"
"path": "../../fdr-sdk/tsconfig.build.json"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const Breadcrumb = memo(
return (
<Head>
<script
key="jsonld-breadcrumb"
type="application/ld+json"
id="jsonld-breadcrumb"
dangerouslySetInnerHTML={{
Expand Down
3 changes: 1 addition & 2 deletions packages/commons/search-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
},
"dependencies": {
"@fern-api/fdr-sdk": "workspace:*",
"@fern-ui/core-utils": "workspace:*",
"@vercel/edge-config": "^1.1.0"
"@fern-ui/core-utils": "workspace:*"
},
"devDependencies": {
"@fern-platform/configs": "workspace:*",
Expand Down
29 changes: 7 additions & 22 deletions packages/commons/search-utils/src/SearchConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@ import type {
InkeepSearchSettings,
InkeepWidgetBaseSettings,
} from "@inkeep/widgets";
import { getAll } from "@vercel/edge-config";
import type { DeepReadonly } from "ts-essentials";

const FEATURE_FLAGS = ["inkeep-enabled" as const];

export type InkeepSharedSettings = DeepReadonly<{
replaceSearch: boolean;
baseSettings: InkeepWidgetBaseSettings;
Expand All @@ -20,10 +17,6 @@ export type InkeepSharedSettings = DeepReadonly<{
modalSettings?: Partial<InkeepModalSettings>;
}>;

interface EdgeConfigResponse {
"inkeep-enabled"?: Record<string, InkeepSharedSettings>;
}

export declare namespace SearchConfig {
interface Unversioned {
type: "unversioned";
Expand Down Expand Up @@ -128,8 +121,8 @@ async function getAlgoliaSearchConfig(

export async function getSearchConfig(
client: FdrClient,
domain: string,
searchInfo: Algolia.SearchInfo,
inkeep: InkeepSharedSettings | undefined,
): Promise<SearchConfig> {
const algolia = await getAlgoliaSearchConfig(client, searchInfo);

Expand All @@ -138,20 +131,12 @@ export async function getSearchConfig(
return { isAvailable: false };
}

try {
const config = await getAll<EdgeConfigResponse>(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);
if (inkeep?.baseSettings.integrationId != null) {
return {
isAvailable: true,
algolia,
inkeep,
};
}

return { isAvailable: true, algolia };
Expand Down
2 changes: 1 addition & 1 deletion packages/commons/search-utils/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"include": ["./src/**/*"],
"references": [
{
"path": "../../fdr-sdk"
"path": "../../fdr-sdk/tsconfig.build.json"
},
{
"path": "../core-utils"
Expand Down
2 changes: 1 addition & 1 deletion packages/fdr-sdk/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./tsconfig.json",
"exclude": ["**/__test__/**"]
"exclude": ["node_modules", "**/__test__/**", "**/*.json"]
}
2 changes: 1 addition & 1 deletion packages/healthchecks/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"path": "../commons/core-utils"
},
{
"path": "../fdr-sdk"
"path": "../../fdr-sdk/tsconfig.build.json"
}
]
}
2 changes: 1 addition & 1 deletion packages/template-resolver/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"include": ["./src/**/*"],
"references": [
{
"path": "../fdr-sdk"
"path": "../fdr-sdk/tsconfig.build.json"
}
]
}
3 changes: 2 additions & 1 deletion packages/ui/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"exports": {
"./bundlers": "./src/mdx/bundlers/index.ts",
"./bundlers/next-mdx-remote": "./src/mdx/bundlers/next-mdx-remote.ts",
"./auth": "./src/auth/index.ts",
".": "./src/index.ts"
},
"sideEffects": [
Expand Down Expand Up @@ -40,6 +39,7 @@
"@fern-ui/components": "workspace:*",
"@fern-ui/core-utils": "workspace:*",
"@fern-ui/fdr-utils": "workspace:*",
"@fern-ui/fern-docs-utils": "workspace:*",
"@fern-ui/loadable": "workspace:*",
"@fern-ui/next-seo": "workspace:*",
"@fern-ui/react-commons": "workspace:*",
Expand Down Expand Up @@ -120,6 +120,7 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@fern-ui/fern-docs-auth": "workspace:*",
"@chromatic-com/storybook": "^1.3.5",
"@fern-platform/configs": "workspace:*",
"@mdx-js/esbuild": "^3.0.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/atoms/auth.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FernUser } from "@fern-ui/fern-docs-auth";
import { useAtomValue } from "jotai";
import { selectAtom } from "jotai/utils";
import { isEqual } from "lodash-es";
import type { FernUser } from "../auth";
import { DOCS_ATOM } from "./docs";

export const FERN_USER_ATOM = selectAtom(DOCS_ATOM, (docs) => docs.user, isEqual);
Expand Down
36 changes: 2 additions & 34 deletions packages/ui/app/src/atoms/docs.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,9 @@
import { DocsV1Read } from "@fern-api/fdr-sdk";
import * as FernNavigation from "@fern-api/fdr-sdk/navigation";
import { DEFAULT_FEATURE_FLAGS } from "@fern-ui/fern-docs-utils";
import { atomWithReducer, useHydrateAtoms } from "jotai/utils";
import type { PropsWithChildren, ReactNode } from "react";
import type { DocsProps, FeatureFlags } from "./types";

export const DEFAULT_FEATURE_FLAGS: FeatureFlags = {
isApiPlaygroundEnabled: false,
isApiScrollingDisabled: false,
isWhitelabeled: false,
isSeoDisabled: false,
isTocDefaultEnabled: false,
isSnippetTemplatesEnabled: false,
isHttpSnippetsEnabled: false,
isInlineFeedbackEnabled: false,
isDarkCodeEnabled: false,
proxyShouldUseAppBuildwithfernCom: false,
isImageZoomDisabled: false,
useJavaScriptAsTypeScript: false,
alwaysEnableJavaScriptFetch: false,
scrollInContainerEnabled: false,
useMdxBundler: false,
isBatchStreamToggleDisabled: false,
isAuthEnabledInDocs: false,
isAiChatbotEnabledInPreview: false,
isAudioFileDownloadSpanSummary: false,
isDocsLogoTextEnabled: false,
isAudioExampleInternal: false,
usesApplicationJsonInFormDataValue: false,
isBinaryOctetStreamAudioPlayer: false,
hasVoiceIdPlaygroundForm: false,
isCohereTheme: false,
isFileForgeHackEnabled: false,
is404PageHidden: false,
isNewSearchExperienceEnabled: false,
// TODO: remove this after pinecone demo, this is a temporary flag
grpcEndpoints: [],
};
import type { DocsProps } from "./types";

export const EMPTY_ANALYTICS_CONFIG: DocsV1Read.AnalyticsConfig = {
segment: undefined,
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/atoms/flags.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { FeatureFlags } from "@fern-ui/fern-docs-utils";
import { useAtomValue } from "jotai";
import { selectAtom } from "jotai/utils";
import { isEqual } from "lodash-es";
import { DOCS_ATOM } from "./docs";
import { FeatureFlags } from "./types";

export const FEATURE_FLAGS_ATOM = selectAtom(DOCS_ATOM, (docs) => docs.featureFlags, isEqual);
FEATURE_FLAGS_ATOM.debugLabel = "FEATURE_FLAGS_ATOM";
Expand Down
94 changes: 94 additions & 0 deletions packages/ui/app/src/atoms/launchdarkly.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { atom, useAtomValue, useSetAtom } from "jotai";
import * as LDClient from "launchdarkly-js-client-sdk";
import { useCallback, useEffect, useState } from "react";
import useSWR from "swr";
import { useApiRoute } from "../hooks/useApiRoute";

// NOTE do not export this file in any index.ts file so that it can be properly tree-shaken
// otherwise we risk importing launchdarkly-js-client-sdk in all of our bundles

// TODO: consolidate the types with the edge-config package
interface LaunchDarklyInfo {
clientSideId: string;
kind: "multi";
user:
| { anonymous: true }
| {
key: string;
email?: string;
name?: string;
};
device: {
key: string;
[key: string]: unknown;
};
}

// this is a singleton atom that initializes the LaunchDarkly client-side SDK
const LD_CLIENT_ATOM = atom<LDClient.LDClient | undefined>(undefined);

const SET_LAUNCH_DARKLY_INFO_ATOM = atom(undefined, async (get, set, info: LaunchDarklyInfo) => {
const client = get(LD_CLIENT_ATOM);
const { clientSideId, ...context } = info;

if (client) {
await client.identify(context);
return;
} else {
const client = LDClient.initialize(clientSideId, context);
await client.waitForInitialization();
set(LD_CLIENT_ATOM, client);
}
});

// TODO: support non-boolean flags
export const useLaunchDarklyFlag = (flag: string): boolean => {
useInitLaunchDarklyClient();

const client = useAtomValue(LD_CLIENT_ATOM);

// TODO: bootstrap the flag value from the server, and/or local storage
const getFlagEnabled = useCallback(() => {
if (!client) {
return false;
}
// force the flag to be a boolean:
return !!client.variation(flag, false);
}, [client, flag]);

const [enabled, setEnabled] = useState(getFlagEnabled);

// listen for changes to the flag
useEffect(() => {
setEnabled(getFlagEnabled());

if (!client) {
return;
}

const listener = () => {
setEnabled(getFlagEnabled());
};

client.on("ready", listener);
client.on("change", listener);

return () => {
client.off("ready", listener);
client.off("change", listener);
};
}, [client, flag, getFlagEnabled]);

return enabled;
};

// since useSWR is cached globally, we can use this hook in multiple components without worrying about multiple requests
function useInitLaunchDarklyClient() {
const route = useApiRoute("/api/fern-docs/integrations/launchdarkly");
const setInfo = useSetAtom(SET_LAUNCH_DARKLY_INFO_ATOM);
useSWR(route, (key): Promise<LaunchDarklyInfo> => fetch(key).then((res) => res.json()), {
onSuccess(data) {
void setInfo(data);
},
});
}
36 changes: 2 additions & 34 deletions packages/ui/app/src/atoms/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,13 @@ import type { DocsV1Read, DocsV2Read, FdrAPI } from "@fern-api/fdr-sdk/client/ty
import type * as FernDocs from "@fern-api/fdr-sdk/docs";
import type * as FernNavigation from "@fern-api/fdr-sdk/navigation";
import { ColorsConfig, SidebarTab, VersionSwitcherInfo } from "@fern-ui/fdr-utils";
import type { FernUser } from "@fern-ui/fern-docs-auth";
import type { FeatureFlags } from "@fern-ui/fern-docs-utils";
import { NextSeoProps } from "@fern-ui/next-seo";
import { CustomerAnalytics } from "../analytics/types";
import { FernUser } from "../auth";
import { DocsContent } from "../resolver/DocsContent";
import { FernTheme } from "../themes/ThemedDocs";

export interface FeatureFlags {
isApiPlaygroundEnabled: boolean;
isApiScrollingDisabled: boolean;
isWhitelabeled: boolean;
isSeoDisabled: boolean;
isTocDefaultEnabled: boolean;
isSnippetTemplatesEnabled: boolean;
isHttpSnippetsEnabled: boolean;
isInlineFeedbackEnabled: boolean;
isDarkCodeEnabled: boolean;
proxyShouldUseAppBuildwithfernCom: boolean;
isImageZoomDisabled: boolean;
useJavaScriptAsTypeScript: boolean;
alwaysEnableJavaScriptFetch: boolean;
scrollInContainerEnabled: boolean;
useMdxBundler: boolean;
isBatchStreamToggleDisabled: boolean;
isAuthEnabledInDocs: boolean;
isAiChatbotEnabledInPreview: boolean;
isAudioFileDownloadSpanSummary: boolean;
isDocsLogoTextEnabled: boolean;
isAudioExampleInternal: boolean;
usesApplicationJsonInFormDataValue: boolean;
isBinaryOctetStreamAudioPlayer: boolean;
hasVoiceIdPlaygroundForm: boolean;
isCohereTheme: boolean;
isFileForgeHackEnabled: boolean;
is404PageHidden: boolean;
isNewSearchExperienceEnabled: boolean;
// TODO: remove this after pinecone demo, this is a temporary flag
grpcEndpoints: readonly string[];
}

export interface NavigationProps {
currentTabIndex: number | undefined;
tabs: SidebarTab[];
Expand Down
22 changes: 22 additions & 0 deletions packages/ui/app/src/components/LinkPreload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Head from "next/head";
import { ReactElement } from "react";
import { useIsLocalPreview } from "../contexts/local-preview";
import { useApiRoute } from "../hooks/useApiRoute";

export function LinkPreload({ href }: { href: string }): ReactElement {
return (
<Head>
<link key={href} rel="preload" href={href} as="fetch" crossOrigin="anonymous" />
</Head>
);
}

type FernDocsApiRoute = `/api/fern-docs/${string}`;
export function LinkPreloadApiRoute({ href }: { href: FernDocsApiRoute }): ReactElement | null {
const isLocalPreview = useIsLocalPreview();
const key = useApiRoute(href);
if (isLocalPreview) {
return null;
}
return <LinkPreload href={key} />;
}
3 changes: 3 additions & 0 deletions packages/ui/app/src/docs/DocsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReactElement } from "react";
import { HydrateAtoms, useMessageHandler, useSetJustNavigated, type DocsProps } from "../atoms";
import { BgImageGradient } from "../components/BgImageGradient";
import { JavascriptProvider } from "../components/JavascriptProvider";
import { LinkPreloadApiRoute } from "../components/LinkPreload";
import { useBeforePopState } from "../hooks/useBeforePopState";
import { useConsoleMessage } from "../hooks/useConsoleMessage";
import { useRouteChangeComplete, useRouteChangeStart } from "../hooks/useRouteChanged";
Expand Down Expand Up @@ -45,6 +46,8 @@ export function DocsPage(pageProps: DocsProps): ReactElement | null {

return (
<>
<LinkPreloadApiRoute href="/api/fern-docs/search" />
<LinkPreloadApiRoute href="/api/fern-docs/auth/api-key-injection" />
<NextSeo />
<InitializeTheme />
<SearchDialog />
Expand Down
3 changes: 1 addition & 2 deletions packages/ui/app/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export { type CustomerAnalytics } from "./analytics/types";
export { DEFAULT_FEATURE_FLAGS } from "./atoms";
export type { DocsProps, FeatureFlags } from "./atoms";
export type { DocsProps } from "./atoms";
export * from "./docs/DocsPage";
export * from "./docs/NextApp";
export { getApiRouteSupplier } from "./hooks/useApiRoute";
Expand Down
Loading

0 comments on commit 365c1e6

Please sign in to comment.