@@ -24,6 +24,7 @@ import {SearchInvalidTag} from 'sentry/components/smartSearchBar/searchInvalidTa
24
24
import { t , tct } from 'sentry/locale' ;
25
25
import { space } from 'sentry/styles/space' ;
26
26
import type { Environment , Organization , Project , SelectValue } from 'sentry/types' ;
27
+ import { ActivationConditionType , MonitorType } from 'sentry/types/alerts' ;
27
28
import { getDisplayName } from 'sentry/utils/environment' ;
28
29
import { hasCustomMetrics } from 'sentry/utils/metrics/features' ;
29
30
import { getMRI } from 'sentry/utils/metrics/mri' ;
@@ -69,20 +70,28 @@ type Props = {
69
70
disabled : boolean ;
70
71
onComparisonDeltaChange : ( value : number ) => void ;
71
72
onFilterSearch : ( query : string , isQueryValid ) => void ;
73
+ onMonitorTypeSelect : ( activatedAlertFields : {
74
+ activationCondition ?: ActivationConditionType | undefined ;
75
+ monitorType ?: MonitorType ;
76
+ monitorWindowSuffix ?: string | undefined ;
77
+ monitorWindowValue ?: number | undefined ;
78
+ } ) => void ;
72
79
onTimeWindowChange : ( value : number ) => void ;
73
80
organization : Organization ;
74
81
project : Project ;
75
82
projects : Project [ ] ;
76
83
router : InjectedRouter ;
77
84
thresholdChart : React . ReactNode ;
78
85
timeWindow : number ;
86
+ activationCondition ?: ActivationConditionType ;
79
87
allowChangeEventTypes ?: boolean ;
80
88
comparisonDelta ?: number ;
81
89
disableProjectSelector ?: boolean ;
82
90
isErrorMigration ?: boolean ;
83
91
isExtrapolatedChartData ?: boolean ;
84
92
isTransactionMigration ?: boolean ;
85
93
loadingProjects ?: boolean ;
94
+ monitorType ?: number ;
86
95
} ;
87
96
88
97
type State = {
@@ -162,6 +171,20 @@ class RuleConditionsForm extends PureComponent<Props, State> {
162
171
}
163
172
}
164
173
174
+ get selectControlStyles ( ) {
175
+ return {
176
+ control : ( provided : { [ x : string ] : string | number | boolean } ) => ( {
177
+ ...provided ,
178
+ minWidth : 200 ,
179
+ maxWidth : 300 ,
180
+ } ) ,
181
+ container : ( provided : { [ x : string ] : string | number | boolean } ) => ( {
182
+ ...provided ,
183
+ margin : `${ space ( 0.5 ) } ` ,
184
+ } ) ,
185
+ } ;
186
+ }
187
+
165
188
renderEventTypeFilter ( ) {
166
189
const { organization, disabled, alertType, isErrorMigration} = this . props ;
167
190
@@ -326,8 +349,15 @@ class RuleConditionsForm extends PureComponent<Props, State> {
326
349
}
327
350
328
351
renderInterval ( ) {
329
- const { organization, disabled, alertType, timeWindow, onTimeWindowChange, project} =
330
- this . props ;
352
+ const {
353
+ organization,
354
+ disabled,
355
+ alertType,
356
+ timeWindow,
357
+ onTimeWindowChange,
358
+ project,
359
+ monitorType,
360
+ } = this . props ;
331
361
332
362
return (
333
363
< Fragment >
@@ -353,27 +383,107 @@ class RuleConditionsForm extends PureComponent<Props, State> {
353
383
alertType = { alertType }
354
384
required
355
385
/>
356
- < SelectControl
357
- name = "timeWindow"
358
- styles = { {
359
- control : ( provided : { [ x : string ] : string | number | boolean } ) => ( {
360
- ...provided ,
361
- minWidth : 200 ,
362
- maxWidth : 300 ,
363
- } ) ,
364
- container : ( provided : { [ x : string ] : string | number | boolean } ) => ( {
365
- ...provided ,
366
- margin : `${ space ( 0.5 ) } ` ,
367
- } ) ,
368
- } }
369
- options = { this . timeWindowOptions }
370
- required
371
- isDisabled = { disabled }
372
- value = { timeWindow }
373
- onChange = { ( { value} ) => onTimeWindowChange ( value ) }
374
- inline = { false }
375
- flexibleControlStateSize
376
- />
386
+ { monitorType === MonitorType . CONTINUOUS && (
387
+ < SelectControl
388
+ name = "timeWindow"
389
+ styles = { this . selectControlStyles }
390
+ options = { this . timeWindowOptions }
391
+ required = { monitorType === MonitorType . CONTINUOUS }
392
+ isDisabled = { disabled }
393
+ value = { timeWindow }
394
+ onChange = { ( { value} ) => onTimeWindowChange ( value ) }
395
+ inline = { false }
396
+ flexibleControlStateSize
397
+ />
398
+ ) }
399
+ </ FormRow >
400
+ </ Fragment >
401
+ ) ;
402
+ }
403
+
404
+ renderMonitorTypeSelect ( ) {
405
+ const {
406
+ onMonitorTypeSelect,
407
+ monitorType,
408
+ activationCondition,
409
+ timeWindow,
410
+ onTimeWindowChange,
411
+ } = this . props ;
412
+
413
+ return (
414
+ < Fragment >
415
+ < StyledListItem >
416
+ < StyledListTitle >
417
+ < div > { t ( 'Select Monitor Type' ) } </ div >
418
+ </ StyledListTitle >
419
+ </ StyledListItem >
420
+ < FormRow >
421
+ < MonitorSelect >
422
+ < MonitorCard
423
+ position = "left"
424
+ isSelected = { monitorType === MonitorType . CONTINUOUS }
425
+ onClick = { ( ) =>
426
+ onMonitorTypeSelect ( {
427
+ monitorType : MonitorType . CONTINUOUS ,
428
+ activationCondition,
429
+ } )
430
+ }
431
+ >
432
+ < strong > { t ( 'Continuous' ) } </ strong >
433
+ < div > { t ( 'Continuously monitor trends for the metrics outlined below' ) } </ div >
434
+ </ MonitorCard >
435
+ < MonitorCard
436
+ position = "right"
437
+ isSelected = { monitorType === MonitorType . ACTIVATED }
438
+ onClick = { ( ) =>
439
+ onMonitorTypeSelect ( {
440
+ monitorType : MonitorType . ACTIVATED ,
441
+ } )
442
+ }
443
+ >
444
+ < strong > Conditional</ strong >
445
+ { monitorType === MonitorType . ACTIVATED ? (
446
+ < ActivatedAlertFields >
447
+ { `${ t ( 'Monitor' ) } ` }
448
+ < SelectControl
449
+ name = "activationCondition"
450
+ styles = { this . selectControlStyles }
451
+ options = { [
452
+ {
453
+ value : ActivationConditionType . RELEASE_CREATION ,
454
+ label : t ( 'New Release' ) ,
455
+ } ,
456
+ {
457
+ value : ActivationConditionType . DEPLOY_CREATION ,
458
+ label : t ( 'New Deploy' ) ,
459
+ } ,
460
+ ] }
461
+ required
462
+ value = { activationCondition }
463
+ onChange = { ( { value} ) =>
464
+ onMonitorTypeSelect ( { activationCondition : value } )
465
+ }
466
+ inline = { false }
467
+ flexibleControlStateSize
468
+ />
469
+ { ` ${ t ( 'for' ) } ` }
470
+ < SelectControl
471
+ name = "timeWindow"
472
+ styles = { this . selectControlStyles }
473
+ options = { this . timeWindowOptions }
474
+ value = { timeWindow }
475
+ onChange = { ( { value} ) => onTimeWindowChange ( value ) }
476
+ inline = { false }
477
+ flexibleControlStateSize
478
+ />
479
+ </ ActivatedAlertFields >
480
+ ) : (
481
+ < div >
482
+ { t ( 'Temporarily monitor specified query given activation condition' ) }
483
+ </ div >
484
+ ) }
485
+ </ MonitorCard >
486
+ </ MonitorSelect >
377
487
</ FormRow >
378
488
</ Fragment >
379
489
) ;
@@ -395,6 +505,7 @@ class RuleConditionsForm extends PureComponent<Props, State> {
395
505
} = this . props ;
396
506
397
507
const { environments} = this . state ;
508
+ const hasActivatedAlerts = organization . features . includes ( 'activated-alert-rules' ) ;
398
509
399
510
const environmentOptions : SelectValue < string | null > [ ] = [
400
511
{
@@ -425,6 +536,7 @@ class RuleConditionsForm extends PureComponent<Props, State> {
425
536
) }
426
537
/>
427
538
) }
539
+ { hasActivatedAlerts && this . renderMonitorTypeSelect ( ) }
428
540
{ ! isErrorMigration && this . renderInterval ( ) }
429
541
< StyledListItem > { t ( 'Filter events' ) } </ StyledListItem >
430
542
< FormRow noMargin columns = { 1 + ( allowChangeEventTypes ? 1 : 0 ) + 1 } >
@@ -653,4 +765,46 @@ const FormRow = styled('div')<{columns?: number; noMargin?: boolean}>`
653
765
` }
654
766
` ;
655
767
768
+ const MonitorSelect = styled ( 'div' ) `
769
+ border-radius: ${ p => p . theme . borderRadius } ;
770
+ border: 1px solid ${ p => p . theme . border } ;
771
+ width: 100%;
772
+ display: grid;
773
+ grid-template-columns: 1fr 1fr;
774
+ ` ;
775
+
776
+ type MonitorCardProps = {
777
+ isSelected : boolean ;
778
+ /**
779
+ * Adds hover and focus states to the card
780
+ */
781
+ position : 'left' | 'right' ;
782
+ } ;
783
+
784
+ const MonitorCard = styled ( 'div' ) < MonitorCardProps > `
785
+ padding: ${ space ( 1 ) } ;
786
+ display: flex;
787
+ flex-grow: 1;
788
+ flex-direction: column;
789
+ cursor: pointer;
790
+
791
+ &:focus,
792
+ &:hover {
793
+ outline: 1px solid ${ p => p . theme . purple200 } ;
794
+ background-color: ${ p => p . theme . backgroundSecondary } ;
795
+ }
796
+
797
+ border-top-left-radius: ${ p => ( p . position === 'left' ? p . theme . borderRadius : 0 ) } ;
798
+ border-bottom-left-radius: ${ p => ( p . position === 'left' ? p . theme . borderRadius : 0 ) } ;
799
+ border-top-right-radius: ${ p => ( p . position !== 'left' ? p . theme . borderRadius : 0 ) } ;
800
+ border-bottom-right-radius: ${ p => ( p . position !== 'left' ? p . theme . borderRadius : 0 ) } ;
801
+ outline: ${ p => ( p . isSelected ? `1px solid ${ p . theme . purple400 } ` : 'none' ) } ;
802
+ ` ;
803
+
804
+ const ActivatedAlertFields = styled ( 'div' ) `
805
+ display: flex;
806
+ align-items: center;
807
+ justify-content: space-between;
808
+ ` ;
809
+
656
810
export default withApi ( withProjects ( RuleConditionsForm ) ) ;
0 commit comments