Skip to content

Commit 024fa03

Browse files
committed
feat: event tracking happy path
1 parent 1792cb0 commit 024fa03

File tree

9 files changed

+268
-12
lines changed

9 files changed

+268
-12
lines changed

cli/src/commands/demo/command.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ async function cleanupFederatedGraph(
117117
);
118118
}
119119

120+
captureOnboardingEvent({
121+
name: 'onboarding_step_completed',
122+
properties: {
123+
step_name: 'delete_federated_graph',
124+
entry_source: 'wgc',
125+
},
126+
});
127+
120128
spinner.succeed(`Federated graph ${pc.bold(graphData.graph.name)} removed.`);
121129
}
122130

@@ -265,7 +273,10 @@ async function handleStep3(
265273
logPath: string;
266274
},
267275
) {
276+
let firedQueries = 0;
277+
268278
function retryFn() {
279+
firedQueries = 0;
269280
resetScreen(userInfo);
270281
return handleStep3(opts, { userInfo, routerBaseUrl, signal, logPath });
271282
}
@@ -316,6 +327,18 @@ async function handleStep3(
316327
const body = await res.json();
317328
querySpinner.succeed('Sample query response:');
318329
console.log(pc.dim(JSON.stringify(body, null, 2)));
330+
331+
if (firedQueries === 0) {
332+
captureOnboardingEvent({
333+
name: 'onboarding_step_completed',
334+
properties: {
335+
step_name: 'run_router_send_metrics',
336+
entry_source: 'wgc',
337+
},
338+
});
339+
}
340+
341+
firedQueries++;
319342
} catch (err) {
320343
querySpinner.fail(`Sample query failed: ${err instanceof Error ? err.message : String(err)}`);
321344
}
@@ -405,10 +428,12 @@ export default function (opts: BaseCommandOptions) {
405428
const userInfo = await getUserInfo(opts.client);
406429
updateScreenWithUserInfo(userInfo);
407430

408-
const onboardingUrl = `${config.webURL}/onboarding`;
431+
const onboardingUrl = new URL('/onboarding', config.webURL);
409432

410433
async function openOnboardingUrl() {
411-
const process = await open(onboardingUrl);
434+
const browserUrl = new URL(onboardingUrl);
435+
browserUrl.searchParams.set('referrer', 'wgc');
436+
const process = await open(browserUrl.toString());
412437
process.on('error', (error) => {
413438
console.log(pc.yellow(`\nCouldn't open browser: ${error.message}`));
414439
});
@@ -425,12 +450,28 @@ export default function (opts: BaseCommandOptions) {
425450

426451
resetScreen(userInfo);
427452

453+
captureOnboardingEvent({
454+
name: 'onboarding_step_completed',
455+
properties: {
456+
step_name: 'init',
457+
entry_source: 'wgc',
458+
},
459+
});
460+
428461
const onboardingCheck = await handleStep1(opts, userInfo);
429462

430463
if (!onboardingCheck) {
431464
return;
432465
}
433466

467+
captureOnboardingEvent({
468+
name: 'onboarding_step_completed',
469+
properties: {
470+
step_name: 'check_onboarding',
471+
entry_source: 'wgc',
472+
},
473+
});
474+
434475
const logPath = getDemoLogPath();
435476

436477
const step2Result = await handleStep2(opts, {
@@ -445,6 +486,14 @@ export default function (opts: BaseCommandOptions) {
445486
return;
446487
}
447488

489+
captureOnboardingEvent({
490+
name: 'onboarding_step_completed',
491+
properties: {
492+
step_name: 'create_federated_graph',
493+
entry_source: 'wgc',
494+
},
495+
});
496+
448497
const routerBaseUrl = new URL(step2Result.routingUrl).origin;
449498
await handleStep3(opts, { userInfo, routerBaseUrl, signal: controller.signal, logPath });
450499
} finally {

cli/src/commands/demo/util.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ora from 'ora';
77
import pc from 'picocolors';
88
import { z } from 'zod';
99
import { config, cacheDir } from '../../core/config.js';
10+
import { capture } from '../../core/telemetry.js';
1011
import { getDefaultPlatforms, publishPluginPipeline, readPluginFiles } from '../../core/plugin-publish.js';
1112
import type { BaseCommandOptions } from '../../core/types/types.js';
1213
import { visibleLength } from '../../utils.js';
@@ -512,3 +513,21 @@ export async function publishAllPlugins({
512513

513514
return { error: null };
514515
}
516+
517+
export function captureOnboardingEvent({
518+
name,
519+
properties,
520+
}: {
521+
name: 'onboarding_step_completed';
522+
properties: {
523+
step_name:
524+
| 'init'
525+
| 'check_onboarding'
526+
| 'create_federated_graph'
527+
| 'delete_federated_graph'
528+
| 'run_router_send_metrics';
529+
entry_source: 'wgc';
530+
};
531+
}): void {
532+
capture(name, properties);
533+
}

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: 46 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,15 @@ 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+
channel: (Object.keys(formValues.channels) as Array<keyof typeof formValues.channels>).filter(
84+
(key) => formValues.channels[key],
85+
),
86+
},
87+
});
6688
router.push('/onboarding/2');
6789
},
6890
onError: (error) => {
@@ -82,7 +104,21 @@ export const Step1 = () => {
82104

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

87123
return (
88124
<OnboardingContainer>
@@ -151,7 +187,15 @@ export const Step1 = () => {
151187
</div>
152188

153189
<OnboardingNavigation
154-
onSkip={setSkipped}
190+
onSkip={() => {
191+
captureOnboardingEvent(posthog, {
192+
name: 'onboarding_skipped',
193+
options: {
194+
step_name: 'welcome',
195+
},
196+
});
197+
setSkipped();
198+
}}
155199
forwardLabel="Start the tour"
156200
forward={{
157201
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';
@@ -115,6 +117,7 @@ const StatusText = ({ status, onRetry }: { status: 'pending' | 'ok' | 'fail' | '
115117
export const Step2 = () => {
116118
const { setStep, setSkipped } = useOnboarding();
117119
const router = useRouter();
120+
const posthog = usePostHog();
118121
const [polling, dispatch] = useReducer(pollingReducer, { active: true, epoch: 0 });
119122

120123
const restartPolling = useCallback(() => dispatch({ type: 'RESTART' }), []);
@@ -214,10 +217,26 @@ export const Step2 = () => {
214217

215218
<OnboardingNavigation
216219
className="pt-2"
217-
onSkip={setSkipped}
220+
onSkip={() => {
221+
captureOnboardingEvent(posthog, {
222+
name: 'onboarding_skipped',
223+
options: {
224+
step_name: 'create_graph',
225+
},
226+
});
227+
setSkipped();
228+
}}
218229
backHref="/onboarding/1"
219230
forward={{
220-
onClick: () => router.push('/onboarding/3'),
231+
onClick: () => {
232+
captureOnboardingEvent(posthog, {
233+
name: 'onboarding_step_completed',
234+
options: {
235+
step_name: 'create_graph',
236+
},
237+
});
238+
router.push('/onboarding/3');
239+
},
221240
disabled: status !== 'ok',
222241
}}
223242
/>

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { motion } from 'framer-motion';
22
import { useEffect, useMemo, useReducer, 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';
@@ -124,6 +126,7 @@ export const Step3 = () => {
124126
const { toast } = useToast();
125127
const { setStep, setSkipped, setOnboarding } = useOnboarding();
126128
const currentOrg = useCurrentOrganization();
129+
const posthog = usePostHog();
127130

128131
const [polling, dispatch] = useReducer(pollingReducer, {
129132
routerTimedOut: false,
@@ -212,6 +215,12 @@ export const Step3 = () => {
212215
email: Boolean(prev?.email),
213216
}));
214217

218+
captureOnboardingEvent(posthog, {
219+
name: 'onboarding_completed',
220+
options: {
221+
step_name: 'run_router_send_metrics',
222+
},
223+
});
215224
setIsFinished(true);
216225
},
217226
onError: (error) => {
@@ -358,7 +367,15 @@ export const Step3 = () => {
358367

359368
<OnboardingNavigation
360369
className="pt-2"
361-
onSkip={setSkipped}
370+
onSkip={() => {
371+
captureOnboardingEvent(posthog, {
372+
name: 'onboarding_skipped',
373+
options: {
374+
step_name: 'run_router_send_metrics',
375+
},
376+
});
377+
setSkipped();
378+
}}
362379
backHref="/onboarding/2"
363380
forward={{
364381
onClick: () => mutate({}),

0 commit comments

Comments
 (0)