Skip to content

Commit 665abcf

Browse files
authored
fix: Skip asking if users have sentry account if --org and --project flags were passed (#817)
If users pass in `--org` and `--project` slugs, there's no need to ask them if they already created a sentry account, as they otherwise wouldn't even have any of this information.
1 parent 9a83cfb commit 665abcf

File tree

3 files changed

+130
-43
lines changed

3 files changed

+130
-43
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Unreleased
44

55
- ref: Set wizard version at build time ([#827](https://github.com/getsentry/sentry-wizard/pull/827))
6+
- fix: Skip asking if users have sentry account if `--org` and `--project` flags were passed ([#817](https://github.com/getsentry/sentry-wizard/pull/817))
67

78
## 4.0.1
89

src/utils/clack-utils.ts

+64-43
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,10 @@ async function askForSelfHosted(
10241024
return { url: validUrl, selfHosted: true };
10251025
}
10261026

1027-
async function askForWizardLogin(options: {
1027+
/**
1028+
* Exported for testing
1029+
*/
1030+
export async function askForWizardLogin(options: {
10281031
url: string;
10291032
promoCode?: string;
10301033
platform?:
@@ -1039,63 +1042,39 @@ async function askForWizardLogin(options: {
10391042
orgSlug?: string;
10401043
projectSlug?: string;
10411044
}): Promise<WizardProjectData> {
1042-
Sentry.setTag('has-promo-code', !!options.promoCode);
1045+
const { orgSlug, projectSlug, url, platform, promoCode } = options;
10431046

1044-
let hasSentryAccount = await clack.confirm({
1045-
message: 'Do you already have a Sentry account?',
1046-
});
1047+
Sentry.setTag('has-promo-code', !!promoCode);
10471048

1048-
hasSentryAccount = await abortIfCancelled(hasSentryAccount);
1049+
const projectAndOrgPreselected = !!(orgSlug && projectSlug);
1050+
1051+
const hasSentryAccount =
1052+
projectAndOrgPreselected || (await askHasSentryAccount());
10491053

10501054
Sentry.setTag('already-has-sentry-account', hasSentryAccount);
10511055

1052-
let wizardHash: string;
1053-
try {
1054-
wizardHash = (
1055-
await axios.get<{ hash: string }>(`${options.url}api/0/wizard/`)
1056-
).data.hash;
1057-
} catch (e: unknown) {
1058-
if (options.url !== SAAS_URL) {
1059-
clack.log.error('Loading Wizard failed. Did you provide the right URL?');
1060-
clack.log.info(JSON.stringify(e, null, 2));
1061-
await abort(
1062-
chalk.red(
1063-
'Please check your configuration and try again.\n\n Let us know if you think this is an issue with the wizard or Sentry: https://github.com/getsentry/sentry-wizard/issues',
1064-
),
1065-
);
1066-
} else {
1067-
clack.log.error('Loading Wizard failed.');
1068-
clack.log.info(JSON.stringify(e, null, 2));
1069-
await abort(
1070-
chalk.red(
1071-
'Please try again in a few minutes and let us know if this issue persists: https://github.com/getsentry/sentry-wizard/issues',
1072-
),
1073-
);
1074-
}
1075-
}
1056+
const wizardHash = await makeInitialWizardHashRequest(url);
10761057

1077-
const loginUrl = new URL(
1078-
`${options.url}account/settings/wizard/${wizardHash!}/`,
1079-
);
1058+
const loginUrl = new URL(`${url}account/settings/wizard/${wizardHash}/`);
10801059

1081-
if (options.orgSlug) {
1082-
loginUrl.searchParams.set('org_slug', options.orgSlug);
1060+
if (orgSlug) {
1061+
loginUrl.searchParams.set('org_slug', orgSlug);
10831062
}
10841063

1085-
if (options.projectSlug) {
1086-
loginUrl.searchParams.set('project_slug', options.projectSlug);
1064+
if (projectSlug) {
1065+
loginUrl.searchParams.set('project_slug', projectSlug);
10871066
}
10881067

10891068
if (!hasSentryAccount) {
10901069
loginUrl.searchParams.set('signup', '1');
10911070
}
10921071

1093-
if (options.platform) {
1094-
loginUrl.searchParams.set('project_platform', options.platform);
1072+
if (platform) {
1073+
loginUrl.searchParams.set('project_platform', platform);
10951074
}
10961075

1097-
if (options.promoCode) {
1098-
loginUrl.searchParams.set('code', options.promoCode);
1076+
if (promoCode) {
1077+
loginUrl.searchParams.set('code', promoCode);
10991078
}
11001079

11011080
const urlToOpen = loginUrl.toString();
@@ -1118,7 +1097,7 @@ async function askForWizardLogin(options: {
11181097
const data = await new Promise<WizardProjectData>((resolve) => {
11191098
const pollingInterval = setInterval(() => {
11201099
axios
1121-
.get<WizardProjectData>(`${options.url}api/0/wizard/${wizardHash}/`, {
1100+
.get<WizardProjectData>(`${url}api/0/wizard/${wizardHash}/`, {
11221101
headers: {
11231102
'Accept-Encoding': 'deflate',
11241103
},
@@ -1127,7 +1106,7 @@ async function askForWizardLogin(options: {
11271106
resolve(result.data);
11281107
clearTimeout(timeout);
11291108
clearInterval(pollingInterval);
1130-
void axios.delete(`${options.url}api/0/wizard/${wizardHash}/`);
1109+
void axios.delete(`${url}api/0/wizard/${wizardHash}/`);
11311110
})
11321111
.catch(() => {
11331112
// noop - just try again
@@ -1151,6 +1130,48 @@ async function askForWizardLogin(options: {
11511130
return data;
11521131
}
11531132

1133+
/**
1134+
* This first request to Sentry creates a cache on the Sentry backend whose key is returned.
1135+
* We use this key later on to poll for the actual project data.
1136+
*/
1137+
async function makeInitialWizardHashRequest(url: string): Promise<string> {
1138+
const reqUrl = `${url}api/0/wizard/`;
1139+
try {
1140+
return (await axios.get<{ hash: string }>(reqUrl)).data.hash;
1141+
} catch (e: unknown) {
1142+
if (url !== SAAS_URL) {
1143+
clack.log.error(
1144+
`Loading Wizard failed. Did you provide the right URL? (url: ${reqUrl})`,
1145+
);
1146+
clack.log.info(JSON.stringify(e, null, 2));
1147+
await abort(
1148+
chalk.red(
1149+
'Please check your configuration and try again.\n\n Let us know if you think this is an issue with the wizard or Sentry: https://github.com/getsentry/sentry-wizard/issues',
1150+
),
1151+
);
1152+
} else {
1153+
clack.log.error('Loading Wizard failed.');
1154+
clack.log.info(JSON.stringify(e, null, 2));
1155+
await abort(
1156+
chalk.red(
1157+
'Please try again in a few minutes and let us know if this issue persists: https://github.com/getsentry/sentry-wizard/issues',
1158+
),
1159+
);
1160+
}
1161+
}
1162+
1163+
// We don't get here as we abort in an error case but TS doesn't know that
1164+
return 'invalid hash';
1165+
}
1166+
1167+
async function askHasSentryAccount(): Promise<boolean> {
1168+
const hasSentryAccount = await clack.confirm({
1169+
message: 'Do you already have a Sentry account?',
1170+
});
1171+
1172+
return abortIfCancelled(hasSentryAccount);
1173+
}
1174+
11541175
async function askForProjectSelection(
11551176
projects: SentryProjectData[],
11561177
orgSlug?: string,

test/utils/clack-utils.test.ts

+65
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
askForToolConfigPath,
3+
askForWizardLogin,
34
createNewConfigFile,
45
installPackage,
56
} from '../../src/utils/clack-utils';
@@ -8,6 +9,8 @@ import * as fs from 'node:fs';
89
import * as ChildProcess from 'node:child_process';
910
import type { PackageManager } from '../../src/utils/package-manager';
1011

12+
import axios from 'axios';
13+
1114
// @ts-ignore - clack is ESM and TS complains about that. It works though
1215
import * as clack from '@clack/prompts';
1316

@@ -21,7 +24,9 @@ jest.mock('@clack/prompts', () => ({
2124
info: jest.fn(),
2225
success: jest.fn(),
2326
warn: jest.fn(),
27+
error: jest.fn(),
2428
},
29+
outro: jest.fn(),
2530
text: jest.fn(),
2631
confirm: jest.fn(),
2732
cancel: jest.fn(),
@@ -31,6 +36,12 @@ jest.mock('@clack/prompts', () => ({
3136
.fn()
3237
.mockImplementation(() => ({ start: jest.fn(), stop: jest.fn() })),
3338
}));
39+
const clackMock = clack as jest.Mocked<typeof clack>;
40+
41+
jest.mock('axios');
42+
const mockedAxios = axios as jest.Mocked<typeof axios>;
43+
44+
jest.mock('opn', () => jest.fn(() => Promise.resolve({ on: jest.fn() })));
3445

3546
function mockUserResponse(fn: jest.Mock, response: any) {
3647
fn.mockReturnValueOnce(response);
@@ -222,3 +233,57 @@ describe('installPackage', () => {
222233
},
223234
);
224235
});
236+
237+
describe('askForWizardLogin', () => {
238+
// mock axios
239+
afterEach(() => {
240+
// clackMock.confirm.mockClear();
241+
// mockUserResponse(clack.confirm as jest.Mock, undefined);
242+
});
243+
244+
beforeEach(() => {
245+
jest.clearAllMocks();
246+
mockedAxios.get.mockClear();
247+
clackMock.confirm.mockClear();
248+
clackMock.confirm.mockReset();
249+
mockUserResponse(clack.confirm as jest.Mock, undefined);
250+
});
251+
252+
it('asks if a user already has a Sentry account by default', async () => {
253+
mockUserResponse(clack.confirm as jest.Mock, Promise.resolve(true));
254+
255+
// Provide the data object to be returned
256+
mockedAxios.get.mockResolvedValue({
257+
data: {
258+
hash: 'mockedHash',
259+
},
260+
});
261+
262+
await askForWizardLogin({ url: 'https://santry.io/' });
263+
264+
expect(clack.confirm).toHaveBeenCalledWith(
265+
expect.objectContaining({
266+
message: expect.stringContaining('already have a Sentry account'),
267+
}),
268+
);
269+
});
270+
271+
it('skips asking for if a user already has a Sentry account if org and project are pre-selected', async () => {
272+
mockUserResponse(clackMock.confirm as jest.Mock, Promise.resolve(true));
273+
274+
// Provide the data object to be returned
275+
mockedAxios.get.mockResolvedValue({
276+
data: {
277+
hash: 'mockedHash',
278+
},
279+
});
280+
281+
await askForWizardLogin({
282+
url: 'https://santry.io/',
283+
orgSlug: 'my-org',
284+
projectSlug: 'my-project',
285+
});
286+
287+
expect(clack.confirm).not.toHaveBeenCalled();
288+
});
289+
});

0 commit comments

Comments
 (0)