@@ -19,6 +19,38 @@ import ProjectPerformance, {
19
19
DetectorConfigCustomer ,
20
20
} from 'sentry/views/settings/projectPerformance/projectPerformance' ;
21
21
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
+
22
54
describe ( 'projectPerformance' , function ( ) {
23
55
const org = OrganizationFixture ( { features : [ 'performance-view' ] } ) ;
24
56
const project = ProjectFixture ( ) ;
@@ -145,13 +177,11 @@ describe('projectPerformance', function () {
145
177
initialRouterConfig,
146
178
} ) ;
147
179
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 ( ) ;
152
182
153
183
const toggle = screen . getByRole ( 'checkbox' , {
154
- name : 'N+1 DB Queries Detection Enabled ' ,
184
+ name : 'N+1 DB Queries Detection' ,
155
185
} ) ;
156
186
await userEvent . click ( toggle ) ;
157
187
@@ -170,99 +200,142 @@ describe('projectPerformance', function () {
170
200
allowedValues : allowedDurationValues ,
171
201
defaultValue : 100 ,
172
202
newValue : 500 ,
173
- sliderIndex : 1 ,
203
+ sliderIdentifier : {
204
+ label : 'Minimum Total Duration' ,
205
+ index : 0 ,
206
+ } ,
174
207
} ,
175
208
{
176
209
title : IssueTitle . PERFORMANCE_N_PLUS_ONE_DB_QUERIES ,
177
210
threshold : DetectorConfigCustomer . N_PLUS_DB_COUNT ,
178
211
allowedValues : allowedCountValues ,
179
212
defaultValue : 5 ,
180
213
newValue : 10 ,
181
- sliderIndex : 2 ,
214
+ sliderIdentifier : {
215
+ label : 'Minimum Query Count' ,
216
+ index : 0 ,
217
+ } ,
182
218
} ,
183
219
{
184
220
title : IssueTitle . PERFORMANCE_SLOW_DB_QUERY ,
185
221
threshold : DetectorConfigCustomer . SLOW_DB_DURATION ,
186
222
allowedValues : allowedDurationValues . slice ( 5 ) ,
187
223
defaultValue : 1000 ,
188
224
newValue : 3000 ,
189
- sliderIndex : 3 ,
225
+ sliderIdentifier : {
226
+ label : 'Minimum Duration' ,
227
+ index : 0 ,
228
+ } ,
190
229
} ,
191
230
{
192
231
title : IssueTitle . PERFORMANCE_N_PLUS_ONE_API_CALLS ,
193
232
threshold : DetectorConfigCustomer . N_PLUS_API_CALLS_DURATION ,
194
233
allowedValues : allowedDurationValues . slice ( 5 ) ,
195
234
defaultValue : 300 ,
196
235
newValue : 500 ,
197
- sliderIndex : 4 ,
236
+ sliderIdentifier : {
237
+ label : 'Minimum Total Duration' ,
238
+ index : 1 ,
239
+ } ,
198
240
} ,
199
241
{
200
242
title : IssueTitle . PERFORMANCE_RENDER_BLOCKING_ASSET ,
201
243
threshold : DetectorConfigCustomer . RENDER_BLOCKING_ASSET_RATIO ,
202
244
allowedValues : allowedPercentageValues ,
203
245
defaultValue : 0.33 ,
204
246
newValue : 0.5 ,
205
- sliderIndex : 5 ,
247
+ sliderIdentifier : {
248
+ label : 'Minimum FCP Ratio' ,
249
+ index : 0 ,
250
+ } ,
206
251
} ,
207
252
{
208
253
title : IssueTitle . PERFORMANCE_LARGE_HTTP_PAYLOAD ,
209
254
threshold : DetectorConfigCustomer . LARGE_HTT_PAYLOAD_SIZE ,
210
255
allowedValues : allowedSizeValues . slice ( 1 ) ,
211
256
defaultValue : 1000000 ,
212
257
newValue : 5000000 ,
213
- sliderIndex : 6 ,
258
+ sliderIdentifier : {
259
+ label : 'Minimum Size' ,
260
+ index : 0 ,
261
+ } ,
214
262
} ,
215
263
{
216
264
title : IssueTitle . PERFORMANCE_DB_MAIN_THREAD ,
217
265
threshold : DetectorConfigCustomer . DB_ON_MAIN_THREAD_DURATION ,
218
266
allowedValues : [ 10 , 16 , 33 , 50 ] ,
219
267
defaultValue : 16 ,
220
268
newValue : 33 ,
221
- sliderIndex : 7 ,
269
+ sliderIdentifier : {
270
+ label : 'Frame Rate Drop' ,
271
+ index : 0 ,
272
+ } ,
222
273
} ,
223
274
{
224
275
title : IssueTitle . PERFORMANCE_FILE_IO_MAIN_THREAD ,
225
276
threshold : DetectorConfigCustomer . FILE_IO_MAIN_THREAD_DURATION ,
226
277
allowedValues : [ 10 , 16 , 33 , 50 ] ,
227
278
defaultValue : 16 ,
228
279
newValue : 50 ,
229
- sliderIndex : 8 ,
280
+ sliderIdentifier : {
281
+ label : 'Frame Rate Drop' ,
282
+ index : 1 ,
283
+ } ,
230
284
} ,
231
285
{
232
286
title : IssueTitle . PERFORMANCE_CONSECUTIVE_DB_QUERIES ,
233
287
threshold : DetectorConfigCustomer . CONSECUTIVE_DB_MIN_TIME_SAVED ,
234
288
allowedValues : allowedDurationValues . slice ( 0 , 23 ) ,
235
289
defaultValue : 100 ,
236
290
newValue : 5000 ,
237
- sliderIndex : 9 ,
291
+ sliderIdentifier : {
292
+ label : 'Minimum Time Saved' ,
293
+ index : 0 ,
294
+ } ,
238
295
} ,
239
296
{
240
297
title : IssueTitle . PERFORMANCE_UNCOMPRESSED_ASSET ,
241
298
threshold : DetectorConfigCustomer . UNCOMPRESSED_ASSET_SIZE ,
242
299
allowedValues : allowedSizeValues . slice ( 1 ) ,
243
300
defaultValue : 512000 ,
244
301
newValue : 700000 ,
245
- sliderIndex : 10 ,
302
+ sliderIdentifier : {
303
+ label : 'Minimum Size' ,
304
+ index : 1 ,
305
+ } ,
246
306
} ,
247
307
{
248
308
title : IssueTitle . PERFORMANCE_UNCOMPRESSED_ASSET ,
249
309
threshold : DetectorConfigCustomer . UNCOMPRESSED_ASSET_DURATION ,
250
310
allowedValues : allowedDurationValues . slice ( 5 ) ,
251
311
defaultValue : 500 ,
252
312
newValue : 400 ,
253
- sliderIndex : 11 ,
313
+ sliderIdentifier : {
314
+ label : 'Minimum Duration' ,
315
+ index : 1 ,
316
+ } ,
254
317
} ,
255
318
{
256
319
title : IssueTitle . PERFORMANCE_CONSECUTIVE_HTTP ,
257
320
threshold : DetectorConfigCustomer . CONSECUTIVE_HTTP_MIN_TIME_SAVED ,
258
321
allowedValues : allowedDurationValues . slice ( 14 ) ,
259
322
defaultValue : 2000 ,
260
323
newValue : 4000 ,
261
- sliderIndex : 12 ,
324
+ sliderIdentifier : {
325
+ label : 'Minimum Time Saved' ,
326
+ index : 1 ,
327
+ } ,
262
328
} ,
263
329
] ) (
264
330
'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
+ } ) => {
266
339
// Mock endpoints
267
340
const mockGETBody = {
268
341
[ threshold ] : defaultValue ,
@@ -305,7 +378,10 @@ describe('projectPerformance', function () {
305
378
await userEvent . click ( chevron ) ;
306
379
}
307
380
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
+ ] ! ;
309
385
const indexOfValue = allowedValues . indexOf ( defaultValue ) ;
310
386
const newValueIndex = allowedValues . indexOf ( newValue ) ;
311
387
@@ -373,4 +449,100 @@ describe('projectPerformance', function () {
373
449
374
450
expect ( delete_request_mock ) . toHaveBeenCalled ( ) ;
375
451
} ) ;
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
+ ) ;
376
548
} ) ;
0 commit comments