add posthog + layout rework (#2926)

* add posthog + layout rework

* remove posthog node

* nit
This commit is contained in:
pablodanswer 2024-10-26 11:15:01 -07:00 committed by GitHub
parent 5e01d6befb
commit 9def9f0dba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 218 additions and 122 deletions

View File

@ -61,6 +61,10 @@ ENV NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT}
ARG NEXT_PUBLIC_CUSTOM_REFRESH_URL ARG NEXT_PUBLIC_CUSTOM_REFRESH_URL
ENV NEXT_PUBLIC_CUSTOM_REFRESH_URL=${NEXT_PUBLIC_CUSTOM_REFRESH_URL} ENV NEXT_PUBLIC_CUSTOM_REFRESH_URL=${NEXT_PUBLIC_CUSTOM_REFRESH_URL}
ARG NEXT_PUBLIC_POSTHOG_KEY
ARG NEXT_PUBLIC_POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
ENV NEXT_PUBLIC_POSTHOG_HOST=${NEXT_PUBLIC_POSTHOG_HOST}
RUN npx next build RUN npx next build
@ -122,6 +126,13 @@ ENV NEXT_PUBLIC_DISABLE_LOGOUT=${NEXT_PUBLIC_DISABLE_LOGOUT}
ARG NEXT_PUBLIC_CUSTOM_REFRESH_URL ARG NEXT_PUBLIC_CUSTOM_REFRESH_URL
ENV NEXT_PUBLIC_CUSTOM_REFRESH_URL=${NEXT_PUBLIC_CUSTOM_REFRESH_URL} ENV NEXT_PUBLIC_CUSTOM_REFRESH_URL=${NEXT_PUBLIC_CUSTOM_REFRESH_URL}
ARG NEXT_PUBLIC_POSTHOG_KEY
ARG NEXT_PUBLIC_POSTHOG_HOST
ENV NEXT_PUBLIC_POSTHOG_KEY=${NEXT_PUBLIC_POSTHOG_KEY}
ENV NEXT_PUBLIC_POSTHOG_HOST=${NEXT_PUBLIC_POSTHOG_HOST}
# Note: Don't expose ports here, Compose will handle that for us if necessary. # Note: Don't expose ports here, Compose will handle that for us if necessary.
# If you want to run this without compose, specify the ports to # If you want to run this without compose, specify the ports to
# expose via cli # expose via cli

41
web/package-lock.json generated
View File

@ -33,6 +33,7 @@
"next": "^14.2.3", "next": "^14.2.3",
"npm": "^10.8.0", "npm": "^10.8.0",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"posthog-js": "^1.176.0",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@ -4493,6 +4494,16 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
}, },
"node_modules/core-js": {
"version": "3.38.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz",
"integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cosmiconfig": { "node_modules/cosmiconfig": {
"version": "7.1.0", "version": "7.1.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
@ -5639,6 +5650,11 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fflate": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@ -11136,6 +11152,26 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/posthog-js": {
"version": "1.176.0",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.176.0.tgz",
"integrity": "sha512-T5XKNtRzp7q6CGb7Vc7wAI76rWap9fiuDUPxPsyPBPDkreKya91x9RIsSapAVFafwD1AEin1QMczCmt9Le9BWw==",
"dependencies": {
"core-js": "^3.38.1",
"fflate": "^0.4.8",
"preact": "^10.19.3",
"web-vitals": "^4.2.0"
}
},
"node_modules/preact": {
"version": "10.24.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz",
"integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -13192,6 +13228,11 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/web-vitals": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View File

@ -34,6 +34,7 @@
"next": "^14.2.3", "next": "^14.2.3",
"npm": "^10.8.0", "npm": "^10.8.0",
"postcss": "^8.4.31", "postcss": "^8.4.31",
"posthog-js": "^1.176.0",
"prismjs": "^1.29.0", "prismjs": "^1.29.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",

View File

@ -0,0 +1,30 @@
"use client";
import { usePathname, useSearchParams } from "next/navigation";
import { useEffect } from "react";
import { usePostHog } from "posthog-js/react";
export default function PostHogPageView(): null {
const pathname = usePathname();
const searchParams = useSearchParams();
const posthog = usePostHog();
useEffect(() => {
if (!posthog) {
return;
}
// Track pageviews
if (pathname) {
let url = window.origin + pathname;
if (searchParams.toString()) {
url = url + `?${searchParams.toString()}`;
}
posthog.capture("$pageview", {
$current_url: url,
});
}
}, [pathname, searchParams, posthog]);
return null;
}

View File

@ -8,19 +8,21 @@ import {
CUSTOM_ANALYTICS_ENABLED, CUSTOM_ANALYTICS_ENABLED,
SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED, SERVER_SIDE_ONLY__PAID_ENTERPRISE_FEATURES_ENABLED,
} from "@/lib/constants"; } from "@/lib/constants";
import { SettingsProvider } from "@/components/settings/SettingsProvider";
import { Metadata } from "next"; import { Metadata } from "next";
import { buildClientUrl } from "@/lib/utilsSS"; import { buildClientUrl } from "@/lib/utilsSS";
import { Inter } from "next/font/google"; import { Inter } from "next/font/google";
import Head from "next/head";
import { EnterpriseSettings, GatingType } from "./admin/settings/interfaces"; import { EnterpriseSettings, GatingType } from "./admin/settings/interfaces";
import { Card } from "@tremor/react"; import { Card } from "@tremor/react";
import { HeaderTitle } from "@/components/header/HeaderTitle"; import { HeaderTitle } from "@/components/header/HeaderTitle";
import { Logo } from "@/components/Logo"; import { Logo } from "@/components/Logo";
import { UserProvider } from "@/components/user/UserProvider";
import { ProviderContextProvider } from "@/components/chat_search/ProviderContext";
import { fetchAssistantData } from "@/lib/chat/fetchAssistantdata"; import { fetchAssistantData } from "@/lib/chat/fetchAssistantdata";
import { AppProvider } from "@/components/context/AppProvider"; import { AppProvider } from "@/components/context/AppProvider";
import { PHProvider } from "./providers";
import { default as dynamicImport } from "next/dynamic";
const PostHogPageView = dynamicImport(() => import("./PostHogPageView"), {
ssr: false,
});
const inter = Inter({ const inter = Inter({
subsets: ["latin"], subsets: ["latin"],
@ -57,20 +59,40 @@ export default async function RootLayout({
}) { }) {
const combinedSettings = await fetchSettingsSS(); const combinedSettings = await fetchSettingsSS();
const data = await fetchAssistantData();
const { assistants, hasAnyConnectors, hasImageCompatibleModel } = data;
const productGating = const productGating =
combinedSettings?.settings.product_gating ?? GatingType.NONE; combinedSettings?.settings.product_gating ?? GatingType.NONE;
if (!combinedSettings) { const getPageContent = (content: React.ReactNode) => (
return (
<html lang="en" className={`${inter.variable} font-sans`}> <html lang="en" className={`${inter.variable} font-sans`}>
<Head> <head>
<title>Settings Unavailable | Danswer</title> <meta
</Head> name="viewport"
<body className="bg-background text-default"> content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, interactive-widget=resizes-content"
/>
{CUSTOM_ANALYTICS_ENABLED &&
combinedSettings?.customAnalyticsScript && (
<script
type="text/javascript"
dangerouslySetInnerHTML={{
__html: combinedSettings.customAnalyticsScript,
}}
/>
)}
</head>
<body className={`relative ${inter.variable} font-sans`}>
<div
className={`text-default min-h-screen bg-background ${
process.env.THEME_IS_DARK?.toLowerCase() === "true" ? "dark" : ""
}`}
>
<PHProvider>{content}</PHProvider>
</div>
</body>
</html>
);
if (!combinedSettings) {
return getPageContent(
<div className="flex flex-col items-center justify-center min-h-screen"> <div className="flex flex-col items-center justify-center min-h-screen">
<div className="mb-2 flex items-center max-w-[175px]"> <div className="mb-2 flex items-center max-w-[175px]">
<HeaderTitle>Danswer</HeaderTitle> <HeaderTitle>Danswer</HeaderTitle>
@ -80,9 +102,9 @@ export default async function RootLayout({
<Card className="p-8 max-w-md"> <Card className="p-8 max-w-md">
<h1 className="text-2xl font-bold mb-4 text-error">Error</h1> <h1 className="text-2xl font-bold mb-4 text-error">Error</h1>
<p className="text-text-500"> <p className="text-text-500">
Your Danswer instance was not configured properly and your Your Danswer instance was not configured properly and your settings
settings could not be loaded. This could be due to an admin could not be loaded. This could be due to an admin configuration
configuration issue or an incomplete setup. issue or an incomplete setup.
</p> </p>
<p className="mt-4"> <p className="mt-4">
If you&apos;re an admin, please check{" "} If you&apos;re an admin, please check{" "}
@ -112,17 +134,10 @@ export default async function RootLayout({
</p> </p>
</Card> </Card>
</div> </div>
</body>
</html>
); );
} }
if (productGating === GatingType.FULL) { if (productGating === GatingType.FULL) {
return ( return getPageContent(
<html lang="en" className={`${inter.variable} font-sans`}>
<Head>
<title>Access Restricted | Danswer</title>
</Head>
<body className="bg-background text-default">
<div className="flex flex-col items-center justify-center min-h-screen"> <div className="flex flex-col items-center justify-center min-h-screen">
<div className="mb-2 flex items-center max-w-[175px]"> <div className="mb-2 flex items-center max-w-[175px]">
<HeaderTitle>Danswer</HeaderTitle> <HeaderTitle>Danswer</HeaderTitle>
@ -137,9 +152,8 @@ export default async function RootLayout({
temporarily suspended due to a lapse in your subscription. temporarily suspended due to a lapse in your subscription.
</p> </p>
<p className="text-text-500 mb-4"> <p className="text-text-500 mb-4">
To reinstate your access and continue benefiting from To reinstate your access and continue benefiting from Danswer&apos;s
Danswer&apos;s powerful features, please update your payment powerful features, please update your payment information.
information.
</p> </p>
<p className="text-text-500"> <p className="text-text-500">
If you&apos;re an admin, you can resolve this by visiting the If you&apos;re an admin, you can resolve this by visiting the
@ -148,48 +162,21 @@ export default async function RootLayout({
</p> </p>
</Card> </Card>
</div> </div>
</body>
</html>
); );
} }
return ( const data = await fetchAssistantData();
<html lang="en"> const { assistants, hasAnyConnectors, hasImageCompatibleModel } = data;
<Head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, interactive-widget=resizes-content"
/>
</Head>
{CUSTOM_ANALYTICS_ENABLED && combinedSettings.customAnalyticsScript && ( return getPageContent(
<head>
<script
type="text/javascript"
dangerouslySetInnerHTML={{
__html: combinedSettings.customAnalyticsScript,
}}
/>
</head>
)}
<body className={`relative ${inter.variable} font-sans`}>
<div
className={`text-default min-h-screen bg-background ${
// TODO: remove this once proper dark mode exists
process.env.THEME_IS_DARK?.toLowerCase() === "true" ? "dark" : ""
}`}
>
<AppProvider <AppProvider
settings={combinedSettings} settings={combinedSettings}
assistants={assistants} assistants={assistants}
hasAnyConnectors={hasAnyConnectors} hasAnyConnectors={hasAnyConnectors}
hasImageCompatibleModel={hasImageCompatibleModel} hasImageCompatibleModel={hasImageCompatibleModel}
> >
<PostHogPageView />
{children} {children}
</AppProvider> </AppProvider>
</div>
</body>
</html>
); );
} }

26
web/src/app/providers.tsx Normal file
View File

@ -0,0 +1,26 @@
"use client";
import posthog from "posthog-js";
import { PostHogProvider } from "posthog-js/react";
import { useEffect } from "react";
const isPostHogEnabled = !!(
process.env.NEXT_PUBLIC_POSTHOG_KEY && process.env.NEXT_PUBLIC_POSTHOG_HOST
);
export function PHProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
if (isPostHogEnabled) {
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST!,
person_profiles: "identified_only",
capture_pageview: false,
});
}
}, []);
if (!isPostHogEnabled) {
return <>{children}</>;
}
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}