Skip to content

Commit

Permalink
telemetry: Set transaction status based on status code when aborting (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Lms24 authored Mar 3, 2025
1 parent f89b4f8 commit e1e4d90
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/utils/clack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ export async function abort(message?: string, status?: number): Promise<never> {
clack.outro(message ?? 'Wizard setup cancelled.');
const sentryHub = Sentry.getCurrentHub();
const sentryTransaction = sentryHub.getScope().getTransaction();
sentryTransaction?.setStatus('aborted');
// 'cancelled' doesn't increase the `failureRate()` shown in the Sentry UI
// 'aborted' increases the failure rate
// see: https://docs.sentry.io/product/insights/overview/metrics/#failure-rate
sentryTransaction?.setStatus(status === 0 ? 'cancelled' : 'aborted');
sentryTransaction?.finish();
const sentrySession = sentryHub.getScope().getSession();
if (sentrySession) {
Expand Down
70 changes: 70 additions & 0 deletions test/utils/clack-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
abort,
askForToolConfigPath,
askForWizardLogin,
createNewConfigFile,
Expand All @@ -14,6 +15,8 @@ import axios from 'axios';
// @ts-ignore - clack is ESM and TS complains about that. It works though
import * as clack from '@clack/prompts';

import * as Sentry from '@sentry/node';

jest.mock('node:child_process', () => ({
__esModule: true,
...jest.requireActual('node:child_process'),
Expand Down Expand Up @@ -287,3 +290,70 @@ describe('askForWizardLogin', () => {
expect(clack.confirm).not.toHaveBeenCalled();
});
});

describe('abort', () => {
const sentryTxn = {
setStatus: jest.fn(),
finish: jest.fn(),
};

let sentrySession = {
status: 999,
};

beforeEach(() => {
jest.clearAllMocks();
sentrySession = {
status: 999,
};
});

jest.spyOn(Sentry, 'getCurrentHub').mockReturnValue({
getScope: () => ({
// @ts-expect-error - don't care about the rest of the required props value
getTransaction: () => sentryTxn,
// @ts-expect-error - don't care about the rest of the required props value
getSession: () => sentrySession,
}),
captureSession: jest.fn(),
});

const flushSpy = jest.fn();
jest.spyOn(Sentry, 'flush').mockImplementation(flushSpy);

it('ends the process with an error exit code by default', async () => {
// @ts-ignore - jest doesn't like the empty function
// eslint-disable-next-line @typescript-eslint/no-empty-function
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});

await abort();

expect(exitSpy).toHaveBeenCalledWith(1);

expect(clackMock.outro).toHaveBeenCalledTimes(1);
expect(clackMock.outro).toHaveBeenCalledWith('Wizard setup cancelled.');

expect(sentryTxn.setStatus).toHaveBeenLastCalledWith('aborted');
expect(sentryTxn.finish).toHaveBeenCalledTimes(1);
expect(sentrySession.status).toBe('crashed');
expect(flushSpy).toHaveBeenLastCalledWith(3000);
});

it('ends the process with a custom exit code and message if provided', async () => {
// @ts-ignore - jest doesn't like the empty function
// eslint-disable-next-line @typescript-eslint/no-empty-function
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {});

await abort('Bye', 0);

expect(exitSpy).toHaveBeenCalledWith(0);

expect(clackMock.outro).toHaveBeenCalledTimes(1);
expect(clackMock.outro).toHaveBeenCalledWith('Bye');

expect(sentryTxn.setStatus).toHaveBeenLastCalledWith('cancelled');
expect(sentryTxn.finish).toHaveBeenCalledTimes(1);
expect(sentrySession.status).toBe('abnormal');
expect(flushSpy).toHaveBeenLastCalledWith(3000);
});
});

0 comments on commit e1e4d90

Please sign in to comment.