feat: add posthog events (#231)

* feat: add posthog events

* fix: workflow_duplicated event

* chore: add events to enum
This commit is contained in:
Sabiha Khan 2026-04-10 17:52:21 +05:30 committed by GitHub
parent bb5f56bfb7
commit 3f19a16e7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 450 additions and 93 deletions

View file

@ -1,6 +1,7 @@
'use client';
import { Headphones, Loader2 } from 'lucide-react';
import posthog from 'posthog-js';
import { useCallback, useState } from 'react';
import { Button } from '@/components/ui/button';
@ -12,6 +13,7 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { PostHogEvent } from '@/constants/posthog-events';
import { downloadFile, getSignedUrl } from '@/lib/files';
export function MediaPreviewDialog() {
@ -48,6 +50,11 @@ export function MediaPreviewDialog() {
const response = await fetch(transcriptResult);
const text = await response.text();
setTranscriptContent(text);
posthog.capture(PostHogEvent.TRANSCRIPT_VIEWED, {
run_id: runId,
source: 'media_preview_dialog',
transcript_length: text.length,
});
} catch (error) {
console.error('Error fetching transcript:', error);
}
@ -78,7 +85,16 @@ export function MediaPreviewDialog() {
)}
{!mediaLoading && audioSignedUrl && (
<audio src={audioSignedUrl} controls autoPlay className="w-full mt-4" />
<audio
src={audioSignedUrl}
controls
autoPlay
className="w-full mt-4"
onPlay={() => posthog.capture(PostHogEvent.RECORDING_PLAYED, {
run_id: selectedRunId,
source: 'media_preview_dialog',
})}
/>
)}
{!mediaLoading && transcriptContent && (

View file

@ -8,39 +8,67 @@ import { useAuth } from '@/lib/auth';
/**
* PostHogIdentify
* ---------------
* A tiny client-side component that calls `posthog.identify` once the
* authenticated user object is available. It also resets PostHog when the
* user logs out or switches accounts.
* Calls `posthog.identify` once the authenticated user object is available,
* using the Stack Auth user ID as distinct_id with email and name as properties.
*
* This component is intended to be rendered high in the React tree (e.g. in
* `app/layout.tsx`) so that PostHog always knows which user is active for the
* current browser session.
* Rendered in the root layout (`app/layout.tsx`) so it fires on every session
* for every logged-in user.
*/
export default function PostHogIdentify() {
const { user } = useAuth();
useEffect(() => {
// Only run if PostHog is enabled
if (process.env.NEXT_PUBLIC_ENABLE_POSTHOG !== 'true') {
return;
}
if (user) {
try {
// Identify the user in PostHog with their unique id and useful traits
posthog.identify(String(user.id ?? ''));
} catch (err) {
// Silently ignore identification errors so they don't break the app
const identify = () => {
try {
// Stack Auth users expose primaryEmail/displayName,
// local users expose email/name — handle both.
const email =
'primaryEmail' in user ? user.primaryEmail :
'email' in user ? user.email :
undefined;
const name =
'displayName' in user ? user.displayName :
'name' in user ? user.name :
undefined;
console.warn('Failed to identify user in PostHog', err);
// Use provider_id as distinct_id to match backend PostHog events.
// Stack Auth users: user.id is already the provider_id.
// OSS users: provider_id is returned from the auth API.
const distinctId =
'provider_id' in user && user.provider_id
? String(user.provider_id)
: String(user.id);
posthog.identify(distinctId, {
...(email && { email }),
...(name && { name }),
});
} catch (err) {
console.warn('Failed to identify user in PostHog', err);
}
};
if (posthog.__loaded) {
identify();
} else {
// PostHog initializes async — retry until ready
let attempts = 0;
const interval = setInterval(() => {
attempts++;
if (posthog.__loaded) {
identify();
clearInterval(interval);
} else if (attempts >= 20) {
clearInterval(interval);
}
}, 200);
return () => clearInterval(interval);
}
} else {
// If the user logs out, clear the PostHog identity so future anonymous
// interactions aren't associated with the previous account.
posthog.reset();
}
}, [user]);
// This component does not render anything
return null;
}