Skip to content

Commit 089708c

Browse files
committed
feat: event tracking errors
1 parent 99eb2f9 commit 089708c

File tree

6 files changed

+252
-28
lines changed

6 files changed

+252
-28
lines changed

cli/src/commands/demo/command.ts

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
checkExistingOnboarding,
1616
} from './api.js';
1717
import {
18+
captureOnboardingEvent,
1819
checkDockerReadiness,
1920
clearScreen,
2021
getDemoLogPath,
@@ -283,7 +284,16 @@ async function handleStep3(
283284
// Delete existing token first (idempotent — no error if missing)
284285
const deleteResult = await deleteRouterToken(tokenParams);
285286
if (deleteResult.error) {
286-
console.error(`Failed to clean up existing router token: ${deleteResult.error.message}`);
287+
const errorText = `Failed to clean up existing router token: ${deleteResult.error.message}`;
288+
console.error(errorText);
289+
captureOnboardingEvent({
290+
name: 'onboarding_step_failed',
291+
properties: {
292+
step_name: 'run_router_send_metrics',
293+
error_category: 'router',
294+
error_message: errorText,
295+
},
296+
});
287297
await waitForKeyPress({ r: retryFn, R: retryFn }, 'Hit [r] to retry. CTRL+C to quit.');
288298
return;
289299
}
@@ -292,7 +302,16 @@ async function handleStep3(
292302
const createResult = await createRouterToken(tokenParams);
293303

294304
if (createResult.error) {
295-
spinner.fail(`Failed to generate router token: ${createResult.error.message}`);
305+
const failText = `Failed to generate router token: ${createResult.error.message}`;
306+
spinner.fail(failText);
307+
captureOnboardingEvent({
308+
name: 'onboarding_step_failed',
309+
properties: {
310+
step_name: 'run_router_send_metrics',
311+
error_category: 'router',
312+
error_message: failText,
313+
},
314+
});
296315
await waitForKeyPress({ r: retryFn, R: retryFn }, 'Hit [r] to retry. CTRL+C to quit.');
297316
return;
298317
}
@@ -331,7 +350,16 @@ async function handleStep3(
331350

332351
firedQueries++;
333352
} catch (err) {
334-
querySpinner.fail(`Sample query failed: ${err instanceof Error ? err.message : String(err)}`);
353+
const failText = `Sample query failed: ${err instanceof Error ? err.message : String(err)}`;
354+
captureOnboardingEvent({
355+
name: 'onboarding_step_failed',
356+
properties: {
357+
step_name: 'run_router_send_metrics',
358+
error_category: 'router',
359+
error_message: failText,
360+
},
361+
});
362+
querySpinner.fail(failText);
335363
}
336364
showQueryPrompt();
337365
}
@@ -352,7 +380,16 @@ async function handleStep3(
352380
});
353381

354382
if (routerResult.error) {
355-
console.error(`\nRouter exited with error: ${routerResult.error.message}`);
383+
const errorText = `Router exited with error: ${routerResult.error.message}`;
384+
console.error(`\n${errorText}`);
385+
captureOnboardingEvent({
386+
name: 'onboarding_step_failed',
387+
properties: {
388+
step_name: 'run_router_send_metrics',
389+
error_category: 'router',
390+
error_message: errorText,
391+
},
392+
});
356393
await waitForKeyPress({ r: retryFn, R: retryFn }, 'Hit [r] to retry. CTRL+C to quit.');
357394
}
358395
}
@@ -369,14 +406,33 @@ async function handleGetOnboardingResponse(client: BaseCommandOptions['client'],
369406
return onboardingCheck.onboarding;
370407
}
371408
case 'not-allowed': {
372-
program.error('Only organization owners can trigger onboarding.');
409+
const errorText = 'Only organization owners can trigger onboarding.';
410+
captureOnboardingEvent({
411+
name: 'onboarding_step_failed',
412+
properties: {
413+
step_name: 'check_onboarding',
414+
error_category: 'resource',
415+
error_message: errorText,
416+
},
417+
});
418+
program.error(errorText);
373419

374420
break;
375421
}
376422
case 'error': {
377-
console.error('An issue occured while fetching the onboarding status');
423+
const errorText = 'An issue occured while fetching the onboarding status';
424+
console.error(errorText);
378425
console.error(onboardingCheck.error);
379426

427+
captureOnboardingEvent({
428+
name: 'onboarding_step_failed',
429+
properties: {
430+
step_name: 'check_onboarding',
431+
error_category: 'resource',
432+
error_message: `${errorText}\n${onboardingCheck.error}`,
433+
},
434+
});
435+
380436
await waitForKeyPress({ Enter: retryFn }, 'Hit Enter to retry. CTRL+C to quit.');
381437
break;
382438
}
@@ -396,6 +452,14 @@ async function getUserInfo(client: BaseCommandOptions['client']) {
396452

397453
if (error) {
398454
spinner.fail(error.message);
455+
captureOnboardingEvent({
456+
name: 'onboarding_step_failed',
457+
properties: {
458+
step_name: 'init',
459+
error_category: 'resource',
460+
error_message: error.message,
461+
},
462+
});
399463
program.error(error.message);
400464
}
401465

cli/src/commands/demo/util.ts

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
22
import { createWriteStream, existsSync, mkdirSync, type WriteStream } from 'node:fs';
33
import path from 'node:path';
44
import { program } from 'commander';
5+
import { PostHog } from 'posthog-node';
56
import { execa, type ResultPromise } from 'execa';
67
import ora from 'ora';
78
import pc from 'picocolors';
@@ -138,13 +139,31 @@ export async function prepareSupportingData() {
138139
);
139140
if (!treeResponse.ok) {
140141
spinner.fail('Failed to fetch repository tree.');
141-
program.error(`GitHub API error: ${treeResponse.statusText}`);
142+
const errorText = `GitHub API error: ${treeResponse.statusText}`;
143+
captureOnboardingEvent({
144+
name: 'onboarding_step_failed',
145+
properties: {
146+
step_name: 'init',
147+
error_category: 'support_files',
148+
error_message: errorText,
149+
},
150+
});
151+
program.error(errorText);
142152
}
143153

144154
const parsed = GitHubTreeSchema.safeParse(await treeResponse.json());
145155
if (!parsed.success) {
146156
spinner.fail('Failed to parse repository tree.');
147-
program.error('Unexpected response format from GitHub API. The repository structure may have changed.');
157+
const errorText = 'Unexpected response format from GitHub API. The repository structure may have changed.';
158+
captureOnboardingEvent({
159+
name: 'onboarding_step_failed',
160+
properties: {
161+
step_name: 'init',
162+
error_category: 'support_files',
163+
error_message: errorText,
164+
},
165+
});
166+
program.error(errorText);
148167
}
149168

150169
const files = parsed.data.tree.filter((entry) => entry.type === 'blob' && entry.path.startsWith('plugins/'));
@@ -165,15 +184,28 @@ export async function prepareSupportingData() {
165184

166185
return { path: file.path, error: null };
167186
} catch (err) {
168-
return { path: file.path, error: err instanceof Error ? err.message : String(err) };
187+
return {
188+
path: file.path,
189+
error: err instanceof Error ? err.message : String(err),
190+
};
169191
}
170192
}),
171193
);
172194

173195
const failed = results.filter((r) => r.error !== null);
174196
if (failed.length > 0) {
175-
spinner.fail(`Failed to fetch some files from onboarding repository or store them in ${cosmoDir}.`);
176-
program.error(failed.map((f) => ` ${f.path}: ${f.error}`).join('\n'));
197+
const failText = `Failed to fetch some files from onboarding repository or store them in ${cosmoDir}.`;
198+
const errorText = failed.map((f) => ` ${f.path}: ${f.error}`).join('\n');
199+
captureOnboardingEvent({
200+
name: 'onboarding_step_failed',
201+
properties: {
202+
step_name: 'init',
203+
error_category: 'support_files',
204+
error_message: `${failText}\n${errorText}`,
205+
},
206+
});
207+
spinner.fail(failText);
208+
program.error(errorText);
177209
}
178210

179211
spinner.succeed(`Support files copied to ${pc.bold(cosmoDir)}`);
@@ -227,14 +259,32 @@ export async function checkDockerReadiness(): Promise<void> {
227259
const spinner = demoSpinner('Checking Docker availability…').start();
228260

229261
if (!(await isDockerAvailable())) {
230-
spinner.fail('Docker is not available.');
262+
const failText = 'Docker is not available.';
263+
captureOnboardingEvent({
264+
name: 'onboarding_step_failed',
265+
properties: {
266+
step_name: 'init',
267+
error_category: 'docker_readiness',
268+
error_message: failText,
269+
},
270+
});
271+
spinner.fail(failText);
231272
program.error(
232273
`Docker CLI is not installed or the daemon is not running.\nInstall Docker: ${pc.underline('https://docs.docker.com/get-docker/')}`,
233274
);
234275
}
235276

236277
if (!(await isBuildxAvailable())) {
237-
spinner.fail('Docker Buildx is not available.');
278+
const failText = 'Docker Buildx is not available.';
279+
captureOnboardingEvent({
280+
name: 'onboarding_step_failed',
281+
properties: {
282+
step_name: 'init',
283+
error_category: 'docker_readiness',
284+
error_message: failText,
285+
},
286+
});
287+
spinner.fail(failText);
238288
program.error(
239289
`Docker Buildx plugin is required for multi-platform builds.\nSee: ${pc.underline('https://docs.docker.com/build/install-buildx/')}`,
240290
);
@@ -249,9 +299,19 @@ export async function checkDockerReadiness(): Promise<void> {
249299
try {
250300
await createDockerContainerBuilder(config.dockerBuilderName);
251301
} catch (err) {
252-
spinner.fail(`Failed to create buildx builder "${config.dockerBuilderName}".`);
302+
const failText = `Failed to create buildx builder "${config.dockerBuilderName}".`;
303+
const errorText = err instanceof Error ? err.message : String(err);
304+
spinner.fail(failText);
305+
captureOnboardingEvent({
306+
name: 'onboarding_step_failed',
307+
properties: {
308+
step_name: 'init',
309+
error_category: 'docker_readiness',
310+
error_message: `${failText}\n${errorText}`,
311+
},
312+
});
253313
program.error(
254-
`Could not create a docker-container buildx builder: ${err instanceof Error ? err.message : String(err)}\nYou can create one manually: docker buildx create --use --driver docker-container --name ${config.dockerBuilderName}`,
314+
`Could not create a docker-container buildx builder: ${errorText}\nYou can create one manually: docker buildx create --use --driver docker-container --name ${config.dockerBuilderName}`,
255315
);
256316
}
257317

@@ -492,11 +552,20 @@ export async function publishAllPlugins({
492552
export function captureOnboardingEvent({
493553
name,
494554
properties,
495-
}: {
496-
name: 'onboarding_step_completed';
497-
properties: {
498-
step_name: 'init' | 'check_onboarding' | 'create_federated_graph' | 'run_router_send_metrics';
499-
};
500-
}): void {
555+
}:
556+
| {
557+
name: 'onboarding_step_completed';
558+
properties: {
559+
step_name: 'init' | 'check_onboarding' | 'create_federated_graph' | 'run_router_send_metrics';
560+
};
561+
}
562+
| {
563+
name: 'onboarding_step_failed';
564+
properties: {
565+
step_name: 'init' | 'check_onboarding' | 'create_federated_graph' | 'run_router_send_metrics';
566+
error_category: 'resource' | 'support_files' | 'docker_readiness' | 'router';
567+
error_message: string;
568+
};
569+
}): void {
501570
capture(name, properties);
502571
}

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

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,19 @@ export const Step1 = () => {
6262
const { mutate, isPending } = useMutation(createOnboarding, {
6363
onSuccess: (d) => {
6464
if (d.response?.code !== EnumStatusCode.OK) {
65+
const description = d.response?.details ?? 'We had issues with storing your data. Please try again.';
6566
toast({
66-
description: d.response?.details ?? 'We had issues with storing your data. Please try again.',
67+
description,
6768
duration: 3000,
6869
});
70+
captureOnboardingEvent(posthog, {
71+
name: 'onboarding_step_failed',
72+
options: {
73+
step_name: 'welcome',
74+
error_category: 'resource',
75+
error_message: description,
76+
},
77+
});
6978
return;
7079
}
7180

@@ -85,10 +94,19 @@ export const Step1 = () => {
8594
router.push('/onboarding/2');
8695
},
8796
onError: (error) => {
97+
const description = error.details.toString() ?? 'We had issues with storing your data. Please try again.';
8898
toast({
89-
description: error.details.toString() ?? 'We had issues with storing your data. Please try again.',
99+
description,
90100
duration: 3000,
91101
});
102+
captureOnboardingEvent(posthog, {
103+
name: 'onboarding_step_failed',
104+
options: {
105+
step_name: 'welcome',
106+
error_category: 'resource',
107+
error_message: description,
108+
},
109+
});
92110
},
93111
});
94112

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,19 @@ export const Step2 = () => {
141141

142142
const status = getDemoGraphStatus({ data, isPolling: polling.active, isError });
143143

144+
useEffect(() => {
145+
if (status !== 'fail' && status !== 'error') return;
146+
147+
captureOnboardingEvent(posthog, {
148+
name: 'onboarding_step_failed',
149+
options: {
150+
step_name: 'create_graph',
151+
error_category: 'resource',
152+
error_message: status === 'error' ? 'Failed to fetch federated graph data' : 'Demo federated graph not created',
153+
},
154+
});
155+
}, [status, posthog]);
156+
144157
return (
145158
<OnboardingContainer>
146159
<div className="mt-4 flex w-full flex-col gap-6 text-left">

0 commit comments

Comments
 (0)