Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const TrialIndicator = ({ subscription }: Props) => {

if (isServiceError(subscription)) {
captureEvent('wa_trial_nav_subscription_fetch_fail', {
error: subscription.errorCode,
errorCode: subscription.errorCode,
});
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export function ChangeOrgDomainCard({ orgDomain, currentUserRole, rootDomain }:
description: `❌ Failed to update organization url. Reason: ${result.message}`,
})
captureEvent('wa_org_domain_updated_fail', {
error: result.errorCode,
errorCode: result.errorCode,
});
} else {
toast({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function ChangeOrgNameCard({ orgName, currentUserRole }: ChangeOrgNameCar
description: `❌ Failed to update organization name. Reason: ${result.message}`,
})
captureEvent('wa_org_name_updated_fail', {
error: result.errorCode,
errorCode: result.errorCode,
});
} else {
toast({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const InviteMemberCard = ({ currentUserRole, isBillingEnabled, seatsAvail
description: `❌ Failed to invite members. Reason: ${res.message}`
});
captureEvent('wa_invite_member_card_invite_fail', {
error: res.errorCode,
errorCode: res.errorCode,
num_emails: data.emails.length,
});
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const InvitesList = ({ invites, currentUserRole }: InviteListProps) => {
description: `❌ Failed to cancel invite. Reason: ${response.message}`
})
captureEvent('wa_invites_list_cancel_invite_fail', {
error: response.errorCode,
errorCode: response.errorCode,
})
} else {
toast({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName }
description: `❌ Failed to remove member. Reason: ${response.message}`
})
captureEvent('wa_members_list_remove_member_fail', {
error: response.errorCode,
errorCode: response.errorCode,
})
} else {
toast({
Expand All @@ -91,7 +91,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName }
description: `❌ Failed to transfer ownership. Reason: ${response.message}`
})
captureEvent('wa_members_list_transfer_ownership_fail', {
error: response.errorCode,
errorCode: response.errorCode,
})
} else {
toast({
Expand All @@ -111,7 +111,7 @@ export const MembersList = ({ members, currentUserId, currentUserRole, orgName }
description: `❌ Failed to leave organization. Reason: ${response.message}`
})
captureEvent('wa_members_list_leave_org_fail', {
error: response.errorCode,
errorCode: response.errorCode,
})
} else {
toast({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const RequestsList = ({ requests, currentUserRole }: RequestsListProps) =
description: `❌ Failed to approve request. Reason: ${response.message}`
})
captureEvent('wa_requests_list_approve_request_fail', {
error: response.errorCode,
errorCode: response.errorCode,
})
} else {
toast({
Expand All @@ -83,7 +83,7 @@ export const RequestsList = ({ requests, currentUserRole }: RequestsListProps) =
description: `❌ Failed to reject request.`
})
captureEvent('wa_requests_list_reject_request_fail', {
error: response.errorCode,
errorCode: response.errorCode,
})
} else {
toast({
Expand Down
3 changes: 3 additions & 0 deletions packages/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { SessionProvider } from "next-auth/react";
import { env } from "@sourcebot/shared";
import { env as clientEnv } from "@sourcebot/shared/client";
import { PlanProvider } from "@/features/entitlements/planProvider";
import { getEntitlements } from "@sourcebot/shared";

Expand Down Expand Up @@ -42,6 +43,8 @@ export default function RootLayout({
// @note: the posthog api key doesn't need to be kept secret,
// so we are safe to send it to the client.
posthogApiKey={env.POSTHOG_PAPIK}
sourcebotVersion={clientEnv.NEXT_PUBLIC_SOURCEBOT_VERSION}
sourcebotInstallId={env.SOURCEBOT_INSTALL_ID}
>
<ThemeProvider
attribute="class"
Expand Down
34 changes: 24 additions & 10 deletions packages/web/src/app/posthogProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,17 @@ interface PostHogProviderProps {
children: React.ReactNode
isDisabled: boolean
posthogApiKey: string
sourcebotVersion: string
sourcebotInstallId: string
}

export function PostHogProvider({ children, isDisabled, posthogApiKey }: PostHogProviderProps) {
export function PostHogProvider({
children,
isDisabled,
posthogApiKey,
sourcebotVersion,
sourcebotInstallId,
}: PostHogProviderProps) {
const { data: session } = useSession();

useEffect(() => {
Expand All @@ -61,27 +69,33 @@ export function PostHogProvider({ children, isDisabled, posthogApiKey }: PostHog
'$referrer',
'$referring_domain',
'$ip',
] : []
] : [],
loaded: (posthog) => {
// Include install id & version in all events.
posthog.register({
sourcebot_version: sourcebotVersion,
install_id: sourcebotInstallId,
});
}
});
} else {
console.debug("PostHog telemetry disabled");
}
}, [isDisabled, posthogApiKey]);
}, [isDisabled, posthogApiKey, sourcebotInstallId, sourcebotVersion]);

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, {
posthog.identify(
session.user.id,
// Only include email & name when running in a cloud environment.
env.NEXT_PUBLIC_SOURCEBOT_CLOUD_ENVIRONMENT !== undefined ? {
email: session.user.email,
name: session.user.name,
});
} else {
console.debug("PostHog identify skipped");
}
} : undefined
);
}, [session]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function ChangeBillingEmailCard({ currentUserRole, billingEmail }: Change
description: "❌ Failed to update billing email. Please try again.",
})
captureEvent('wa_billing_email_updated_fail', {
error: result.message,
errorCode: result.errorCode,
})
}
setIsLoading(false)
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/ee/features/billing/components/checkout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const Checkout = () => {
variant: "destructive",
});
captureEvent('wa_onboard_checkout_fail', {
error: errorMessage,
errorCode: errorMessage,
});
}
}, [errorCode, errorMessage, toast, captureEvent]);
Expand All @@ -45,7 +45,7 @@ export const Checkout = () => {
variant: "destructive",
})
captureEvent('wa_onboard_checkout_fail', {
error: response.errorCode,
errorCode: response.errorCode,
});
} else {
captureEvent('wa_onboard_checkout_success', {});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function ManageSubscriptionButton({ currentUserRole }: { currentUserRole:
const session = await getCustomerPortalSessionLink(domain);
if (isServiceError(session)) {
captureEvent('wa_manage_subscription_button_create_portal_session_fail', {
error: session.errorCode,
errorCode: session.errorCode,
});
setIsLoading(false);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const TeamUpgradeCard = ({ buttonText }: TeamUpgradeCardProps) => {
variant: "destructive",
});
captureEvent('wa_team_upgrade_checkout_fail', {
error: response.errorCode,
errorCode: response.errorCode,
});
} else {
router.push(response.url);
Expand Down
6 changes: 1 addition & 5 deletions packages/web/src/hooks/useCaptureEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@
import { CaptureOptions } from "posthog-js";
import posthog from "posthog-js";
import { PosthogEvent, PosthogEventMap } from "../lib/posthogEvents";
import { env } from "@sourcebot/shared/client";

export function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E], options?: CaptureOptions) {
if(!options) {
options = {};
}
options.send_instantly = true;
posthog.capture(event, {
...properties,
sourcebot_version: env.NEXT_PUBLIC_SOURCEBOT_VERSION,
}, options);
posthog.capture(event, properties, options);
}

/**
Expand Down
48 changes: 43 additions & 5 deletions packages/web/src/lib/posthog.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { PostHog } from 'posthog-node'
import { env } from '@sourcebot/shared'
import { env as clientEnv } from '@sourcebot/shared/client';
import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
import * as Sentry from "@sentry/nextjs";
import { PosthogEvent, PosthogEventMap } from './posthogEvents';
import { cookies } from 'next/headers';
import { cookies, headers } from 'next/headers';
import { auth } from '@/auth';
import { getVerifiedApiObject } from '@/withAuthV2';

/**
* @note: This is a subset of the properties stored in the
Expand Down Expand Up @@ -47,13 +50,43 @@ const getPostHogCookie = (cookieStore: Pick<RequestCookies, 'get'>): PostHogCook
return undefined;
}

/**
* Attempts to retrieve the distinct id of the current user.
*/
const tryGetDistinctId = async () => {
// First, attempt to retrieve the distinct id from the cookie.
const cookieStore = await cookies();
const cookie = getPostHogCookie(cookieStore);
if (cookie) {
return cookie.distinct_id;
}

// Next, from the session.
const session = await auth();
if (session) {
return session.user.id;
}

// Finally, from the api key.
const headersList = await headers();
const apiKeyString = headersList.get("X-Sourcebot-Api-Key") ?? undefined;
if (!apiKeyString) {
return undefined;
}

const apiKey = await getVerifiedApiObject(apiKeyString);
return apiKey?.createdById;
}

export async function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E]) {
if (env.SOURCEBOT_TELEMETRY_DISABLED === 'true') {
return;
}

const cookieStore = await cookies();
const cookie = getPostHogCookie(cookieStore);
const distinctId = await tryGetDistinctId();

const headersList = await headers();
const host = headersList.get("host") ?? undefined;

const posthog = new PostHog(env.POSTHOG_PAPIK, {
host: 'https://us.i.posthog.com',
Expand All @@ -63,7 +96,12 @@ export async function captureEvent<E extends PosthogEvent>(event: E, properties:

posthog.capture({
event,
properties,
distinctId: cookie?.distinct_id ?? '',
properties: {
...properties,
sourcebot_version: clientEnv.NEXT_PUBLIC_SOURCEBOT_VERSION,
install_id: env.SOURCEBOT_INSTALL_ID,
$host: host,
},
distinctId,
});
}
Loading
Loading