From 26262d34b4aef4db94f15e536052d9fb761b7e97 Mon Sep 17 00:00:00 2001 From: Kirill Chernakov Date: Wed, 13 Nov 2024 16:25:42 +0400 Subject: [PATCH] fix: do not fetch data twice due to wait for config (#2463) --- keep-ui/app/config-provider.tsx | 20 +++++++ keep-ui/app/layout.tsx | 50 ++++++++++------ keep-ui/app/posthog-provider.tsx | 22 +++++++ keep-ui/components/navbar/InitPostHog.tsx | 58 ------------------- keep-ui/components/navbar/Navbar.tsx | 2 - .../lib/server/getConfig.ts} | 17 ++---- keep-ui/shared/ui/PostHogPageView.tsx | 53 +++++++++++++++++ keep-ui/utils/hooks/useConfig.ts | 18 +++--- keep-ui/utils/hooks/usePusher.ts | 3 +- 9 files changed, 144 insertions(+), 99 deletions(-) create mode 100644 keep-ui/app/config-provider.tsx create mode 100644 keep-ui/app/posthog-provider.tsx delete mode 100644 keep-ui/components/navbar/InitPostHog.tsx rename keep-ui/{pages/api/config.tsx => shared/lib/server/getConfig.ts} (85%) create mode 100644 keep-ui/shared/ui/PostHogPageView.tsx diff --git a/keep-ui/app/config-provider.tsx b/keep-ui/app/config-provider.tsx new file mode 100644 index 000000000..cebf488f2 --- /dev/null +++ b/keep-ui/app/config-provider.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { createContext } from "react"; +import { InternalConfig } from "types/internal-config"; + +// Create the context with undefined as initial value +export const ConfigContext = createContext(null); + +// Create a provider component +export function ConfigProvider({ + children, + config, +}: { + children: React.ReactNode; + config: any; +}) { + return ( + {children} + ); +} diff --git a/keep-ui/app/layout.tsx b/keep-ui/app/layout.tsx index a8a921d2f..632a064ed 100644 --- a/keep-ui/app/layout.tsx +++ b/keep-ui/app/layout.tsx @@ -1,9 +1,20 @@ import { ReactNode } from "react"; import { NextAuthProvider } from "./auth-provider"; import { Mulish } from "next/font/google"; - +import { ToastContainer } from "react-toastify"; +import Navbar from "components/navbar/Navbar"; +import { TopologyPollingContextProvider } from "@/app/topology/model/TopologyPollingContext"; +import { FrigadeProvider } from "./frigade-provider"; +import { getConfig } from "@/shared/lib/server/getConfig"; +import { ConfigProvider } from "./config-provider"; import "./globals.css"; import "react-toastify/dist/ReactToastify.css"; +import { PHProvider } from "./posthog-provider"; +import dynamic from "next/dynamic"; + +const PostHogPageView = dynamic(() => import("@/shared/ui/PostHogPageView"), { + ssr: false, +}); // If loading a variable font, you don't need to specify the font weight const mulish = Mulish({ @@ -11,32 +22,33 @@ const mulish = Mulish({ display: "swap", }); -import { ToastContainer } from "react-toastify"; -import Navbar from "components/navbar/Navbar"; -import { TopologyPollingContextProvider } from "@/app/topology/model/TopologyPollingContext"; -import { FrigadeProvider } from "./frigade-provider"; - type RootLayoutProps = { children: ReactNode; }; export default async function RootLayout({ children }: RootLayoutProps) { + const config = getConfig(); return ( - - - - {/* @ts-ignore-error Server Component */} - - {/* https://discord.com/channels/752553802359505017/1068089513253019688/1117731746922893333 */} -
-
{children}
- -
-
-
-
+ + + + + + {/* @ts-ignore-error Server Component */} + + + {/* https://discord.com/channels/752553802359505017/1068089513253019688/1117731746922893333 */} +
+
{children}
+ +
+
+
+
+
+
{/** footer */} {process.env.GIT_COMMIT_HASH && ( diff --git a/keep-ui/app/posthog-provider.tsx b/keep-ui/app/posthog-provider.tsx new file mode 100644 index 000000000..28c0fabff --- /dev/null +++ b/keep-ui/app/posthog-provider.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { useConfig } from "@/utils/hooks/useConfig"; +import posthog from "posthog-js"; +import { PostHogProvider } from "posthog-js/react"; +import { useEffect } from "react"; + +export function PHProvider({ children }: { children: React.ReactNode }) { + const { data: config } = useConfig(); + + useEffect(() => { + if (!config || config.POSTHOG_DISABLED === "true" || !config.POSTHOG_KEY) { + return; + } + posthog.init(config.POSTHOG_KEY!, { + api_host: config.POSTHOG_HOST, + ui_host: config.POSTHOG_HOST, + }); + }, [config]); + + return {children}; +} diff --git a/keep-ui/components/navbar/InitPostHog.tsx b/keep-ui/components/navbar/InitPostHog.tsx deleted file mode 100644 index 2257479d8..000000000 --- a/keep-ui/components/navbar/InitPostHog.tsx +++ /dev/null @@ -1,58 +0,0 @@ -// app/posthog.tsx -// took this from https://posthog.com/tutorials/nextjs-app-directory-analytics -"use client"; -import posthog from "posthog-js"; -import { usePathname, useSearchParams } from "next/navigation"; -import { useSession } from "next-auth/react"; -import { NoAuthUserEmail } from "utils/authenticationType"; -import { useConfig } from "utils/hooks/useConfig"; - -export const InitPostHog = () => { - const pathname = usePathname(); - const searchParams = useSearchParams(); - const { data: session } = useSession(); - const { data: configData } = useConfig(); - - if ( - typeof window !== "undefined" && - configData && - configData.POSTHOG_KEY && - configData.POSTHOG_DISABLED !== "true" - ) { - posthog.init(configData.POSTHOG_KEY!, { - api_host: configData.POSTHOG_HOST, - ui_host: configData.POSTHOG_HOST, - }); - } - - if ( - pathname && - configData && - configData.POSTHOG_KEY && - configData.POSTHOG_DISABLED !== "true" - ) { - let url = window.origin + pathname; - - if (searchParams) { - url = url + `?${searchParams.toString()}`; - } - - if (session) { - const { user } = session; - - const posthog_id = user.email; - - if (posthog_id && posthog_id !== NoAuthUserEmail) { - console.log("Identifying user in PostHog"); - posthog.identify(posthog_id); - } - } - - posthog.capture("$pageview", { - $current_url: url, - keep_version: process.env.NEXT_PUBLIC_KEEP_VERSION ?? "unknown", - }); - } - - return null; -}; diff --git a/keep-ui/components/navbar/Navbar.tsx b/keep-ui/components/navbar/Navbar.tsx index b8f5c9e82..118594557 100644 --- a/keep-ui/components/navbar/Navbar.tsx +++ b/keep-ui/components/navbar/Navbar.tsx @@ -3,7 +3,6 @@ import { Search } from "components/navbar/Search"; import { NoiseReductionLinks } from "components/navbar/NoiseReductionLinks"; import { AlertsLinks } from "components/navbar/AlertsLinks"; import { UserInfo } from "components/navbar/UserInfo"; -import { InitPostHog } from "components/navbar/InitPostHog"; import { Menu } from "components/navbar/Menu"; import { MinimizeMenuButton } from "components/navbar/MinimizeMenuButton"; import { authOptions } from "pages/api/auth/[...nextauth]"; @@ -16,7 +15,6 @@ export default async function NavbarInner() { return ( <> -
diff --git a/keep-ui/pages/api/config.tsx b/keep-ui/shared/lib/server/getConfig.ts similarity index 85% rename from keep-ui/pages/api/config.tsx rename to keep-ui/shared/lib/server/getConfig.ts index 9e317e5d3..bcb6a148a 100644 --- a/keep-ui/pages/api/config.tsx +++ b/keep-ui/shared/lib/server/getConfig.ts @@ -1,17 +1,12 @@ -import type { NextApiRequest, NextApiResponse } from "next"; +import { getApiURL } from "@/utils/apiUrl"; import { AuthenticationType, MULTI_TENANT, - SINGLE_TENANT, NO_AUTH, -} from "utils/authenticationType"; -import { getApiURL } from "utils/apiUrl"; -import { get } from "http"; + SINGLE_TENANT, +} from "@/utils/authenticationType"; -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { +export function getConfig() { let authType = process.env.AUTH_TYPE; // Backward compatibility @@ -32,7 +27,7 @@ export default async function handler( } else { API_URL_CLIENT = process.env.API_URL_CLIENT; } - res.status(200).json({ + return { AUTH_TYPE: authType, PUSHER_DISABLED: process.env.PUSHER_DISABLED === "true", // could be relative (for ingress) or absolute (e.g. Pusher) @@ -52,5 +47,5 @@ export default async function handler( POSTHOG_KEY: process.env.POSTHOG_KEY, POSTHOG_DISABLED: process.env.POSTHOG_DISABLED, POSTHOG_HOST: process.env.POSTHOG_HOST, - }); + }; } diff --git a/keep-ui/shared/ui/PostHogPageView.tsx b/keep-ui/shared/ui/PostHogPageView.tsx new file mode 100644 index 000000000..d267f5968 --- /dev/null +++ b/keep-ui/shared/ui/PostHogPageView.tsx @@ -0,0 +1,53 @@ +// app/PostHogPageView.tsx +"use client"; + +import { usePathname, useSearchParams } from "next/navigation"; +import { useEffect } from "react"; +import { usePostHog } from "posthog-js/react"; +import { useConfig } from "@/utils/hooks/useConfig"; +import { useSession } from "next-auth/react"; +import { NoAuthUserEmail } from "@/utils/authenticationType"; + +export default function PostHogPageView(): null { + const pathname = usePathname(); + const searchParams = useSearchParams(); + const posthog = usePostHog(); + const { data: config } = useConfig(); + const { data: session } = useSession(); + + const isPosthogDisabled = + config?.POSTHOG_DISABLED === "true" || !config?.POSTHOG_KEY; + + useEffect(() => { + // Track pageviews + if (!pathname || !posthog || isPosthogDisabled) { + return; + } + let url = window.origin + pathname; + if (searchParams && searchParams.toString()) { + url = url + `?${searchParams.toString()}`; + } + posthog.capture("$pageview", { + $current_url: url, + keep_version: process.env.NEXT_PUBLIC_KEEP_VERSION ?? "unknown", + }); + }, [pathname, searchParams, posthog, isPosthogDisabled]); + + useEffect(() => { + // Identify user in PostHog + if (isPosthogDisabled || !session) { + return; + } + + const { user } = session; + + const posthog_id = user.email; + + if (posthog_id && posthog_id !== NoAuthUserEmail) { + console.log("Identifying user in PostHog"); + posthog.identify(posthog_id); + } + }, [session, posthog, isPosthogDisabled]); + + return null; +} diff --git a/keep-ui/utils/hooks/useConfig.ts b/keep-ui/utils/hooks/useConfig.ts index c92b71750..648ca0a05 100644 --- a/keep-ui/utils/hooks/useConfig.ts +++ b/keep-ui/utils/hooks/useConfig.ts @@ -1,14 +1,16 @@ -import { useSession } from "next-auth/react"; -import useSWRImmutable from "swr/immutable"; -import { InternalConfig } from "types/internal-config"; -import { fetcher } from "utils/fetcher"; +import { ConfigContext } from "@/app/config-provider"; +import { useContext } from "react"; export const useConfig = () => { - const { data: session } = useSession(); + const context = useContext(ConfigContext); - return useSWRImmutable("/api/config", () => - fetcher("/api/config", session?.accessToken) - ); + if (context === undefined) { + throw new Error("useConfig must be used within a ConfigProvider"); + } + + return { + data: context, + }; }; export const useApiUrl = () => { diff --git a/keep-ui/utils/hooks/usePusher.ts b/keep-ui/utils/hooks/usePusher.ts index 57828a2a3..20be77a03 100644 --- a/keep-ui/utils/hooks/usePusher.ts +++ b/keep-ui/utils/hooks/usePusher.ts @@ -16,9 +16,10 @@ export const useWebsocket = () => { console.log("useWebsocket: Initializing with config:", configData); console.log("useWebsocket: Session:", session); + // TODO: should be in useMemo? if ( PUSHER === null && - configData !== undefined && + configData !== null && session !== undefined && configData.PUSHER_DISABLED === false ) {