Skip to content

Commit f92bb55

Browse files
committed
feat: event tracking happy path
1 parent 3f5ce69 commit f92bb55

File tree

5 files changed

+142
-9
lines changed

5 files changed

+142
-9
lines changed

studio/src/components/onboarding/onboarding-provider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const OnboardingContext = createContext<OnboardingState>({
4040
resetSkipped: () => undefined,
4141
});
4242

43-
const ONBOARDING_V1_LAST_STEP = 4;
43+
const ONBOARDING_V1_LAST_STEP = 3;
4444

4545
export const OnboardingProvider = ({ children }: { children: ReactNode }) => {
4646
const { onboarding: onboardingFlag, status: featureFlagStatus } = useContext(PostHogFeatureFlagContext);

studio/src/components/onboarding/step-1.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect } from 'react';
22
import { useOnboarding } from '@/hooks/use-onboarding';
3+
import { usePostHog } from 'posthog-js/react';
34
import { OnboardingContainer } from './onboarding-container';
45
import { OnboardingNavigation } from './onboarding-navigation';
56
import { useMutation } from '@connectrpc/connect-query';
@@ -8,6 +9,7 @@ import { useRouter } from 'next/router';
89
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
910
import { useToast } from '../ui/use-toast';
1011
import { SubmitHandler, useZodForm } from '@/hooks/use-form';
12+
import { captureOnboardingEvent } from '@/lib/track';
1113
import { Controller } from 'react-hook-form';
1214
import { z } from 'zod';
1315
import { Form } from '../ui/form';
@@ -33,10 +35,21 @@ const WhyListItem = ({ title, text }: { title: string; text: string }) => (
3335
</li>
3436
);
3537

38+
const normalizeReferrer = (referrer: string | string[]): string => {
39+
if (Array.isArray(referrer)) {
40+
return referrer.join(' ');
41+
}
42+
43+
return referrer;
44+
};
45+
3646
export const Step1 = () => {
3747
const router = useRouter();
48+
const posthog = usePostHog();
3849
const { toast } = useToast();
3950
const { setStep, setSkipped, setOnboarding, onboarding } = useOnboarding();
51+
// Referrer can be `wgc` when onboarding is opened via `wgc demo` command
52+
const referrer = normalizeReferrer(router.query.referrer || document.referrer);
4053

4154
const form = useZodForm<OnboardingFormValues>({
4255
mode: 'onChange',
@@ -63,6 +76,12 @@ export const Step1 = () => {
6376
slack: formValues.channels.slack,
6477
email: formValues.channels.email,
6578
});
79+
captureOnboardingEvent(posthog, {
80+
name: 'onboarding_step_completed',
81+
options: {
82+
step_name: 'welcome',
83+
},
84+
});
6685
router.push('/onboarding/2');
6786
},
6887
onError: (error) => {
@@ -82,7 +101,21 @@ export const Step1 = () => {
82101

83102
useEffect(() => {
84103
setStep(1);
85-
}, [setStep]);
104+
}, [setStep, posthog, referrer]);
105+
106+
useEffect(() => {
107+
// We want to trigger the onboarding only for the first time when the onboarding record
108+
// does not exist in the database yet. Users can navigate to this first step as well
109+
// and in that case we want to ignore it.
110+
if (onboarding) return;
111+
112+
captureOnboardingEvent(posthog, {
113+
name: 'onboarding_started',
114+
options: {
115+
entry_source: referrer,
116+
},
117+
});
118+
}, [onboarding, referrer, posthog]);
86119

87120
return (
88121
<OnboardingContainer>
@@ -151,7 +184,15 @@ export const Step1 = () => {
151184
</div>
152185

153186
<OnboardingNavigation
154-
onSkip={setSkipped}
187+
onSkip={() => {
188+
captureOnboardingEvent(posthog, {
189+
name: 'onboarding_skipped',
190+
options: {
191+
step_name: 'welcome',
192+
},
193+
});
194+
setSkipped();
195+
}}
155196
forwardLabel="Start the tour"
156197
forward={{
157198
onClick: form.handleSubmit(onSubmit),

studio/src/components/onboarding/step-2.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { getFederatedGraphByName } from '@wundergraph/cosmo-connect/dist/platfor
55
import { EnumStatusCode } from '@wundergraph/cosmo-connect/dist/common/common_pb';
66
import { GetFederatedGraphByNameResponse } from '@wundergraph/cosmo-connect/dist/platform/v1/platform_pb';
77
import { CheckCircledIcon, InfoCircledIcon } from '@radix-ui/react-icons';
8+
import { usePostHog } from 'posthog-js/react';
89
import { useOnboarding } from '@/hooks/use-onboarding';
10+
import { captureOnboardingEvent } from '@/lib/track';
911
import { OnboardingContainer } from './onboarding-container';
1012
import { OnboardingNavigation } from './onboarding-navigation';
1113
import { FederationAnimation } from './federation-animation';
@@ -118,6 +120,7 @@ const StatusText = ({ status, onRetry }: { status: 'pending' | 'ok' | 'fail' | '
118120
export const Step2 = () => {
119121
const { setStep, setSkipped } = useOnboarding();
120122
const router = useRouter();
123+
const posthog = usePostHog();
121124
const [isPolling, setIsPolling] = useState(true);
122125
const [pollingEpoch, setPollingEpoch] = useState(0);
123126

@@ -225,10 +228,26 @@ export const Step2 = () => {
225228

226229
<OnboardingNavigation
227230
className="pt-2"
228-
onSkip={setSkipped}
231+
onSkip={() => {
232+
captureOnboardingEvent(posthog, {
233+
name: 'onboarding_skipped',
234+
options: {
235+
step_name: 'create_graph',
236+
},
237+
});
238+
setSkipped();
239+
}}
229240
backHref="/onboarding/1"
230241
forward={{
231-
onClick: () => router.push('/onboarding/3'),
242+
onClick: () => {
243+
captureOnboardingEvent(posthog, {
244+
name: 'onboarding_step_completed',
245+
options: {
246+
step_name: 'create_graph',
247+
},
248+
});
249+
router.push('/onboarding/3');
250+
},
232251
disabled: status !== 'ok',
233252
}}
234253
/>

studio/src/components/onboarding/step-3.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { motion } from 'framer-motion';
22
import { useCallback, useEffect, useMemo, useState } from 'react';
33
import { useOnboarding } from '@/hooks/use-onboarding';
44
import { useFireworks } from '@/hooks/use-fireworks';
5+
import { usePostHog } from 'posthog-js/react';
6+
import { captureOnboardingEvent } from '@/lib/track';
57
import { OnboardingContainer } from './onboarding-container';
68
import { OnboardingNavigation } from './onboarding-navigation';
79
import { StatusIcon, type OnboardingStatus } from './status-icon';
@@ -100,6 +102,7 @@ export const Step3 = () => {
100102
const { toast } = useToast();
101103
const { setStep, setSkipped, setOnboarding } = useOnboarding();
102104
const currentOrg = useCurrentOrganization();
105+
const posthog = usePostHog();
103106

104107
const [isPolling, setIsPolling] = useState(true);
105108
const [isMetricsPolling, setIsMetricsPolling] = useState(false);
@@ -139,7 +142,11 @@ export const Step3 = () => {
139142

140143
const hasActiveRouter = (routersData?.routers?.length ?? 0) > 0;
141144

142-
const metricsStatus = getMetricsStatus({ data: graphData, isPolling: isMetricsPolling, hasPolled: hasMetricsPolled });
145+
const metricsStatus = getMetricsStatus({
146+
data: graphData,
147+
isPolling: isMetricsPolling,
148+
hasPolled: hasMetricsPolled,
149+
});
143150

144151
useFireworks(metricsStatus === 'ok');
145152

@@ -186,6 +193,12 @@ export const Step3 = () => {
186193
email: Boolean(prev?.email),
187194
}));
188195

196+
captureOnboardingEvent(posthog, {
197+
name: 'onboarding_completed',
198+
options: {
199+
step_name: 'run_router_send_metrics',
200+
},
201+
});
189202
setIsFinished(true);
190203
},
191204
onError: (error) => {
@@ -332,7 +345,15 @@ export const Step3 = () => {
332345

333346
<OnboardingNavigation
334347
className="pt-2"
335-
onSkip={setSkipped}
348+
onSkip={() => {
349+
captureOnboardingEvent(posthog, {
350+
name: 'onboarding_skipped',
351+
options: {
352+
step_name: 'run_router_send_metrics',
353+
},
354+
});
355+
setSkipped();
356+
}}
336357
backHref="/onboarding/2"
337358
forward={{
338359
onClick: () => mutate({}),

studio/src/lib/track.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Tracking. This will be available if the following scripts are embedded though CUSTOM_HEAD_SCRIPTS
22
// Reo, PostHog
33

4-
import posthog from 'posthog-js';
4+
import posthog, { type PostHog } from 'posthog-js';
55

66
declare global {
77
interface Window {
@@ -79,4 +79,56 @@ const identify = ({
7979
});
8080
};
8181

82-
export { resetTracking, identify };
82+
/**
83+
* IDs in this type are ordered. They correspond to [cosmo-onboarding-v1] onboarding version
84+
*/
85+
type OnboardingStepId = 'welcome' | 'create_graph' | 'run_router_send_metrics';
86+
type OnboardingTrackEvent =
87+
| {
88+
name: 'onboarding_started';
89+
options: {
90+
entry_source: string;
91+
};
92+
}
93+
| {
94+
name: 'onboarding_step_completed';
95+
options: {
96+
step_name: OnboardingStepId;
97+
};
98+
}
99+
| {
100+
name: 'onboarding_skipped';
101+
options: {
102+
step_name: OnboardingStepId;
103+
};
104+
}
105+
| {
106+
name: 'onboarding_step_failed';
107+
options: {
108+
step_name: OnboardingStepId;
109+
entry_source: string;
110+
/**
111+
* + [resource] - CRUD operation failures, such as onboarding record created
112+
* in the database, updating communication channels, creating federated
113+
* graph & plugin publish (CLI)
114+
* + [router] - failures when running the router via CLI
115+
* + [metrics] - sending metrics fails (CLI) or metrics are not detected
116+
* within permitted time window (web)
117+
* + [invites] - failed to send invites after finishing the onboarding
118+
*/
119+
error_category: 'resource_created' | 'router' | 'metrics' | 'invites';
120+
error_message: string;
121+
};
122+
}
123+
| {
124+
name: 'onboarding_completed';
125+
options: {
126+
step_name: OnboardingStepId;
127+
};
128+
};
129+
130+
const captureOnboardingEvent = (client: PostHog, event: OnboardingTrackEvent): void => {
131+
client.capture(event.name, event.options);
132+
};
133+
134+
export { resetTracking, identify, captureOnboardingEvent };

0 commit comments

Comments
 (0)