Skip to content

Commit e7976a6

Browse files
authored
feat(perf-issues): Expose management settings to users (#91740)
This PR will enable flagged organizations to modify the detection settings manually rather than having to go through support. Keeping the regression issue fields, but everything else has been moved into the section for the detector. <img width="635" alt="image" src="https://github.com/user-attachments/assets/76bdf93c-0544-434f-a8f1-1dd1c8ed2620" /> **todo** - [x] Add some tests - [x] Add screenshots/videos to the PR
1 parent a137a20 commit e7976a6

File tree

2 files changed

+258
-105
lines changed

2 files changed

+258
-105
lines changed

static/app/views/settings/projectPerformance/projectPerformance.spec.tsx

Lines changed: 191 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,38 @@ import ProjectPerformance, {
1919
DetectorConfigCustomer,
2020
} from 'sentry/views/settings/projectPerformance/projectPerformance';
2121

22+
const manageDetectorData = [
23+
{label: 'N+1 DB Queries Detection', key: 'n_plus_one_db_queries_detection_enabled'},
24+
{label: 'Slow DB Queries Detection', key: 'slow_db_queries_detection_enabled'},
25+
{label: 'DB on Main Thread Detection', key: 'db_on_main_thread_detection_enabled'},
26+
{
27+
label: 'File I/O on Main Thread Detection',
28+
key: 'file_io_on_main_thread_detection_enabled',
29+
},
30+
{
31+
label: 'Consecutive DB Queries Detection',
32+
key: 'consecutive_db_queries_detection_enabled',
33+
},
34+
{
35+
label: 'Large Render Blocking Asset Detection',
36+
key: 'large_render_blocking_asset_detection_enabled',
37+
},
38+
{
39+
label: 'Uncompressed Assets Detection',
40+
key: 'uncompressed_assets_detection_enabled',
41+
},
42+
{label: 'Large HTTP Payload Detection', key: 'large_http_payload_detection_enabled'},
43+
{label: 'N+1 API Calls Detection', key: 'n_plus_one_api_calls_detection_enabled'},
44+
{
45+
label: 'Consecutive HTTP Detection',
46+
key: 'consecutive_http_spans_detection_enabled',
47+
},
48+
{
49+
label: 'HTTP/1.1 Overhead Detection',
50+
key: 'http_overhead_detection_enabled',
51+
},
52+
];
53+
2254
describe('projectPerformance', function () {
2355
const org = OrganizationFixture({features: ['performance-view']});
2456
const project = ProjectFixture();
@@ -145,13 +177,11 @@ describe('projectPerformance', function () {
145177
initialRouterConfig,
146178
});
147179

148-
expect(
149-
await screen.findByText('N+1 DB Queries Detection Enabled')
150-
).toBeInTheDocument();
151-
expect(screen.getByText('Slow DB Queries Detection Enabled')).toBeInTheDocument();
180+
expect(await screen.findByText('N+1 DB Queries Detection')).toBeInTheDocument();
181+
expect(screen.getByText('Slow DB Queries Detection')).toBeInTheDocument();
152182

153183
const toggle = screen.getByRole('checkbox', {
154-
name: 'N+1 DB Queries Detection Enabled',
184+
name: 'N+1 DB Queries Detection',
155185
});
156186
await userEvent.click(toggle);
157187

@@ -170,99 +200,142 @@ describe('projectPerformance', function () {
170200
allowedValues: allowedDurationValues,
171201
defaultValue: 100,
172202
newValue: 500,
173-
sliderIndex: 1,
203+
sliderIdentifier: {
204+
label: 'Minimum Total Duration',
205+
index: 0,
206+
},
174207
},
175208
{
176209
title: IssueTitle.PERFORMANCE_N_PLUS_ONE_DB_QUERIES,
177210
threshold: DetectorConfigCustomer.N_PLUS_DB_COUNT,
178211
allowedValues: allowedCountValues,
179212
defaultValue: 5,
180213
newValue: 10,
181-
sliderIndex: 2,
214+
sliderIdentifier: {
215+
label: 'Minimum Query Count',
216+
index: 0,
217+
},
182218
},
183219
{
184220
title: IssueTitle.PERFORMANCE_SLOW_DB_QUERY,
185221
threshold: DetectorConfigCustomer.SLOW_DB_DURATION,
186222
allowedValues: allowedDurationValues.slice(5),
187223
defaultValue: 1000,
188224
newValue: 3000,
189-
sliderIndex: 3,
225+
sliderIdentifier: {
226+
label: 'Minimum Duration',
227+
index: 0,
228+
},
190229
},
191230
{
192231
title: IssueTitle.PERFORMANCE_N_PLUS_ONE_API_CALLS,
193232
threshold: DetectorConfigCustomer.N_PLUS_API_CALLS_DURATION,
194233
allowedValues: allowedDurationValues.slice(5),
195234
defaultValue: 300,
196235
newValue: 500,
197-
sliderIndex: 4,
236+
sliderIdentifier: {
237+
label: 'Minimum Total Duration',
238+
index: 1,
239+
},
198240
},
199241
{
200242
title: IssueTitle.PERFORMANCE_RENDER_BLOCKING_ASSET,
201243
threshold: DetectorConfigCustomer.RENDER_BLOCKING_ASSET_RATIO,
202244
allowedValues: allowedPercentageValues,
203245
defaultValue: 0.33,
204246
newValue: 0.5,
205-
sliderIndex: 5,
247+
sliderIdentifier: {
248+
label: 'Minimum FCP Ratio',
249+
index: 0,
250+
},
206251
},
207252
{
208253
title: IssueTitle.PERFORMANCE_LARGE_HTTP_PAYLOAD,
209254
threshold: DetectorConfigCustomer.LARGE_HTT_PAYLOAD_SIZE,
210255
allowedValues: allowedSizeValues.slice(1),
211256
defaultValue: 1000000,
212257
newValue: 5000000,
213-
sliderIndex: 6,
258+
sliderIdentifier: {
259+
label: 'Minimum Size',
260+
index: 0,
261+
},
214262
},
215263
{
216264
title: IssueTitle.PERFORMANCE_DB_MAIN_THREAD,
217265
threshold: DetectorConfigCustomer.DB_ON_MAIN_THREAD_DURATION,
218266
allowedValues: [10, 16, 33, 50],
219267
defaultValue: 16,
220268
newValue: 33,
221-
sliderIndex: 7,
269+
sliderIdentifier: {
270+
label: 'Frame Rate Drop',
271+
index: 0,
272+
},
222273
},
223274
{
224275
title: IssueTitle.PERFORMANCE_FILE_IO_MAIN_THREAD,
225276
threshold: DetectorConfigCustomer.FILE_IO_MAIN_THREAD_DURATION,
226277
allowedValues: [10, 16, 33, 50],
227278
defaultValue: 16,
228279
newValue: 50,
229-
sliderIndex: 8,
280+
sliderIdentifier: {
281+
label: 'Frame Rate Drop',
282+
index: 1,
283+
},
230284
},
231285
{
232286
title: IssueTitle.PERFORMANCE_CONSECUTIVE_DB_QUERIES,
233287
threshold: DetectorConfigCustomer.CONSECUTIVE_DB_MIN_TIME_SAVED,
234288
allowedValues: allowedDurationValues.slice(0, 23),
235289
defaultValue: 100,
236290
newValue: 5000,
237-
sliderIndex: 9,
291+
sliderIdentifier: {
292+
label: 'Minimum Time Saved',
293+
index: 0,
294+
},
238295
},
239296
{
240297
title: IssueTitle.PERFORMANCE_UNCOMPRESSED_ASSET,
241298
threshold: DetectorConfigCustomer.UNCOMPRESSED_ASSET_SIZE,
242299
allowedValues: allowedSizeValues.slice(1),
243300
defaultValue: 512000,
244301
newValue: 700000,
245-
sliderIndex: 10,
302+
sliderIdentifier: {
303+
label: 'Minimum Size',
304+
index: 1,
305+
},
246306
},
247307
{
248308
title: IssueTitle.PERFORMANCE_UNCOMPRESSED_ASSET,
249309
threshold: DetectorConfigCustomer.UNCOMPRESSED_ASSET_DURATION,
250310
allowedValues: allowedDurationValues.slice(5),
251311
defaultValue: 500,
252312
newValue: 400,
253-
sliderIndex: 11,
313+
sliderIdentifier: {
314+
label: 'Minimum Duration',
315+
index: 1,
316+
},
254317
},
255318
{
256319
title: IssueTitle.PERFORMANCE_CONSECUTIVE_HTTP,
257320
threshold: DetectorConfigCustomer.CONSECUTIVE_HTTP_MIN_TIME_SAVED,
258321
allowedValues: allowedDurationValues.slice(14),
259322
defaultValue: 2000,
260323
newValue: 4000,
261-
sliderIndex: 12,
324+
sliderIdentifier: {
325+
label: 'Minimum Time Saved',
326+
index: 1,
327+
},
262328
},
263329
])(
264330
'renders detector thresholds settings for $title issue',
265-
async ({title, threshold, allowedValues, defaultValue, newValue, sliderIndex}) => {
331+
async ({
332+
title,
333+
threshold,
334+
allowedValues,
335+
defaultValue,
336+
newValue,
337+
sliderIdentifier,
338+
}) => {
266339
// Mock endpoints
267340
const mockGETBody = {
268341
[threshold]: defaultValue,
@@ -305,7 +378,10 @@ describe('projectPerformance', function () {
305378
await userEvent.click(chevron);
306379
}
307380

308-
const slider = screen.getAllByRole('slider')[sliderIndex]!;
381+
// Some of the sliders have the same label, so use an index as well
382+
const slider = screen.getAllByRole('slider', {name: sliderIdentifier.label})[
383+
sliderIdentifier.index
384+
]!;
309385
const indexOfValue = allowedValues.indexOf(defaultValue);
310386
const newValueIndex = allowedValues.indexOf(newValue);
311387

@@ -373,4 +449,100 @@ describe('projectPerformance', function () {
373449

374450
expect(delete_request_mock).toHaveBeenCalled();
375451
});
452+
453+
it.each(manageDetectorData)(
454+
'allows project admins to manage $label',
455+
async function ({label, key}) {
456+
MockApiClient.addMockResponse({
457+
url: '/projects/org-slug/project-slug/',
458+
method: 'GET',
459+
body: ProjectFixture({access: ['project:admin']}),
460+
statusCode: 200,
461+
});
462+
463+
render(<ProjectPerformance />, {
464+
organization: OrganizationFixture({
465+
features: ['performance-view', 'performance-manage-detectors'],
466+
}),
467+
initialRouterConfig,
468+
});
469+
await screen.findByText('Performance Issues - Detector Threshold Settings');
470+
471+
// Hidden by form panels being collapsed
472+
let toggle = screen.queryByRole<HTMLInputElement>('checkbox', {name: label});
473+
expect(toggle).not.toBeInTheDocument();
474+
475+
const chevrons = screen.getAllByTestId('form-panel-collapse-chevron');
476+
for (const chevron of chevrons) {
477+
await userEvent.click(chevron);
478+
}
479+
480+
const mockPut = MockApiClient.addMockResponse({
481+
url: '/projects/org-slug/project-slug/performance-issues/configure/',
482+
method: 'PUT',
483+
});
484+
485+
// Enabled by default
486+
toggle = screen.getByRole<HTMLInputElement>('checkbox', {name: label});
487+
expect(toggle).toBeChecked();
488+
489+
// Disable the detector
490+
await userEvent.click(toggle);
491+
expect(mockPut).toHaveBeenCalledWith(
492+
'/projects/org-slug/project-slug/performance-issues/configure/',
493+
expect.objectContaining({
494+
data: {
495+
[key]: false,
496+
},
497+
})
498+
);
499+
mockPut.mockClear();
500+
expect(toggle).not.toBeChecked();
501+
502+
// Re-enable the detector
503+
await userEvent.click(toggle);
504+
expect(mockPut).toHaveBeenCalledWith(
505+
'/projects/org-slug/project-slug/performance-issues/configure/',
506+
expect.objectContaining({
507+
data: {
508+
[key]: true,
509+
},
510+
})
511+
);
512+
expect(toggle).toBeChecked();
513+
}
514+
);
515+
516+
it.each(manageDetectorData)(
517+
'does not allow non-admins to manage $label',
518+
async function ({label}) {
519+
MockApiClient.addMockResponse({
520+
url: '/projects/org-slug/project-slug/',
521+
method: 'GET',
522+
body: ProjectFixture({access: ['project:read']}),
523+
statusCode: 200,
524+
});
525+
526+
render(<ProjectPerformance />, {
527+
organization: OrganizationFixture({
528+
features: ['performance-view', 'performance-manage-detectors'],
529+
access: ['project:read'],
530+
}),
531+
initialRouterConfig,
532+
});
533+
534+
await screen.findByText('Performance Issues - Detector Threshold Settings');
535+
536+
let toggle = screen.queryByRole<HTMLInputElement>('checkbox', {name: label});
537+
expect(toggle).not.toBeInTheDocument();
538+
539+
const chevrons = screen.getAllByTestId('form-panel-collapse-chevron');
540+
for (const chevron of chevrons) {
541+
await userEvent.click(chevron);
542+
}
543+
544+
toggle = screen.queryByRole<HTMLInputElement>('checkbox', {name: label});
545+
expect(toggle).toBeDisabled();
546+
}
547+
);
376548
});

0 commit comments

Comments
 (0)