From 7b66c1b1f1e796d56114a9d85afbd899ddbcd7b6 Mon Sep 17 00:00:00 2001 From: bkellam Date: Tue, 25 Mar 2025 23:01:56 -0700 Subject: [PATCH 1/3] PostHog event pass --- .env.development | 6 ++- Dockerfile | 9 +++- .../[domain]/components/settingsDropdown.tsx | 5 ++- .../onboard/components/connectCodeHost.tsx | 16 +------ .../src/app/components/logoutEscapeHatch.tsx | 4 ++ .../src/app/login/components/loginForm.tsx | 9 ++++ packages/web/src/app/posthogProvider.tsx | 45 +++++++++++++------ packages/web/src/env.mjs | 9 +++- 8 files changed, 67 insertions(+), 36 deletions(-) diff --git a/.env.development b/.env.development index 9ba1073aa..e49cbea68 100644 --- a/.env.development +++ b/.env.development @@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000" # Sentry # SENTRY_BACKEND_DSN="" # NEXT_PUBLIC_SENTRY_WEBAPP_DSN="" -SENTRY_ENVIRONMENT="dev" +# SENTRY_ENVIRONMENT="dev" # NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev" # SENTRY_AUTH_TOKEN= @@ -76,4 +76,6 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection # CONFIG_MAX_REPOS_NO_TOKEN= # NODE_ENV= -# SOURCEBOT_TENANCY_MODE=single \ No newline at end of file +# SOURCEBOT_TENANCY_MODE=single + +# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT= \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 67f27bb9f..4dfa1f64e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION # @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key ARG POSTHOG_PAPIK ARG SENTRY_ENVIRONMENT +ARG SOURCEBOT_CLOUD_ENVIRONMENT FROM node:20-alpine3.19 AS node-alpine FROM golang:1.23.4-alpine3.19 AS go-alpine @@ -52,9 +53,11 @@ ARG POSTHOG_PAPIK ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK ARG SENTRY_ENVIRONMENT ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT +ARG SOURCEBOT_CLOUD_ENVIRONMENT +ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT # Local args -ARG SENTRY_WEBAPP_DSN -ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN +ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN +ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN # ----------- RUN apk add --no-cache libc6-compat @@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION ARG POSTHOG_PAPIK ENV POSTHOG_PAPIK=$POSTHOG_PAPIK +ARG SENTRY_ENVIRONMENT +ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT # Local args # ----------- diff --git a/packages/web/src/app/[domain]/components/settingsDropdown.tsx b/packages/web/src/app/[domain]/components/settingsDropdown.tsx index e6b84ead8..771440359 100644 --- a/packages/web/src/app/[domain]/components/settingsDropdown.tsx +++ b/packages/web/src/app/[domain]/components/settingsDropdown.tsx @@ -32,6 +32,7 @@ import { useSession } from "next-auth/react"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { signOut } from "next-auth/react" import { env } from "@/env.mjs"; +import posthog from "posthog-js"; interface SettingsDropdownProps { menuButtonClassName?: string; @@ -94,7 +95,9 @@ export const SettingsDropdown = ({ onClick={() => { signOut({ redirectTo: "/login", - }); + }).then(() => { + posthog.reset(); + }) }} > diff --git a/packages/web/src/app/[domain]/onboard/components/connectCodeHost.tsx b/packages/web/src/app/[domain]/onboard/components/connectCodeHost.tsx index 7a665ca44..dc0f4589b 100644 --- a/packages/web/src/app/[domain]/onboard/components/connectCodeHost.tsx +++ b/packages/web/src/app/[domain]/onboard/components/connectCodeHost.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { CodeHostType } from "@/lib/utils"; import { getCodeHostIcon } from "@/lib/utils"; import { @@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants"; import { BackButton } from "./onboardBackButton"; import { CodeHostIconButton } from "../../components/codeHostIconButton"; import useCaptureEvent from "@/hooks/useCaptureEvent"; -import { useSession } from "next-auth/react"; -import posthog from "posthog-js"; import SecurityCard from "@/app/components/securityCard"; interface ConnectCodeHostProps { @@ -27,18 +25,6 @@ interface ConnectCodeHostProps { export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => { const [selectedCodeHost, setSelectedCodeHost] = useState(null); const router = useRouter(); - const { data: session } = useSession(); - - // Note: this is currently the first client side page that gets loaded after a user registers. If this changes, we need to update this. - // @nocheckin - useEffect(() => { - if (session?.user) { - posthog.identify(session.user.id, { - email: session.user.email, - name: session.user.name, - }); - } - }, [session?.user]); const onCreated = useCallback(() => { router.push(`?step=${nextStep}`); diff --git a/packages/web/src/app/components/logoutEscapeHatch.tsx b/packages/web/src/app/components/logoutEscapeHatch.tsx index 8311afe00..ce7754362 100644 --- a/packages/web/src/app/components/logoutEscapeHatch.tsx +++ b/packages/web/src/app/components/logoutEscapeHatch.tsx @@ -1,5 +1,7 @@ import { LogOutIcon } from "lucide-react"; import { signOut } from "@/auth"; +import posthog from "posthog-js"; + interface LogoutEscapeHatchProps { className?: string; } @@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({ "use server"; await signOut({ redirectTo: "/login", + }).then(() => { + posthog.reset(); }); }} > diff --git a/packages/web/src/app/login/components/loginForm.tsx b/packages/web/src/app/login/components/loginForm.tsx index 8f479d7a3..3d5d953c7 100644 --- a/packages/web/src/app/login/components/loginForm.tsx +++ b/packages/web/src/app/login/components/loginForm.tsx @@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo"; import { TextSeparator } from "@/app/components/textSeparator"; import useCaptureEvent from "@/hooks/useCaptureEvent"; import DemoCard from "@/app/[domain]/onboard/components/demoCard"; +import Link from "next/link"; +import { env } from "@/env.mjs"; + +const TERMS_OF_SERVICE_URL = "https://sourcebot.dev/terms"; +const PRIVACY_POLICY_URL = "https://sourcebot.dev/privacy"; + interface LoginFormProps { callbackUrl?: string; error?: string; @@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps ]} /> + {env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && ( +

By signing in, you agree to the Terms of Service and Privacy Policy.

+ )} ) } diff --git a/packages/web/src/app/posthogProvider.tsx b/packages/web/src/app/posthogProvider.tsx index c8198b7ba..4060292b4 100644 --- a/packages/web/src/app/posthogProvider.tsx +++ b/packages/web/src/app/posthogProvider.tsx @@ -5,13 +5,14 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react' import { usePathname, useSearchParams } from "next/navigation" import { Suspense, useEffect } from "react" import { env } from '@/env.mjs' +import { useSession } from 'next-auth/react' +// @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews function PostHogPageView() { const pathname = usePathname() const searchParams = useSearchParams() const posthog = usePostHog() - // Track pageviews useEffect(() => { if (pathname && posthog) { let url = window.origin + pathname @@ -32,34 +33,50 @@ interface PostHogProviderProps { } export function PostHogProvider({ children, disabled }: PostHogProviderProps) { + const { data: session } = useSession(); + useEffect(() => { if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) { + console.debug(`PostHog telemetry enabled. Cloud environment: ${env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT}`); posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, { // @see next.config.mjs for path rewrites to the "/ingest" route. api_host: "/ingest", person_profiles: 'identified_only', capture_pageview: false, autocapture: false, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - sanitize_properties: (properties: Record, _event: string) => { - // https://posthog.com/docs/libraries/js#config - if (properties['$current_url']) { - properties['$current_url'] = null; - } - if (properties['$ip']) { - properties['$ip'] = null; + before_send: (cr) => { + // Remove all default posthog properties if we are not running in a cloud environment. + if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined && cr) { + cr.properties = Object.fromEntries( + Object.entries(cr.properties ?? {}).filter(([key]) => !key.startsWith('$')) + ); } - - return properties; - } + + return cr; + }, }); } else { - console.log("PostHog telemetry disabled"); + console.debug("PostHog telemetry disabled"); + } + }, [disabled]); + + useEffect(() => { + if (!session) { + return; + } + + // Only identify the user if we are running in a cloud environment. + if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined) { + posthog.identify(session.user.id, { + email: session.user.email, + name: session.user.name, + }); } - }, [disabled]) + }, [session]); return ( + {/* @see: https://github.com/vercel/next.js/issues/51581 */} diff --git a/packages/web/src/env.mjs b/packages/web/src/env.mjs index fc839b9a6..31b778410 100644 --- a/packages/web/src/env.mjs +++ b/packages/web/src/env.mjs @@ -50,8 +50,10 @@ export const env = createEnv({ // Misc UI flags SECURITY_CARD_ENABLED: booleanSchema.default('false'), }, - // @NOTE: Make sure you destructure all client variables in the - // `experimental__runtimeEnv` block below. + // @NOTE: Please make sure of the following: + // - Make sure you destructure all client variables in + // the `experimental__runtimeEnv` block below. + // - Update the Dockerfile to pass these variables as build-args. client: { // PostHog NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(), @@ -59,12 +61,15 @@ export const env = createEnv({ // Misc NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'), NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000), + + NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: z.enum(["dev", "staging", "prod"]).optional(), }, // For Next.js >= 13.4.4, you only need to destructure client variables: experimental__runtimeEnv: { NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK, NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION, NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS, + NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT: process.env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT, }, skipValidation: process.env.SKIP_ENV_VALIDATION === "1", emptyStringAsUndefined: true, From e5c81dfbeb17aeb347b2bfa86ba6faad95a9857f Mon Sep 17 00:00:00 2001 From: bkellam Date: Wed, 26 Mar 2025 15:20:00 -0700 Subject: [PATCH 2/3] feedback --- packages/web/src/app/posthogProvider.tsx | 7 ++++++- packages/web/src/lib/posthogEvents.ts | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/web/src/app/posthogProvider.tsx b/packages/web/src/app/posthogProvider.tsx index 4060292b4..80a523ae4 100644 --- a/packages/web/src/app/posthogProvider.tsx +++ b/packages/web/src/app/posthogProvider.tsx @@ -6,6 +6,7 @@ import { usePathname, useSearchParams } from "next/navigation" import { Suspense, useEffect } from "react" import { env } from '@/env.mjs' import { useSession } from 'next-auth/react' +import { captureEvent } from '@/hooks/useCaptureEvent' // @see: https://posthog.com/docs/libraries/next-js#capturing-pageviews function PostHogPageView() { @@ -20,7 +21,10 @@ function PostHogPageView() { url = url + `?${searchParams.toString()}` } - posthog.capture('$pageview', { '$current_url': url }) + captureEvent('$pageview', { + $current_url: url, + domain: window.location.hostname, + }); } }, [pathname, searchParams, posthog]) @@ -46,6 +50,7 @@ export function PostHogProvider({ children, disabled }: PostHogProviderProps) { autocapture: false, before_send: (cr) => { // Remove all default posthog properties if we are not running in a cloud environment. + if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined && cr) { cr.properties = Object.fromEntries( Object.entries(cr.properties ?? {}).filter(([key]) => !key.startsWith('$')) diff --git a/packages/web/src/lib/posthogEvents.ts b/packages/web/src/lib/posthogEvents.ts index 2f36712d7..20e5f1d26 100644 --- a/packages/web/src/lib/posthogEvents.ts +++ b/packages/web/src/lib/posthogEvents.ts @@ -244,6 +244,11 @@ export type PosthogEventMap = { wa_demo_card_click: {}, wa_demo_try_card_pressed: {}, wa_share_link_created: {}, + ////////////////////////////////////////////////////////////////// + $pageview: { + $current_url: string, + domain: string, + }, } export type PosthogEvent = keyof PosthogEventMap; \ No newline at end of file From 7a2c62eacad7fe7ee001676992c58ba89eb59d91 Mon Sep 17 00:00:00 2001 From: bkellam Date: Wed, 26 Mar 2025 15:56:07 -0700 Subject: [PATCH 3/3] feedback --- packages/web/src/app/posthogProvider.tsx | 27 +++++++++++++----------- packages/web/src/lib/posthogEvents.ts | 1 - 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/web/src/app/posthogProvider.tsx b/packages/web/src/app/posthogProvider.tsx index 80a523ae4..12aa91fd4 100644 --- a/packages/web/src/app/posthogProvider.tsx +++ b/packages/web/src/app/posthogProvider.tsx @@ -23,7 +23,6 @@ function PostHogPageView() { captureEvent('$pageview', { $current_url: url, - domain: window.location.hostname, }); } }, [pathname, searchParams, posthog]) @@ -48,17 +47,21 @@ export function PostHogProvider({ children, disabled }: PostHogProviderProps) { person_profiles: 'identified_only', capture_pageview: false, autocapture: false, - before_send: (cr) => { - // Remove all default posthog properties if we are not running in a cloud environment. - - if (env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined && cr) { - cr.properties = Object.fromEntries( - Object.entries(cr.properties ?? {}).filter(([key]) => !key.startsWith('$')) - ); - } - - return cr; - }, + // In self-hosted mode, we don't want to capture the following + // default properties. + // @see: https://posthog.com/docs/data/events#default-properties + property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [ + '$current_url', + '$pathname', + '$session_entry_url', + '$session_entry_host', + '$session_entry_pathname', + '$session_entry_referrer', + '$session_entry_referring_domain', + '$referrer', + '$referring_domain', + '$ip', + ] : [] }); } else { console.debug("PostHog telemetry disabled"); diff --git a/packages/web/src/lib/posthogEvents.ts b/packages/web/src/lib/posthogEvents.ts index 20e5f1d26..0cbc95683 100644 --- a/packages/web/src/lib/posthogEvents.ts +++ b/packages/web/src/lib/posthogEvents.ts @@ -247,7 +247,6 @@ export type PosthogEventMap = { ////////////////////////////////////////////////////////////////// $pageview: { $current_url: string, - domain: string, }, }