posthog integration (cloud) (#1304)

This commit is contained in:
Philip Okugbe
2025-06-27 10:58:36 +01:00
committed by GitHub
parent e44c170873
commit 9f144d35fb
10 changed files with 134 additions and 4 deletions

View File

@ -41,6 +41,7 @@
"lowlight": "^3.3.0", "lowlight": "^3.3.0",
"mermaid": "^11.6.0", "mermaid": "^11.6.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"posthog-js": "^1.255.1",
"react": "^18.3.1", "react": "^18.3.1",
"react-arborist": "3.4.0", "react-arborist": "3.4.0",
"react-clear-modal": "^2.0.15", "react-clear-modal": "^2.0.15",

View File

@ -1,6 +1,8 @@
import { UserProvider } from "@/features/user/user-provider.tsx"; import { UserProvider } from "@/features/user/user-provider.tsx";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import GlobalAppShell from "@/components/layouts/global/global-app-shell.tsx"; import GlobalAppShell from "@/components/layouts/global/global-app-shell.tsx";
import { PosthogUser } from "@/ee/components/posthog-user.tsx";
import { isCloud } from "@/lib/config.ts";
export default function Layout() { export default function Layout() {
return ( return (
@ -8,6 +10,7 @@ export default function Layout() {
<GlobalAppShell> <GlobalAppShell>
<Outlet /> <Outlet />
</GlobalAppShell> </GlobalAppShell>
{isCloud() && <PosthogUser />}
</UserProvider> </UserProvider>
); );
} }

View File

@ -0,0 +1,41 @@
import { usePostHog } from "posthog-js/react";
import { useEffect } from "react";
import { useAtom } from "jotai";
import { currentUserAtom } from "@/features/user/atoms/current-user-atom.ts";
export function PosthogUser() {
const posthog = usePostHog();
const [currentUser] = useAtom(currentUserAtom);
useEffect(() => {
if (currentUser) {
const user = currentUser?.user;
const workspace = currentUser?.workspace;
if (!user || !workspace) return;
posthog?.identify(user.id, {
name: user.name,
email: user.email,
workspaceId: user.workspaceId,
workspaceHostname: workspace.hostname,
lastActiveAt: new Date().toISOString(),
createdAt: user.createdAt,
source: "docmost-app",
});
posthog?.group("workspace", workspace.id, {
name: workspace.name,
hostname: workspace.hostname,
plan: workspace?.plan,
status: workspace.status,
isOnTrial: !!workspace.trialEndAt,
hasStripeCustomerId: !!workspace.stripeCustomerId,
memberCount: workspace.memberCount,
lastActiveAt: new Date().toISOString(),
createdAt: workspace.createdAt,
source: "docmost-app",
});
}
}, [posthog, currentUser]);
return null;
}

View File

@ -12,6 +12,7 @@ export interface IWorkspace {
settings: any; settings: any;
status: string; status: string;
enforceSso: boolean; enforceSso: boolean;
stripeCustomerId: string;
billingEmail: string; billingEmail: string;
trialEndAt: Date; trialEndAt: Date;
createdAt: Date; createdAt: Date;

View File

@ -83,6 +83,18 @@ export function getBillingTrialDays() {
return getConfigValue("BILLING_TRIAL_DAYS"); return getConfigValue("BILLING_TRIAL_DAYS");
} }
export function getPostHogHost() {
return getConfigValue("POSTHOG_HOST");
}
export function isPostHogEnabled(): boolean {
return Boolean(getPostHogHost() && getPostHogKey());
}
export function getPostHogKey() {
return getConfigValue("POSTHOG_KEY");
}
function getConfigValue(key: string, defaultValue: string = undefined): string { function getConfigValue(key: string, defaultValue: string = undefined): string {
const rawValue = import.meta.env.DEV const rawValue = import.meta.env.DEV
? process?.env?.[key] ? process?.env?.[key]

View File

@ -3,7 +3,7 @@ import "@mantine/spotlight/styles.css";
import "@mantine/notifications/styles.css"; import "@mantine/notifications/styles.css";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import App from "./App.tsx"; import App from "./App.tsx";
import { mantineCssResolver, theme } from '@/theme'; import { mantineCssResolver, theme } from "@/theme";
import { MantineProvider } from "@mantine/core"; import { MantineProvider } from "@mantine/core";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { ModalsProvider } from "@mantine/modals"; import { ModalsProvider } from "@mantine/modals";
@ -11,6 +11,14 @@ import { Notifications } from "@mantine/notifications";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { HelmetProvider } from "react-helmet-async"; import { HelmetProvider } from "react-helmet-async";
import "./i18n"; import "./i18n";
import { PostHogProvider } from "posthog-js/react";
import {
getPostHogHost,
getPostHogKey,
isCloud,
isPostHogEnabled,
} from "@/lib/config.ts";
import posthog from "posthog-js";
export const queryClient = new QueryClient({ export const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@ -23,9 +31,16 @@ export const queryClient = new QueryClient({
}, },
}); });
if (isCloud() && isPostHogEnabled) {
posthog.init(getPostHogKey(), {
api_host: getPostHogHost(),
defaults: "2025-05-24",
disable_session_recording: true,
});
}
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement document.getElementById("root") as HTMLElement,
); );
root.render( root.render(
@ -35,10 +50,12 @@ root.render(
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Notifications position="bottom-center" limit={3} /> <Notifications position="bottom-center" limit={3} />
<HelmetProvider> <HelmetProvider>
<App /> <PostHogProvider client={posthog}>
<App />
</PostHogProvider>
</HelmetProvider> </HelmetProvider>
</QueryClientProvider> </QueryClientProvider>
</ModalsProvider> </ModalsProvider>
</MantineProvider> </MantineProvider>
</BrowserRouter> </BrowserRouter>,
); );

View File

@ -14,6 +14,8 @@ export default defineConfig(({ mode }) => {
SUBDOMAIN_HOST, SUBDOMAIN_HOST,
COLLAB_URL, COLLAB_URL,
BILLING_TRIAL_DAYS, BILLING_TRIAL_DAYS,
POSTHOG_HOST,
POSTHOG_KEY,
} = loadEnv(mode, envPath, ""); } = loadEnv(mode, envPath, "");
return { return {
@ -27,6 +29,8 @@ export default defineConfig(({ mode }) => {
SUBDOMAIN_HOST, SUBDOMAIN_HOST,
COLLAB_URL, COLLAB_URL,
BILLING_TRIAL_DAYS, BILLING_TRIAL_DAYS,
POSTHOG_HOST,
POSTHOG_KEY,
}, },
APP_VERSION: JSON.stringify(process.env.npm_package_version), APP_VERSION: JSON.stringify(process.env.npm_package_version),
}, },

View File

@ -205,4 +205,12 @@ export class EnvironmentService {
.toLowerCase(); .toLowerCase();
return disable === 'true'; return disable === 'true';
} }
getPostHogHost(): string {
return this.configService.get<string>('POSTHOG_HOST');
}
getPostHogKey(): string {
return this.configService.get<string>('POSTHOG_KEY');
}
} }

View File

@ -47,6 +47,8 @@ export class StaticModule implements OnModuleInit {
BILLING_TRIAL_DAYS: this.environmentService.isCloud() BILLING_TRIAL_DAYS: this.environmentService.isCloud()
? this.environmentService.getBillingTrialDays() ? this.environmentService.getBillingTrialDays()
: undefined, : undefined,
POSTHOG_HOST: this.environmentService.getPostHogHost(),
POSTHOG_KEY: this.environmentService.getPostHogKey(),
}; };
const windowScriptContent = `<script>window.CONFIG=${JSON.stringify(configString)};</script>`; const windowScriptContent = `<script>window.CONFIG=${JSON.stringify(configString)};</script>`;

41
pnpm-lock.yaml generated
View File

@ -296,6 +296,9 @@ importers:
mitt: mitt:
specifier: ^3.0.1 specifier: ^3.0.1
version: 3.0.1 version: 3.0.1
posthog-js:
specifier: ^1.255.1
version: 1.255.1
react: react:
specifier: ^18.3.1 specifier: ^18.3.1
version: 18.3.1 version: 18.3.1
@ -5213,6 +5216,9 @@ packages:
core-js-compat@3.35.0: core-js-compat@3.35.0:
resolution: {integrity: sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==} resolution: {integrity: sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==}
core-js@3.43.0:
resolution: {integrity: sha512-N6wEbTTZSYOY2rYAn85CuvWWkCK6QweMn7/4Nr3w+gDBeBhk/x4EJeY6FPo4QzDoJZxVTv8U7CMvgWk6pOHHqA==}
core-util-is@1.0.3: core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@ -5988,6 +5994,9 @@ packages:
picomatch: picomatch:
optional: true optional: true
fflate@0.4.8:
resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
fflate@0.8.2: fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
@ -7955,9 +7964,23 @@ packages:
postgres-range@1.1.4: postgres-range@1.1.4:
resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==} resolution: {integrity: sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==}
posthog-js@1.255.1:
resolution: {integrity: sha512-KMh0o9MhORhEZVjXpktXB5rJ8PfDk+poqBoTSoLzWgNjhJf6D8jcyB9jUMA6vVPfn4YeepVX5NuclDRqOwr5Mw==}
peerDependencies:
'@rrweb/types': 2.0.0-alpha.17
rrweb-snapshot: 2.0.0-alpha.17
peerDependenciesMeta:
'@rrweb/types':
optional: true
rrweb-snapshot:
optional: true
postmark@4.0.5: postmark@4.0.5:
resolution: {integrity: sha512-nerZdd3TwOH4CgGboZnlUM/q7oZk0EqpZgJL+Y3Nup8kHeaukxouQ6JcFF3EJEijc4QbuNv1TefGhboAKtf/SQ==} resolution: {integrity: sha512-nerZdd3TwOH4CgGboZnlUM/q7oZk0EqpZgJL+Y3Nup8kHeaukxouQ6JcFF3EJEijc4QbuNv1TefGhboAKtf/SQ==}
preact@10.26.9:
resolution: {integrity: sha512-SSjF9vcnF27mJK1XyFMNJzFd5u3pQiATFqoaDy03XuN00u4ziveVVEGt5RKJrDR8MHE/wJo9Nnad56RLzS2RMA==}
prelude-ls@1.2.1: prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -9297,6 +9320,9 @@ packages:
wcwidth@1.0.1: wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
web-vitals@4.2.4:
resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
web-worker@1.5.0: web-worker@1.5.0:
resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==}
@ -15194,6 +15220,8 @@ snapshots:
dependencies: dependencies:
browserslist: 4.24.2 browserslist: 4.24.2
core-js@3.43.0: {}
core-util-is@1.0.3: {} core-util-is@1.0.3: {}
cors@2.8.5: cors@2.8.5:
@ -16181,6 +16209,8 @@ snapshots:
optionalDependencies: optionalDependencies:
picomatch: 4.0.2 picomatch: 4.0.2
fflate@0.4.8: {}
fflate@0.8.2: {} fflate@0.8.2: {}
figures@3.2.0: figures@3.2.0:
@ -18482,12 +18512,21 @@ snapshots:
postgres-range@1.1.4: {} postgres-range@1.1.4: {}
posthog-js@1.255.1:
dependencies:
core-js: 3.43.0
fflate: 0.4.8
preact: 10.26.9
web-vitals: 4.2.4
postmark@4.0.5: postmark@4.0.5:
dependencies: dependencies:
axios: 1.9.0 axios: 1.9.0
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
preact@10.26.9: {}
prelude-ls@1.2.1: {} prelude-ls@1.2.1: {}
prettier@3.4.1: {} prettier@3.4.1: {}
@ -19911,6 +19950,8 @@ snapshots:
dependencies: dependencies:
defaults: 1.0.4 defaults: 1.0.4
web-vitals@4.2.4: {}
web-worker@1.5.0: {} web-worker@1.5.0: {}
webidl-conversions@3.0.1: {} webidl-conversions@3.0.1: {}