@@ -5,6 +5,7 @@ import * as qs from 'query-string';
5
5
6
6
import ProjectAvatar from 'sentry/components/avatar/projectAvatar' ;
7
7
import { Button } from 'sentry/components/button' ;
8
+ import { CompactSelect } from 'sentry/components/compactSelect' ;
8
9
import { SegmentedControl } from 'sentry/components/segmentedControl' ;
9
10
import { t } from 'sentry/locale' ;
10
11
import { space } from 'sentry/styles/space' ;
@@ -19,9 +20,11 @@ import useProjects from 'sentry/utils/useProjects';
19
20
import useRouter from 'sentry/utils/useRouter' ;
20
21
import { normalizeUrl } from 'sentry/utils/withDomainRequired' ;
21
22
import { AverageValueMarkLine } from 'sentry/views/performance/charts/averageValueMarkLine' ;
23
+ import { HTTP_RESPONSE_STATUS_CODES } from 'sentry/views/performance/http/definitions' ;
22
24
import { DurationChart } from 'sentry/views/performance/http/durationChart' ;
23
25
import decodePanel from 'sentry/views/performance/http/queryParameterDecoders/panel' ;
24
- import { ResponseRateChart } from 'sentry/views/performance/http/responseRateChart' ;
26
+ import decodeResponseCodeClass from 'sentry/views/performance/http/queryParameterDecoders/responseCodeClass' ;
27
+ import { ResponseCodeCountChart } from 'sentry/views/performance/http/responseCodeCountChart' ;
25
28
import { SpanSamplesTable } from 'sentry/views/performance/http/spanSamplesTable' ;
26
29
import { useDebouncedState } from 'sentry/views/performance/http/useDebouncedState' ;
27
30
import { useSpanSamples } from 'sentry/views/performance/http/useSpanSamples' ;
@@ -32,6 +35,7 @@ import DetailPanel from 'sentry/views/starfish/components/detailPanel';
32
35
import { getTimeSpentExplanation } from 'sentry/views/starfish/components/tableCells/timeSpentCell' ;
33
36
import { useSpanMetrics } from 'sentry/views/starfish/queries/useSpanMetrics' ;
34
37
import { useSpanMetricsSeries } from 'sentry/views/starfish/queries/useSpanMetricsSeries' ;
38
+ import { useSpanMetricsTopNSeries } from 'sentry/views/starfish/queries/useSpanMetricsTopNSeries' ;
35
39
import {
36
40
ModuleName ,
37
41
SpanFunction ,
@@ -53,6 +57,7 @@ export function HTTPSamplesPanel() {
53
57
transaction : decodeScalar ,
54
58
transactionMethod : decodeScalar ,
55
59
panel : decodePanel ,
60
+ responseCodeClass : decodeResponseCodeClass ,
56
61
} ,
57
62
} ) ;
58
63
@@ -86,19 +91,50 @@ export function HTTPSamplesPanel() {
86
91
} ) ;
87
92
} ;
88
93
94
+ const handleResponseCodeClassChange = newResponseCodeClass => {
95
+ router . replace ( {
96
+ pathname : location . pathname ,
97
+ query : {
98
+ ...location . query ,
99
+ responseCodeClass : newResponseCodeClass . value ,
100
+ } ,
101
+ } ) ;
102
+ } ;
103
+
89
104
const isPanelOpen = Boolean ( detailKey ) ;
90
105
106
+ // The ribbon is above the data selectors, and not affected by them. So, it has its own filters.
107
+ const ribbonFilters : SpanMetricsQueryFilters = {
108
+ 'span.module' : ModuleName . HTTP ,
109
+ 'span.domain' : query . domain ,
110
+ transaction : query . transaction ,
111
+ } ;
112
+
113
+ // These filters are for the charts and samples tables
91
114
const filters : SpanMetricsQueryFilters = {
92
115
'span.module' : ModuleName . HTTP ,
93
116
'span.domain' : query . domain ,
94
117
transaction : query . transaction ,
95
118
} ;
96
119
120
+ const responseCodeInRange = query . responseCodeClass
121
+ ? Object . keys ( HTTP_RESPONSE_STATUS_CODES ) . filter ( code =>
122
+ code . startsWith ( query . responseCodeClass )
123
+ )
124
+ : [ ] ;
125
+
126
+ if ( responseCodeInRange . length > 0 ) {
127
+ // TODO: Allow automatic array parameter concatenation
128
+ filters [ 'span.status_code' ] = `[${ responseCodeInRange . join ( ',' ) } ]` ;
129
+ }
130
+
131
+ const search = MutableSearch . fromQueryObject ( filters ) ;
132
+
97
133
const {
98
134
data : domainTransactionMetrics ,
99
135
isFetching : areDomainTransactionMetricsFetching ,
100
136
} = useSpanMetrics ( {
101
- search : MutableSearch . fromQueryObject ( filters ) ,
137
+ search : MutableSearch . fromQueryObject ( ribbonFilters ) ,
102
138
fields : [
103
139
`${ SpanFunction . SPM } ()` ,
104
140
`avg(${ SpanMetricsField . SPAN_SELF_TIME } )` ,
@@ -117,7 +153,7 @@ export function HTTPSamplesPanel() {
117
153
data : durationData ,
118
154
error : durationError ,
119
155
} = useSpanMetricsSeries ( {
120
- search : MutableSearch . fromQueryObject ( filters ) ,
156
+ search,
121
157
yAxis : [ `avg(span.self_time)` ] ,
122
158
enabled : isPanelOpen && query . panel === 'duration' ,
123
159
referrer : 'api.starfish.http-module-samples-panel-duration-chart' ,
@@ -127,9 +163,11 @@ export function HTTPSamplesPanel() {
127
163
isFetching : isResponseCodeDataLoading ,
128
164
data : responseCodeData ,
129
165
error : responseCodeError ,
130
- } = useSpanMetricsSeries ( {
131
- search : MutableSearch . fromQueryObject ( filters ) ,
132
- yAxis : [ 'http_response_rate(3)' , 'http_response_rate(4)' , 'http_response_rate(5)' ] ,
166
+ } = useSpanMetricsTopNSeries ( {
167
+ search,
168
+ fields : [ 'span.status_code' , 'count()' ] ,
169
+ yAxis : [ 'count()' ] ,
170
+ topEvents : 5 ,
133
171
enabled : isPanelOpen && query . panel === 'status' ,
134
172
referrer : 'api.starfish.http-module-samples-panel-response-code-chart' ,
135
173
} ) ;
@@ -142,7 +180,7 @@ export function HTTPSamplesPanel() {
142
180
error : samplesDataError ,
143
181
refetch : refetchSpanSamples ,
144
182
} = useSpanSamples ( {
145
- search : MutableSearch . fromQueryObject ( filters ) ,
183
+ search,
146
184
fields : [
147
185
SpanIndexedField . TRANSACTION_ID ,
148
186
SpanIndexedField . SPAN_DESCRIPTION ,
@@ -276,18 +314,29 @@ export function HTTPSamplesPanel() {
276
314
</ ModuleLayout . Full >
277
315
278
316
< ModuleLayout . Full >
279
- < SegmentedControl
280
- value = { query . panel }
281
- onChange = { handlePanelChange }
282
- aria-label = { t ( 'Choose breakdown type' ) }
283
- >
284
- < SegmentedControl . Item key = "duration" >
285
- { t ( 'By Duration' ) }
286
- </ SegmentedControl . Item >
287
- < SegmentedControl . Item key = "status" >
288
- { t ( 'By Response Code' ) }
289
- </ SegmentedControl . Item >
290
- </ SegmentedControl >
317
+ < PanelControls >
318
+ < SegmentedControl
319
+ value = { query . panel }
320
+ onChange = { handlePanelChange }
321
+ aria-label = { t ( 'Choose breakdown type' ) }
322
+ >
323
+ < SegmentedControl . Item key = "duration" >
324
+ { t ( 'By Duration' ) }
325
+ </ SegmentedControl . Item >
326
+ < SegmentedControl . Item key = "status" >
327
+ { t ( 'By Response Code' ) }
328
+ </ SegmentedControl . Item >
329
+ </ SegmentedControl >
330
+
331
+ < CompactSelect
332
+ value = { query . responseCodeClass }
333
+ options = { HTTP_RESPONSE_CODE_CLASS_OPTIONS }
334
+ onChange = { handleResponseCodeClassChange }
335
+ triggerProps = { {
336
+ prefix : t ( 'Response Code' ) ,
337
+ } }
338
+ />
339
+ </ PanelControls >
291
340
</ ModuleLayout . Full >
292
341
293
342
{ query . panel === 'duration' && (
@@ -339,21 +388,8 @@ export function HTTPSamplesPanel() {
339
388
340
389
{ query . panel === 'status' && (
341
390
< ModuleLayout . Full >
342
- < ResponseRateChart
343
- series = { [
344
- {
345
- ...responseCodeData [ `http_response_rate(3)` ] ,
346
- seriesName : t ( '3XX' ) ,
347
- } ,
348
- {
349
- ...responseCodeData [ `http_response_rate(4)` ] ,
350
- seriesName : t ( '4XX' ) ,
351
- } ,
352
- {
353
- ...responseCodeData [ `http_response_rate(5)` ] ,
354
- seriesName : t ( '5XX' ) ,
355
- } ,
356
- ] }
391
+ < ResponseCodeCountChart
392
+ series = { Object . values ( responseCodeData ) . filter ( Boolean ) }
357
393
isLoading = { isResponseCodeDataLoading }
358
394
error = { responseCodeError }
359
395
/>
@@ -377,6 +413,29 @@ const SpanSummaryProjectAvatar = styled(ProjectAvatar)`
377
413
padding-right: ${ space ( 1 ) } ;
378
414
` ;
379
415
416
+ const HTTP_RESPONSE_CODE_CLASS_OPTIONS = [
417
+ {
418
+ value : '' ,
419
+ label : t ( 'All' ) ,
420
+ } ,
421
+ {
422
+ value : '2' ,
423
+ label : t ( '2XXs' ) ,
424
+ } ,
425
+ {
426
+ value : '3' ,
427
+ label : t ( '3XXs' ) ,
428
+ } ,
429
+ {
430
+ value : '4' ,
431
+ label : t ( '4XXs' ) ,
432
+ } ,
433
+ {
434
+ value : '5' ,
435
+ label : t ( '5XXs' ) ,
436
+ } ,
437
+ ] ;
438
+
380
439
const HeaderContainer = styled ( 'div' ) `
381
440
display: grid;
382
441
grid-template-rows: auto auto auto;
@@ -404,3 +463,9 @@ const MetricsRibbon = styled('div')`
404
463
flex-wrap: wrap;
405
464
gap: ${ space ( 4 ) } ;
406
465
` ;
466
+
467
+ const PanelControls = styled ( 'div' ) `
468
+ display: flex;
469
+ justify-content: space-between;
470
+ gap: ${ space ( 2 ) } ;
471
+ ` ;
0 commit comments