Skip to content

Commit f1a46cc

Browse files
mydeaschew2381
authored andcommitted
feat(settings): Ensure JS Loader validates SDK version & products (#46604)
1 parent 30686f8 commit f1a46cc

File tree

2 files changed

+235
-6
lines changed

2 files changed

+235
-6
lines changed

static/app/views/settings/project/projectKeys/details/keySettings.spec.tsx

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ const dynamicSdkLoaderOptions = {
1616
[DynamicSDKLoaderOption.HAS_DEBUG]: false,
1717
};
1818

19+
const fullDynamicSdkLoaderOptions = {
20+
[DynamicSDKLoaderOption.HAS_PERFORMANCE]: true,
21+
[DynamicSDKLoaderOption.HAS_REPLAY]: true,
22+
[DynamicSDKLoaderOption.HAS_DEBUG]: true,
23+
};
24+
1925
function renderMockRequests(
2026
organizationSlug: Organization['slug'],
2127
projectSlug: Project['slug'],
@@ -120,5 +126,170 @@ describe('Key Settings', function () {
120126
);
121127
});
122128
});
129+
130+
it('resets performance & replay when selecting SDK version <7', async function () {
131+
const params = {
132+
projectId: '1',
133+
keyId: '1',
134+
};
135+
136+
const {organization} = initializeOrg({
137+
...initializeOrg(),
138+
organization: {
139+
...initializeOrg().organization,
140+
features: ORG_FEATURES,
141+
},
142+
router: {
143+
params,
144+
},
145+
});
146+
147+
const data = {
148+
...(TestStubs.ProjectKeys()[0] as ProjectKey),
149+
dynamicSdkLoaderOptions: fullDynamicSdkLoaderOptions,
150+
} as ProjectKey;
151+
152+
const mockRequests = renderMockRequests(
153+
organization.slug,
154+
params.projectId,
155+
params.keyId
156+
);
157+
158+
render(
159+
<KeySettings
160+
data={data}
161+
onRemove={jest.fn()}
162+
organization={organization}
163+
params={params}
164+
/>
165+
);
166+
167+
// Update SDK version - should reset performance & replay
168+
await selectEvent.select(screen.getByText('latest'), '6.x');
169+
170+
await waitFor(() => {
171+
expect(mockRequests.projectKeys).toHaveBeenCalledWith(
172+
`/projects/${organization.slug}/${params.projectId}/keys/${params.keyId}/`,
173+
expect.objectContaining({
174+
data: {
175+
browserSdkVersion: '6.x',
176+
dynamicSdkLoaderOptions: {
177+
...fullDynamicSdkLoaderOptions,
178+
[DynamicSDKLoaderOption.HAS_PERFORMANCE]: false,
179+
[DynamicSDKLoaderOption.HAS_REPLAY]: false,
180+
[DynamicSDKLoaderOption.HAS_DEBUG]: true,
181+
},
182+
},
183+
})
184+
);
185+
});
186+
});
187+
188+
it('disabled performance & replay when SDK version <7 is selected', function () {
189+
const params = {
190+
projectId: '1',
191+
keyId: '1',
192+
};
193+
194+
const {organization} = initializeOrg({
195+
...initializeOrg(),
196+
organization: {
197+
...initializeOrg().organization,
198+
features: ORG_FEATURES,
199+
},
200+
router: {
201+
params,
202+
},
203+
});
204+
205+
const data = {
206+
...(TestStubs.ProjectKeys()[0] as ProjectKey),
207+
dynamicSdkLoaderOptions: {
208+
[DynamicSDKLoaderOption.HAS_PERFORMANCE]: false,
209+
[DynamicSDKLoaderOption.HAS_REPLAY]: false,
210+
[DynamicSDKLoaderOption.HAS_DEBUG]: true,
211+
},
212+
browserSdkVersion: '6.x',
213+
} as ProjectKey;
214+
215+
render(
216+
<KeySettings
217+
data={data}
218+
onRemove={jest.fn()}
219+
organization={organization}
220+
params={params}
221+
/>
222+
);
223+
224+
for (const key of Object.keys(sdkLoaderOptions)) {
225+
const toggle = screen.getByRole('checkbox', {name: sdkLoaderOptions[key].label});
226+
227+
if (key === DynamicSDKLoaderOption.HAS_DEBUG) {
228+
expect(toggle).toBeEnabled();
229+
expect(toggle).toBeChecked();
230+
} else {
231+
expect(toggle).toBeDisabled();
232+
expect(toggle).not.toBeChecked();
233+
}
234+
}
235+
236+
const infos = screen.getAllByText('Only available in SDK version 7.x and above');
237+
expect(infos.length).toBe(2);
238+
});
239+
240+
it('shows replay message when it is enabled', function () {
241+
const params = {
242+
projectId: '1',
243+
keyId: '1',
244+
};
245+
246+
const {organization} = initializeOrg({
247+
...initializeOrg(),
248+
organization: {
249+
...initializeOrg().organization,
250+
features: ORG_FEATURES,
251+
},
252+
router: {
253+
params,
254+
},
255+
});
256+
257+
const data = {
258+
...(TestStubs.ProjectKeys()[0] as ProjectKey),
259+
dynamicSdkLoaderOptions: fullDynamicSdkLoaderOptions,
260+
} as ProjectKey;
261+
262+
const {rerender} = render(
263+
<KeySettings
264+
data={data}
265+
onRemove={jest.fn()}
266+
organization={organization}
267+
params={params}
268+
/>
269+
);
270+
271+
expect(
272+
screen.queryByText(
273+
'When using Replay, the loader will load the ES6 bundle instead of the ES5 bundle.'
274+
)
275+
).toBeInTheDocument();
276+
277+
data.dynamicSdkLoaderOptions.hasReplay = false;
278+
279+
rerender(
280+
<KeySettings
281+
data={data}
282+
onRemove={jest.fn()}
283+
organization={organization}
284+
params={params}
285+
/>
286+
);
287+
288+
expect(
289+
screen.queryByText(
290+
'When using Replay, the loader will load the ES6 bundle instead of the ES5 bundle.'
291+
)
292+
).not.toBeInTheDocument();
293+
});
123294
});
124295
});

static/app/views/settings/project/projectKeys/details/keySettings.tsx

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,15 @@ export enum DynamicSDKLoaderOption {
5151
export const sdkLoaderOptions = {
5252
[DynamicSDKLoaderOption.HAS_PERFORMANCE]: {
5353
label: t('Enable Performance Monitoring'),
54+
requiresV7: true,
5455
},
5556
[DynamicSDKLoaderOption.HAS_REPLAY]: {
5657
label: t('Enable Session Replay'),
58+
requiresV7: true,
5759
},
5860
[DynamicSDKLoaderOption.HAS_DEBUG]: {
5961
label: t('Enable Debug Bundles'),
62+
requiresV7: false,
6063
},
6164
};
6265

@@ -145,24 +148,55 @@ export function KeySettings({onRemove, organization, params, data}: Props) {
145148
async (newBrowserSDKVersion: typeof browserSdkVersion) => {
146149
addLoadingMessage();
147150

151+
const apiData: {
152+
browserSdkVersion: typeof browserSdkVersion;
153+
dynamicSdkLoaderOptions?: Partial<Record<DynamicSDKLoaderOption, boolean>>;
154+
} = {
155+
browserSdkVersion: newBrowserSDKVersion,
156+
};
157+
158+
const shouldRestrictDynamicSdkLoaderOptions =
159+
hasJSSDKDynamicLoaderFeatureFlag &&
160+
!sdkVersionSupportsPerformanceAndReplay(newBrowserSDKVersion);
161+
162+
if (shouldRestrictDynamicSdkLoaderOptions) {
163+
// Performance & Replay are not supported before 7.x
164+
const newDynamicSdkLoaderOptions = {
165+
...dynamicSDKLoaderOptions,
166+
hasPerformance: false,
167+
hasReplay: false,
168+
};
169+
170+
apiData.dynamicSdkLoaderOptions = newDynamicSdkLoaderOptions;
171+
}
172+
148173
try {
149174
const response = await api.requestPromise(apiEndpoint, {
150175
method: 'PUT',
151-
data: {
152-
browserSdkVersion: newBrowserSDKVersion,
153-
},
176+
data: apiData,
154177
});
155178

156179
setBrowserSdkVersion(response.browserSdkVersion);
157180

181+
if (shouldRestrictDynamicSdkLoaderOptions) {
182+
setDynamicSDKLoaderOptions(response.dynamicSdkLoaderOptions);
183+
}
184+
158185
addSuccessMessage(t('Successfully updated SDK version'));
159186
} catch (error) {
160187
const message = t('Unable to updated SDK version');
161188
handleXhrErrorResponse(message)(error);
162189
addErrorMessage(message);
163190
}
164191
},
165-
[api, apiEndpoint, setBrowserSdkVersion]
192+
[
193+
api,
194+
apiEndpoint,
195+
setBrowserSdkVersion,
196+
setDynamicSDKLoaderOptions,
197+
hasJSSDKDynamicLoaderFeatureFlag,
198+
dynamicSDKLoaderOptions,
199+
]
166200
);
167201

168202
return (
@@ -267,14 +301,34 @@ export function KeySettings({onRemove, organization, params, data}: Props) {
267301
label={value.label}
268302
key={key}
269303
name={key}
270-
value={dynamicSDKLoaderOptions[sdkLoaderOption]}
304+
value={
305+
value.requiresV7 &&
306+
!sdkVersionSupportsPerformanceAndReplay(browserSdkVersion)
307+
? false
308+
: dynamicSDKLoaderOptions[sdkLoaderOption]
309+
}
271310
onChange={() =>
272311
handleToggleDynamicSDKLoaderOption(
273312
sdkLoaderOption as DynamicSDKLoaderOption,
274313
!dynamicSDKLoaderOptions[sdkLoaderOption]
275314
)
276315
}
277-
disabled={!hasAccess}
316+
disabled={
317+
!hasAccess ||
318+
(value.requiresV7 &&
319+
!sdkVersionSupportsPerformanceAndReplay(browserSdkVersion))
320+
}
321+
help={
322+
value.requiresV7 &&
323+
!sdkVersionSupportsPerformanceAndReplay(browserSdkVersion)
324+
? t('Only available in SDK version 7.x and above')
325+
: key === DynamicSDKLoaderOption.HAS_REPLAY &&
326+
dynamicSDKLoaderOptions[sdkLoaderOption]
327+
? t(
328+
'When using Replay, the loader will load the ES6 bundle instead of the ES5 bundle.'
329+
)
330+
: undefined
331+
}
278332
disabledReason={
279333
!hasAccess
280334
? t('You do not have permission to edit this setting')
@@ -337,3 +391,7 @@ export function KeySettings({onRemove, organization, params, data}: Props) {
337391
</Access>
338392
);
339393
}
394+
395+
function sdkVersionSupportsPerformanceAndReplay(sdkVersion: string): boolean {
396+
return sdkVersion === 'latest' || sdkVersion === '7.x';
397+
}

0 commit comments

Comments
 (0)