mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-15 13:55:20 +00:00
PostHog event pass (#246)
This commit is contained in:
parent
78b8916797
commit
8e3235a56d
9 changed files with 81 additions and 38 deletions
|
|
@ -34,7 +34,7 @@ AUTH_URL="http://localhost:3000"
|
||||||
# Sentry
|
# Sentry
|
||||||
# SENTRY_BACKEND_DSN=""
|
# SENTRY_BACKEND_DSN=""
|
||||||
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
|
# NEXT_PUBLIC_SENTRY_WEBAPP_DSN=""
|
||||||
SENTRY_ENVIRONMENT="dev"
|
# SENTRY_ENVIRONMENT="dev"
|
||||||
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
|
# NEXT_PUBLIC_SENTRY_ENVIRONMENT="dev"
|
||||||
# SENTRY_AUTH_TOKEN=
|
# SENTRY_AUTH_TOKEN=
|
||||||
|
|
||||||
|
|
@ -77,3 +77,5 @@ SOURCEBOT_TELEMETRY_DISABLED=true # Disables telemetry collection
|
||||||
# CONFIG_MAX_REPOS_NO_TOKEN=
|
# CONFIG_MAX_REPOS_NO_TOKEN=
|
||||||
# NODE_ENV=
|
# NODE_ENV=
|
||||||
# SOURCEBOT_TENANCY_MODE=single
|
# SOURCEBOT_TENANCY_MODE=single
|
||||||
|
|
||||||
|
# NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=
|
||||||
|
|
@ -9,6 +9,7 @@ ARG SOURCEBOT_VERSION
|
||||||
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
|
# @see: https://posthog.com/tutorials/api-capture-events#authenticating-with-the-project-api-key
|
||||||
ARG POSTHOG_PAPIK
|
ARG POSTHOG_PAPIK
|
||||||
ARG SENTRY_ENVIRONMENT
|
ARG SENTRY_ENVIRONMENT
|
||||||
|
ARG SOURCEBOT_CLOUD_ENVIRONMENT
|
||||||
|
|
||||||
FROM node:20-alpine3.19 AS node-alpine
|
FROM node:20-alpine3.19 AS node-alpine
|
||||||
FROM golang:1.23.4-alpine3.19 AS go-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
|
ENV NEXT_PUBLIC_POSTHOG_PAPIK=$POSTHOG_PAPIK
|
||||||
ARG SENTRY_ENVIRONMENT
|
ARG SENTRY_ENVIRONMENT
|
||||||
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
|
ENV NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
|
||||||
|
ARG SOURCEBOT_CLOUD_ENVIRONMENT
|
||||||
|
ENV NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT=$SOURCEBOT_CLOUD_ENVIRONMENT
|
||||||
# Local args
|
# Local args
|
||||||
ARG SENTRY_WEBAPP_DSN
|
ARG NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
||||||
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$SENTRY_WEBAPP_DSN
|
ENV NEXT_PUBLIC_SENTRY_WEBAPP_DSN=$NEXT_PUBLIC_SENTRY_WEBAPP_DSN
|
||||||
# -----------
|
# -----------
|
||||||
|
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat
|
||||||
|
|
@ -104,6 +107,8 @@ ARG SOURCEBOT_VERSION
|
||||||
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
|
ENV SOURCEBOT_VERSION=$SOURCEBOT_VERSION
|
||||||
ARG POSTHOG_PAPIK
|
ARG POSTHOG_PAPIK
|
||||||
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
|
ENV POSTHOG_PAPIK=$POSTHOG_PAPIK
|
||||||
|
ARG SENTRY_ENVIRONMENT
|
||||||
|
ENV SENTRY_ENVIRONMENT=$SENTRY_ENVIRONMENT
|
||||||
# Local args
|
# Local args
|
||||||
# -----------
|
# -----------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { useSession } from "next-auth/react";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
import { signOut } from "next-auth/react"
|
import { signOut } from "next-auth/react"
|
||||||
import { env } from "@/env.mjs";
|
import { env } from "@/env.mjs";
|
||||||
|
import posthog from "posthog-js";
|
||||||
|
|
||||||
interface SettingsDropdownProps {
|
interface SettingsDropdownProps {
|
||||||
menuButtonClassName?: string;
|
menuButtonClassName?: string;
|
||||||
|
|
@ -94,7 +95,9 @@ export const SettingsDropdown = ({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
signOut({
|
signOut({
|
||||||
redirectTo: "/login",
|
redirectTo: "/login",
|
||||||
});
|
}).then(() => {
|
||||||
|
posthog.reset();
|
||||||
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<LogOut className="mr-2 h-4 w-4" />
|
<LogOut className="mr-2 h-4 w-4" />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useState } from "react";
|
||||||
import { CodeHostType } from "@/lib/utils";
|
import { CodeHostType } from "@/lib/utils";
|
||||||
import { getCodeHostIcon } from "@/lib/utils";
|
import { getCodeHostIcon } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
|
|
@ -15,8 +15,6 @@ import { OnboardingSteps } from "@/lib/constants";
|
||||||
import { BackButton } from "./onboardBackButton";
|
import { BackButton } from "./onboardBackButton";
|
||||||
import { CodeHostIconButton } from "../../components/codeHostIconButton";
|
import { CodeHostIconButton } from "../../components/codeHostIconButton";
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
import { useSession } from "next-auth/react";
|
|
||||||
import posthog from "posthog-js";
|
|
||||||
import SecurityCard from "@/app/components/securityCard";
|
import SecurityCard from "@/app/components/securityCard";
|
||||||
|
|
||||||
interface ConnectCodeHostProps {
|
interface ConnectCodeHostProps {
|
||||||
|
|
@ -27,18 +25,6 @@ interface ConnectCodeHostProps {
|
||||||
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
|
export const ConnectCodeHost = ({ nextStep, securityCardEnabled }: ConnectCodeHostProps) => {
|
||||||
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
|
const [selectedCodeHost, setSelectedCodeHost] = useState<CodeHostType | null>(null);
|
||||||
const router = useRouter();
|
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(() => {
|
const onCreated = useCallback(() => {
|
||||||
router.push(`?step=${nextStep}`);
|
router.push(`?step=${nextStep}`);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { LogOutIcon } from "lucide-react";
|
import { LogOutIcon } from "lucide-react";
|
||||||
import { signOut } from "@/auth";
|
import { signOut } from "@/auth";
|
||||||
|
import posthog from "posthog-js";
|
||||||
|
|
||||||
interface LogoutEscapeHatchProps {
|
interface LogoutEscapeHatchProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +16,8 @@ export const LogoutEscapeHatch = ({
|
||||||
"use server";
|
"use server";
|
||||||
await signOut({
|
await signOut({
|
||||||
redirectTo: "/login",
|
redirectTo: "/login",
|
||||||
|
}).then(() => {
|
||||||
|
posthog.reset();
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,12 @@ import { SourcebotLogo } from "@/app/components/sourcebotLogo";
|
||||||
import { TextSeparator } from "@/app/components/textSeparator";
|
import { TextSeparator } from "@/app/components/textSeparator";
|
||||||
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
import useCaptureEvent from "@/hooks/useCaptureEvent";
|
||||||
import DemoCard from "@/app/[domain]/onboard/components/demoCard";
|
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 {
|
interface LoginFormProps {
|
||||||
callbackUrl?: string;
|
callbackUrl?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
|
@ -98,6 +104,9 @@ export const LoginForm = ({ callbackUrl, error, enabledMethods }: LoginFormProps
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
{env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined && (
|
||||||
|
<p className="text-xs text-muted-foreground mt-8">By signing in, you agree to the <Link className="underline" href={TERMS_OF_SERVICE_URL} target="_blank">Terms of Service</Link> and <Link className="underline" href={PRIVACY_POLICY_URL} target="_blank">Privacy Policy</Link>.</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,15 @@ import { PostHogProvider as PHProvider } from 'posthog-js/react'
|
||||||
import { usePathname, useSearchParams } from "next/navigation"
|
import { usePathname, useSearchParams } from "next/navigation"
|
||||||
import { Suspense, useEffect } from "react"
|
import { Suspense, useEffect } from "react"
|
||||||
import { env } from '@/env.mjs'
|
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() {
|
function PostHogPageView() {
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const posthog = usePostHog()
|
const posthog = usePostHog()
|
||||||
|
|
||||||
// Track pageviews
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pathname && posthog) {
|
if (pathname && posthog) {
|
||||||
let url = window.origin + pathname
|
let url = window.origin + pathname
|
||||||
|
|
@ -19,7 +21,9 @@ function PostHogPageView() {
|
||||||
url = url + `?${searchParams.toString()}`
|
url = url + `?${searchParams.toString()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
posthog.capture('$pageview', { '$current_url': url })
|
captureEvent('$pageview', {
|
||||||
|
$current_url: url,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [pathname, searchParams, posthog])
|
}, [pathname, searchParams, posthog])
|
||||||
|
|
||||||
|
|
@ -32,34 +36,55 @@ interface PostHogProviderProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
|
export function PostHogProvider({ children, disabled }: PostHogProviderProps) {
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!disabled && env.NEXT_PUBLIC_POSTHOG_PAPIK) {
|
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, {
|
posthog.init(env.NEXT_PUBLIC_POSTHOG_PAPIK, {
|
||||||
// @see next.config.mjs for path rewrites to the "/ingest" route.
|
// @see next.config.mjs for path rewrites to the "/ingest" route.
|
||||||
api_host: "/ingest",
|
api_host: "/ingest",
|
||||||
person_profiles: 'identified_only',
|
person_profiles: 'identified_only',
|
||||||
capture_pageview: false,
|
capture_pageview: false,
|
||||||
autocapture: false,
|
autocapture: false,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// In self-hosted mode, we don't want to capture the following
|
||||||
sanitize_properties: (properties: Record<string, any>, _event: string) => {
|
// default properties.
|
||||||
// https://posthog.com/docs/libraries/js#config
|
// @see: https://posthog.com/docs/data/events#default-properties
|
||||||
if (properties['$current_url']) {
|
property_denylist: env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT === undefined ? [
|
||||||
properties['$current_url'] = null;
|
'$current_url',
|
||||||
}
|
'$pathname',
|
||||||
if (properties['$ip']) {
|
'$session_entry_url',
|
||||||
properties['$ip'] = null;
|
'$session_entry_host',
|
||||||
}
|
'$session_entry_pathname',
|
||||||
|
'$session_entry_referrer',
|
||||||
return properties;
|
'$session_entry_referring_domain',
|
||||||
}
|
'$referrer',
|
||||||
|
'$referring_domain',
|
||||||
|
'$ip',
|
||||||
|
] : []
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("PostHog telemetry disabled");
|
console.debug("PostHog telemetry disabled");
|
||||||
}
|
}
|
||||||
}, [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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PHProvider client={posthog}>
|
<PHProvider client={posthog}>
|
||||||
|
{/* @see: https://github.com/vercel/next.js/issues/51581 */}
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<PostHogPageView />
|
<PostHogPageView />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,10 @@ export const env = createEnv({
|
||||||
// Misc UI flags
|
// Misc UI flags
|
||||||
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
|
SECURITY_CARD_ENABLED: booleanSchema.default('false'),
|
||||||
},
|
},
|
||||||
// @NOTE: Make sure you destructure all client variables in the
|
// @NOTE: Please make sure of the following:
|
||||||
// `experimental__runtimeEnv` block below.
|
// - Make sure you destructure all client variables in
|
||||||
|
// the `experimental__runtimeEnv` block below.
|
||||||
|
// - Update the Dockerfile to pass these variables as build-args.
|
||||||
client: {
|
client: {
|
||||||
// PostHog
|
// PostHog
|
||||||
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),
|
NEXT_PUBLIC_POSTHOG_PAPIK: z.string().optional(),
|
||||||
|
|
@ -59,12 +61,15 @@ export const env = createEnv({
|
||||||
// Misc
|
// Misc
|
||||||
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
|
NEXT_PUBLIC_SOURCEBOT_VERSION: z.string().default('unknown'),
|
||||||
NEXT_PUBLIC_POLLING_INTERVAL_MS: numberSchema.default(5000),
|
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:
|
// For Next.js >= 13.4.4, you only need to destructure client variables:
|
||||||
experimental__runtimeEnv: {
|
experimental__runtimeEnv: {
|
||||||
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
|
NEXT_PUBLIC_POSTHOG_PAPIK: process.env.NEXT_PUBLIC_POSTHOG_PAPIK,
|
||||||
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
|
NEXT_PUBLIC_SOURCEBOT_VERSION: process.env.NEXT_PUBLIC_SOURCEBOT_VERSION,
|
||||||
NEXT_PUBLIC_POLLING_INTERVAL_MS: process.env.NEXT_PUBLIC_POLLING_INTERVAL_MS,
|
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",
|
skipValidation: process.env.SKIP_ENV_VALIDATION === "1",
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
|
|
|
||||||
|
|
@ -244,6 +244,10 @@ export type PosthogEventMap = {
|
||||||
wa_demo_card_click: {},
|
wa_demo_card_click: {},
|
||||||
wa_demo_try_card_pressed: {},
|
wa_demo_try_card_pressed: {},
|
||||||
wa_share_link_created: {},
|
wa_share_link_created: {},
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
$pageview: {
|
||||||
|
$current_url: string,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PosthogEvent = keyof PosthogEventMap;
|
export type PosthogEvent = keyof PosthogEventMap;
|
||||||
Loading…
Reference in a new issue