1
1
import { useContext } from 'react' ;
2
2
import styled from '@emotion/styled' ;
3
+ import toNumber from 'lodash/toNumber' ;
3
4
4
5
import { GroupPriorityBadge } from 'sentry/components/badge/groupPriority' ;
5
6
import { CompactSelect } from 'sentry/components/core/compactSelect' ;
@@ -16,11 +17,12 @@ import {
16
17
DETECTOR_PRIORITY_LEVEL_TO_PRIORITY_LEVEL ,
17
18
DetectorPriorityLevel ,
18
19
} from 'sentry/types/workflowEngine/dataConditions' ;
20
+ import type { MetricDetectorFormData } from 'sentry/views/detectors/components/forms/metric/metricFormData' ;
19
21
import {
20
22
METRIC_DETECTOR_FORM_FIELDS ,
21
23
useMetricDetectorFormField ,
22
24
} from 'sentry/views/detectors/components/forms/metric/metricFormData' ;
23
- import { getStaticDetectorThresholdSuffix } from 'sentry/views/detectors/utils/metricDetectorSuffix' ;
25
+ import { getMetricDetectorSuffix } from 'sentry/views/detectors/utils/metricDetectorSuffix' ;
24
26
25
27
const priorities = [
26
28
DetectorPriorityLevel . LOW ,
@@ -42,17 +44,13 @@ const conditionKindAndTypeToLabel: Record<
42
44
} ,
43
45
} ;
44
46
45
- function ThresholdPriority ( ) {
47
+ function ThresholdPriority ( { thresholdSuffix } : { thresholdSuffix : string } ) {
46
48
const conditionType = useMetricDetectorFormField (
47
49
METRIC_DETECTOR_FORM_FIELDS . conditionType
48
50
) ;
49
51
const conditionValue = useMetricDetectorFormField (
50
52
METRIC_DETECTOR_FORM_FIELDS . conditionValue
51
53
) ;
52
- const aggregate = useMetricDetectorFormField (
53
- METRIC_DETECTOR_FORM_FIELDS . aggregateFunction
54
- ) ;
55
- const thresholdSuffix = getStaticDetectorThresholdSuffix ( aggregate ) ;
56
54
57
55
return (
58
56
< div >
@@ -63,17 +61,13 @@ function ThresholdPriority() {
63
61
) ;
64
62
}
65
63
66
- function ChangePriority ( ) {
64
+ function ChangePriority ( { thresholdSuffix } : { thresholdSuffix : string } ) {
67
65
const conditionType = useMetricDetectorFormField (
68
66
METRIC_DETECTOR_FORM_FIELDS . conditionType
69
67
) ;
70
68
const conditionValue = useMetricDetectorFormField (
71
69
METRIC_DETECTOR_FORM_FIELDS . conditionValue
72
70
) ;
73
- const aggregate = useMetricDetectorFormField (
74
- METRIC_DETECTOR_FORM_FIELDS . aggregateFunction
75
- ) ;
76
- const thresholdSuffix = getStaticDetectorThresholdSuffix ( aggregate ) ;
77
71
78
72
return (
79
73
< div >
@@ -83,8 +77,54 @@ function ChangePriority() {
83
77
) ;
84
78
}
85
79
80
+ function createValidationError ( field : string , message : string ) : [ string , string ] {
81
+ return [ field , message ] ;
82
+ }
83
+
84
+ function validateThresholdOrder (
85
+ value : number ,
86
+ reference : number ,
87
+ conditionType : DataConditionType ,
88
+ isGreaterExpected : boolean
89
+ ) : boolean {
90
+ if ( conditionType === DataConditionType . GREATER ) {
91
+ return isGreaterExpected ? value > reference : value < reference ;
92
+ }
93
+ // For LESS condition type, logic is inverted
94
+ return isGreaterExpected ? value < reference : value > reference ;
95
+ }
96
+
97
+ function validateHighThreshold ( {
98
+ form,
99
+ } : {
100
+ form : MetricDetectorFormData ;
101
+ id : string ;
102
+ } ) : Array < [ string , string ] > {
103
+ const highNum = toNumber ( form . highThreshold ) ;
104
+ const conditionNum = toNumber ( form . conditionValue ) ;
105
+ const { conditionType} = form ;
106
+
107
+ if ( ! conditionType ) {
108
+ return [ ] ;
109
+ }
110
+
111
+ if (
112
+ Number . isFinite ( highNum ) &&
113
+ Number . isFinite ( conditionNum ) &&
114
+ ! validateThresholdOrder ( highNum , conditionNum , conditionType , true )
115
+ ) {
116
+ const message = t (
117
+ 'High threshold must be %s than medium threshold' ,
118
+ conditionType === DataConditionType . GREATER ? t ( 'higher' ) : t ( 'lower' )
119
+ ) ;
120
+ return [ createValidationError ( METRIC_DETECTOR_FORM_FIELDS . highThreshold , message ) ] ;
121
+ }
122
+
123
+ return [ ] ;
124
+ }
125
+
86
126
interface PriorityControlProps {
87
- minimumPriority : DetectorPriorityLevel ;
127
+ minimumPriority : DetectorPriorityLevel . MEDIUM | DetectorPriorityLevel . HIGH ;
88
128
}
89
129
90
130
export default function PriorityControl ( { minimumPriority} : PriorityControlProps ) {
@@ -100,7 +140,7 @@ export default function PriorityControl({minimumPriority}: PriorityControlProps)
100
140
const aggregate = useMetricDetectorFormField (
101
141
METRIC_DETECTOR_FORM_FIELDS . aggregateFunction
102
142
) ;
103
- const thresholdSuffix = getStaticDetectorThresholdSuffix ( aggregate ) ;
143
+ const thresholdSuffix = getMetricDetectorSuffix ( detectionType , aggregate ) ;
104
144
105
145
if ( detectionType === 'dynamic' ) {
106
146
return null ;
@@ -111,47 +151,31 @@ export default function PriorityControl({minimumPriority}: PriorityControlProps)
111
151
< PrioritizeRow
112
152
left = {
113
153
< Flex align = "center" direction = "column" >
114
- { detectionType === 'static' ? < ThresholdPriority /> : < ChangePriority /> }
154
+ { detectionType === 'static' ? (
155
+ < ThresholdPriority thresholdSuffix = { thresholdSuffix } />
156
+ ) : (
157
+ < ChangePriority thresholdSuffix = { thresholdSuffix } />
158
+ ) }
115
159
< SecondaryLabel > ({ t ( 'issue created' ) } )</ SecondaryLabel >
116
160
</ Flex >
117
161
}
118
162
right = { < InitialPrioritySelect minimumPriority = { minimumPriority } /> }
119
163
/>
120
- { priorityIsConfigurable ( initialPriorityLevel , DetectorPriorityLevel . MEDIUM ) && (
164
+ { initialPriorityLevel === DetectorPriorityLevel . MEDIUM && (
121
165
< PrioritizeRow
122
166
left = {
123
167
< Flex align = "center" gap = "md" >
124
168
< SmallNumberField
125
- alignRight
126
- inline
127
- hideLabel
128
- flexibleControlStateSize
129
- size = "sm"
130
- suffix = { thresholdSuffix }
131
- placeholder = "0"
132
- name = { METRIC_DETECTOR_FORM_FIELDS . mediumThreshold }
133
- aria-label = { t ( 'Medium threshold' ) }
134
- />
135
- < div > { conditionKindAndTypeToLabel [ detectionType ] [ conditionType ! ] } </ div >
136
- </ Flex >
137
- }
138
- right = { < GroupPriorityBadge showLabel priority = { PriorityLevel . MEDIUM } /> }
139
- />
140
- ) }
141
- { priorityIsConfigurable ( initialPriorityLevel , DetectorPriorityLevel . HIGH ) && (
142
- < PrioritizeRow
143
- left = {
144
- < Flex align = "center" gap = "md" >
145
- < SmallNumberField
146
- alignRight
147
- inline
169
+ inline = { false }
148
170
hideLabel
149
171
flexibleControlStateSize
150
172
size = "sm"
151
173
suffix = { thresholdSuffix }
152
174
placeholder = "0"
153
175
name = { METRIC_DETECTOR_FORM_FIELDS . highThreshold }
154
176
aria-label = { t ( 'High threshold' ) }
177
+ validate = { validateHighThreshold }
178
+ required
155
179
/>
156
180
< div > { conditionKindAndTypeToLabel [ detectionType ] [ conditionType ! ] } </ div >
157
181
</ Flex >
@@ -163,13 +187,6 @@ export default function PriorityControl({minimumPriority}: PriorityControlProps)
163
187
) ;
164
188
}
165
189
166
- function priorityIsConfigurable (
167
- initialPriorityLevel : DetectorPriorityLevel ,
168
- targetPriority : DetectorPriorityLevel
169
- ) : boolean {
170
- return targetPriority > initialPriorityLevel ;
171
- }
172
-
173
190
function PrioritizeRow ( { left, right} : { left : React . ReactNode ; right : React . ReactNode } ) {
174
191
return (
175
192
< Row >
@@ -258,7 +275,7 @@ const Cell = styled('div')`
258
275
` ;
259
276
260
277
const SmallNumberField = styled ( NumberField ) `
261
- width: 3.5rem ;
278
+ width: 6rem ;
262
279
padding: 0;
263
280
& > div {
264
281
padding-left: 0;
0 commit comments