Skip to content

Commit

Permalink
Nick/neos 435 implement posthog for analytics (#952)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickzelei committed Dec 22, 2023
1 parent 00ad52b commit 3c868bf
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 6 deletions.
2 changes: 2 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ services:
- NEOSYNC_API_BASE_URL=http://api:8080
- NEXT_PUBLIC_APP_BASE_URL=http://localhost:3000

- POSTHOG_KEY=phc_qju45RhNvCDwYVdRyUjtWuWsOmLFaQZi3fmztMBaJip

- AUTH_ENABLED=false

networks:
Expand Down
6 changes: 5 additions & 1 deletion frontend/apps/web/app/BaseLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import SiteFooter from '@/components/SiteFooter';
import SiteHeader from '@/components/SiteHeader';
import AccountProvider from '@/components/providers/account-provider';
import { PostHogIdentifier } from '@/components/providers/posthog-provider';
import { SessionProvider } from '@/components/providers/session-provider';
import { Toaster } from '@/components/ui/toaster';
import { ReactElement, ReactNode } from 'react';
import { ReactElement, ReactNode, Suspense } from 'react';
import { auth, signIn } from './api/auth/[...nextauth]/auth';
import { getSystemAppConfig } from './api/config/config';

Expand All @@ -23,6 +24,9 @@ export default async function BaseLayout(props: Props): Promise<ReactElement> {
return (
<SessionProvider session={session}>
<AccountProvider>
<Suspense>
<PostHogIdentifier systemConfig={systemAppConfig} />
</Suspense>
<div className="relative flex min-h-screen flex-col">
<SiteHeader />
<div className="flex-1 container" id="top-level-layout">
Expand Down
11 changes: 9 additions & 2 deletions frontend/apps/web/app/[account]/jobs/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useGetJobs } from '@/libs/hooks/useGetJobs';
import { JobStatus } from '@neosync/sdk';
import { PlusIcon } from '@radix-ui/react-icons';
import NextLink from 'next/link';
import { usePostHog } from 'posthog-js/react';
import { ReactElement, useMemo } from 'react';
import { getColumns } from './components/DataTable/columns';
import { DataTable } from './components/DataTable/data-table';
Expand Down Expand Up @@ -75,9 +76,15 @@ function JobTable(props: JobTableProps): ReactElement {

function NewJobButton(): ReactElement {
const { account } = useAccount();
const posthog = usePostHog();
return (
<NextLink href={`/${account?.name}/new/job`}>
<Button>
<NextLink
href={`/${account?.name}/new/job`}
onClick={() => {
posthog.capture('clicked_new_job_button');
}}
>
<Button onClick={() => {}}>
<ButtonText leftIcon={<PlusIcon />} text="New Job" />
</Button>
</NextLink>
Expand Down
7 changes: 7 additions & 0 deletions frontend/apps/web/app/api/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,12 @@ export function getSystemAppConfig(): SystemAppConfig {
isAuthEnabled: process.env.AUTH_ENABLED == 'true',
publicAppBaseUrl:
process.env.NEXT_PUBLIC_APP_BASE_URL ?? 'http://localhost:3000',
posthog: {
enabled: process.env.NEOSYNC_ANALYTICS_ENABLED
? process.env.NEOSYNC_ANALYTICS_ENABLED == 'true'
: true,
host: process.env.POSTHOG_HOST ?? 'https://app.posthog.com',
key: process.env.POSTHOG_KEY,
},
};
}
7 changes: 7 additions & 0 deletions frontend/apps/web/app/config/app-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
export interface SystemAppConfig {
isAuthEnabled: boolean;
publicAppBaseUrl: string;
posthog: PosthogConfig;
}

export interface PosthogConfig {
enabled: boolean;
key?: string;
host: string;
}
15 changes: 13 additions & 2 deletions frontend/apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import '@/app/globals.css';
import {
PHProvider,
PostHogPageview,
} from '@/components/providers/posthog-provider';
import { ThemeProvider } from '@/components/providers/theme-provider';
import { fontSans } from '@/libs/fonts';
import { cn } from '@/libs/utils';
import { Metadata } from 'next';
import { ReactElement } from 'react';
import { ReactElement, Suspense } from 'react';
import { getSystemAppConfig } from './api/config/config';

export const metadata: Metadata = {
title: 'Neosync',
Expand All @@ -16,6 +21,7 @@ export default async function RootLayout({
}: {
children: React.ReactNode;
}): Promise<ReactElement> {
const appConfig = getSystemAppConfig();
return (
<html lang="en" suppressHydrationWarning>
<head />
Expand All @@ -31,7 +37,12 @@ export default async function RootLayout({
enableSystem
disableTransitionOnChange
>
{children}
<>
<Suspense>
<PostHogPageview config={appConfig.posthog} />
</Suspense>
<PHProvider>{children}</PHProvider>
</>
</ThemeProvider>
</body>
</html>
Expand Down
10 changes: 10 additions & 0 deletions frontend/apps/web/charts/app/templates/app-env-vars.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,13 @@ stringData:
{{- end }}

AUTH_ENABLED: {{ .Values.auth.enabled | default "false" | quote }}

NEOSYNC_ANALYTICS_ENABLED: {{ .Values.analytics.enabled | default "true" | quote }}

{{ if and .Values.posthog .Values.posthog.key }}
POSTHOG_KEY: {{ .Values.posthog.key }}
{{- end }}

{{ if and .Values.posthog .Values.posthog.host }}
POSTHOG_HOST: {{ .Values.posthog.host }}
{{- end }}
7 changes: 7 additions & 0 deletions frontend/apps/web/charts/app/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,10 @@ auth0:

ingress:
enabled: false

analytics:
enabled: true

posthog:
key: phc_qju45RhNvCDwYVdRyUjtWuWsOmLFaQZi3fmztMBaJip
# host:
7 changes: 6 additions & 1 deletion frontend/apps/web/components/UserNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import {
} from '@/components/ui/dropdown-menu';
import { GearIcon } from '@radix-ui/react-icons';
import { signOut, useSession } from 'next-auth/react';
import { usePostHog } from 'posthog-js/react';
import { ReactElement } from 'react';

export function UserNav(): ReactElement | null {
const session = useSession();
const posthog = usePostHog();

const avatarImageSrc = session.data?.user?.image ?? '';
const avatarImageAlt = session.data?.user?.name ?? 'unknown';
Expand Down Expand Up @@ -63,7 +65,10 @@ export function UserNav(): ReactElement | null {
{session.status === 'authenticated' && (
<DropdownMenuItem
className="cursor-pointer"
onClick={() => signOut()}
onClick={() => {
posthog.reset();
signOut();
}}
>
Log out
</DropdownMenuItem>
Expand Down
90 changes: 90 additions & 0 deletions frontend/apps/web/components/providers/posthog-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use client';
import { PosthogConfig, SystemAppConfig } from '@/app/config/app-config';
import { useNeosyncUser } from '@/libs/hooks/useNeosyncUser';
import { usePathname, useSearchParams } from 'next/navigation';
import posthog from 'posthog-js';
import { PostHogProvider, usePostHog } from 'posthog-js/react';
import { ReactElement, ReactNode, useEffect } from 'react';
import { useAccount } from './account-provider';

interface PosthogPageviewProps {
config: PosthogConfig;
}

// Enables posthog, as well as turns on pageview tracking.
export function PostHogPageview(props: PosthogPageviewProps): JSX.Element {
const { config } = props;
const pathname = usePathname();
const searchParams = useSearchParams();

useEffect(() => {
if (typeof window !== 'undefined' && config.enabled && config.key) {
posthog.init(config.key, {
api_host: config.host,
capture_pageview: false, // Disable automatic pageview capture, as we capture manually
});
}
}, []);

useEffect(() => {
if (pathname) {
let url = window.origin + pathname;
if (searchParams && searchParams.toString()) {
url = url + `?${searchParams.toString()}`;
}
posthog.capture('$pageview', {
$current_url: url,
});
}
}, [pathname, searchParams]);

return <></>;
}

interface PHProps {
children: ReactNode;
}

// Enables Posthog to be used througout the app via the hook.
export function PHProvider({ children }: PHProps) {
return <PostHogProvider client={posthog}>{children}</PostHogProvider>;
}

interface Props {
systemConfig: SystemAppConfig;
}

// Handles setting global user data for the user so that it doesn't have to be set on every capture call.
export function PostHogIdentifier(props: Props): ReactElement {
const { systemConfig } = props;
const { data: userData, isLoading: isUserDataLoading } = useNeosyncUser();
const { account, isLoading: isAccountLoading } = useAccount();
const posthog = usePostHog();

useEffect(() => {
if (
isUserDataLoading ||
isAccountLoading ||
!account?.name ||
!account?.id ||
!userData?.userId
) {
return;
}
// we only want to set the user id if auth is enabled, otherwise it is always the same
// so it makes it harder to identify unique posthog sessions when running in un-auth mode.
const userId = systemConfig.isAuthEnabled ? userData.userId : undefined;
posthog.identify(userId, {
accountName: account.name,
accountId: account.id,
});
}, [
isUserDataLoading,
isAccountLoading,
account?.id,
account?.name,
userData?.userId,
systemConfig?.isAuthEnabled,
]);
return <></>;
}
1 change: 1 addition & 0 deletions frontend/apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"next": "^14.0.3",
"next-auth": "^5.0.0-beta.4",
"next-themes": "^0.2.1",
"posthog-js": "^1.96.1",
"react": "18.2.0",
"react-day-picker": "^8.9.1",
"react-dom": "18.2.0",
Expand Down
14 changes: 14 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 comment on commit 3c868bf

@vercel
Copy link

@vercel vercel bot commented on 3c868bf Dec 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.