Skip to content

Commit f0134a0

Browse files
authored
feat(aci): Create detector on submit (#93380)
1 parent 2546709 commit f0134a0

File tree

13 files changed

+609
-242
lines changed

13 files changed

+609
-242
lines changed

static/app/components/workflowEngine/form/control/priorityControl.spec.tsx

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
33
import Form from 'sentry/components/forms/form';
44
import FormModel from 'sentry/components/forms/model';
55
import PriorityControl from 'sentry/components/workflowEngine/form/control/priorityControl';
6-
import {PriorityLevel} from 'sentry/types/group';
6+
import {DetectorPriorityLevel} from 'sentry/types/workflowEngine/dataConditions';
7+
import {
8+
DEFAULT_THRESHOLD_METRIC_FORM_DATA,
9+
METRIC_DETECTOR_FORM_FIELDS,
10+
} from 'sentry/views/detectors/components/forms/metricFormData';
711

812
describe('PriorityControl', function () {
913
it('renders children', async function () {
1014
const formModel = new FormModel({
1115
initialData: {
12-
'conditionGroup.conditions.0.type': 'above',
13-
'conditionGroup.conditions.0.comparison': '0',
14-
'conditionGroup.conditions.0.conditionResult': PriorityLevel.LOW,
16+
...DEFAULT_THRESHOLD_METRIC_FORM_DATA,
17+
[METRIC_DETECTOR_FORM_FIELDS.initialPriorityLevel]: DetectorPriorityLevel.LOW,
1518
},
1619
});
1720
render(
@@ -28,9 +31,8 @@ describe('PriorityControl', function () {
2831
it('allows configuring priority', async function () {
2932
const formModel = new FormModel({
3033
initialData: {
31-
'conditionGroup.conditions.0.type': 'above',
32-
'conditionGroup.conditions.0.comparison': '0',
33-
'conditionGroup.conditions.0.conditionResult': PriorityLevel.LOW,
34+
...DEFAULT_THRESHOLD_METRIC_FORM_DATA,
35+
[METRIC_DETECTOR_FORM_FIELDS.initialPriorityLevel]: DetectorPriorityLevel.LOW,
3436
},
3537
});
3638
render(
@@ -44,19 +46,18 @@ describe('PriorityControl', function () {
4446

4547
await userEvent.click(screen.getByRole('button', {name: 'Low'}));
4648
await userEvent.click(await screen.findByRole('option', {name: 'High'}));
47-
expect(formModel.getValue('conditionGroup.conditions.0.conditionResult')).toBe(
48-
PriorityLevel.HIGH
49+
expect(formModel.getValue(METRIC_DETECTOR_FORM_FIELDS.initialPriorityLevel)).toBe(
50+
DetectorPriorityLevel.HIGH
4951
);
5052
// Check that the medium threshold is not visible
5153
expect(screen.getAllByRole('button')).toHaveLength(1);
5254
});
5355

54-
it('allows configuring medium threshold', async function () {
56+
it('allows configuring medium and high thresholds', async function () {
5557
const formModel = new FormModel({
5658
initialData: {
57-
'conditionGroup.conditions.0.type': 'above',
58-
'conditionGroup.conditions.0.comparison': '0',
59-
'conditionGroup.conditions.0.conditionResult': PriorityLevel.LOW,
59+
...DEFAULT_THRESHOLD_METRIC_FORM_DATA,
60+
[METRIC_DETECTOR_FORM_FIELDS.initialPriorityLevel]: DetectorPriorityLevel.LOW,
6061
},
6162
});
6263
render(
@@ -65,25 +66,35 @@ describe('PriorityControl', function () {
6566
</Form>
6667
);
6768
const medium = await screen.findByTestId('priority-control-medium');
68-
await userEvent.type(medium, '12');
69-
expect(formModel.getValue('conditionGroup.conditions.1.comparison')).toBe('12');
69+
await userEvent.type(medium, '4');
70+
71+
const high = await screen.findByTestId('priority-control-high');
72+
await userEvent.type(high, '5');
73+
74+
expect(formModel.getValue(METRIC_DETECTOR_FORM_FIELDS.mediumThreshold)).toBe('4');
75+
expect(formModel.getValue(METRIC_DETECTOR_FORM_FIELDS.highThreshold)).toBe('5');
7076
});
7177

72-
it('allows configuring high value', async function () {
78+
it('filters priority options based on minimumPriority prop', async function () {
7379
const formModel = new FormModel({
7480
initialData: {
75-
'conditionGroup.conditions.0.type': 'above',
76-
'conditionGroup.conditions.0.comparison': '0',
77-
'conditionGroup.conditions.0.conditionResult': PriorityLevel.LOW,
81+
...DEFAULT_THRESHOLD_METRIC_FORM_DATA,
82+
[METRIC_DETECTOR_FORM_FIELDS.initialPriorityLevel]: DetectorPriorityLevel.MEDIUM,
7883
},
7984
});
85+
8086
render(
8187
<Form model={formModel} hideFooter>
82-
<PriorityControl />
88+
<PriorityControl minimumPriority={DetectorPriorityLevel.MEDIUM} />
8389
</Form>
8490
);
85-
const high = await screen.findByTestId('priority-control-high');
86-
await userEvent.type(high, '12');
87-
expect(formModel.getValue('conditionGroup.conditions.2.comparison')).toBe('12');
91+
92+
// Open the priority dropdown
93+
await userEvent.click(screen.getByRole('button', {name: 'Med'}));
94+
95+
// Should only show Medium and High options (not Low)
96+
expect(screen.getByRole('option', {name: 'Med'})).toBeInTheDocument();
97+
expect(screen.getByRole('option', {name: 'High'})).toBeInTheDocument();
98+
expect(screen.queryByRole('option', {name: 'Low'})).not.toBeInTheDocument();
8899
});
89100
});

static/app/components/workflowEngine/form/control/priorityControl.tsx

Lines changed: 90 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -8,48 +8,76 @@ import {FieldWrapper} from 'sentry/components/forms/fieldGroup/fieldWrapper';
88
import NumberField from 'sentry/components/forms/fields/numberField';
99
import FormContext from 'sentry/components/forms/formContext';
1010
import InteractionStateLayer from 'sentry/components/interactionStateLayer';
11-
import {useFormField} from 'sentry/components/workflowEngine/form/useFormField';
1211
import {IconArrow, IconChevron} from 'sentry/icons';
1312
import {t} from 'sentry/locale';
1413
import {space} from 'sentry/styles/space';
1514
import {PriorityLevel} from 'sentry/types/group';
15+
import {
16+
DataConditionType,
17+
DetectorPriorityLevel,
18+
} from 'sentry/types/workflowEngine/dataConditions';
19+
import {
20+
METRIC_DETECTOR_FORM_FIELDS,
21+
useMetricDetectorFormField,
22+
} from 'sentry/views/detectors/components/forms/metricFormData';
23+
24+
const priorities = [
25+
DetectorPriorityLevel.LOW,
26+
DetectorPriorityLevel.MEDIUM,
27+
DetectorPriorityLevel.HIGH,
28+
] as const;
29+
30+
const DETECTOR_PRIORITY_LEVEL_TO_PRIORITY_LEVEL: Record<
31+
(typeof priorities)[number],
32+
PriorityLevel
33+
> = {
34+
[DetectorPriorityLevel.LOW]: PriorityLevel.LOW,
35+
[DetectorPriorityLevel.MEDIUM]: PriorityLevel.MEDIUM,
36+
[DetectorPriorityLevel.HIGH]: PriorityLevel.HIGH,
37+
};
1638

1739
function ThresholdPriority() {
18-
const lowThresholdDirection = useFormField<string>('conditionGroup.conditions.0.type')!;
19-
const lowThreshold = useFormField<string>('conditionGroup.conditions.0.comparison')!;
40+
const conditionType = useMetricDetectorFormField(
41+
METRIC_DETECTOR_FORM_FIELDS.conditionType
42+
);
43+
const conditionValue = useMetricDetectorFormField(
44+
METRIC_DETECTOR_FORM_FIELDS.conditionValue
45+
);
2046
return (
2147
<div>
22-
{lowThresholdDirection === ''
23-
? t('Above')
24-
: lowThresholdDirection === 'above'
25-
? t('Above')
26-
: t('Below')}{' '}
27-
{lowThreshold === '' ? '0s' : lowThreshold + 's'}
48+
{conditionType === DataConditionType.GREATER ? t('Above') : t('Below')}{' '}
49+
{conditionValue === '' ? '0s' : `${conditionValue}s`}
2850
</div>
2951
);
3052
}
3153

3254
function ChangePriority() {
33-
const lowThresholdDirection = useFormField<string>('conditionGroup.conditions.0.type')!;
34-
const lowThreshold = useFormField<string>('conditionGroup.conditions.0.comparison')!;
55+
const conditionType = useMetricDetectorFormField(
56+
METRIC_DETECTOR_FORM_FIELDS.conditionType
57+
);
58+
const conditionValue = useMetricDetectorFormField(
59+
METRIC_DETECTOR_FORM_FIELDS.conditionValue
60+
);
3561
return (
3662
<div>
37-
{lowThreshold === '' ? '0' : lowThreshold}%{' '}
38-
{lowThresholdDirection === ''
39-
? t('higher')
40-
: lowThresholdDirection === 'higher'
41-
? t('higher')
42-
: t('lower')}
63+
{conditionValue === '' ? '0' : conditionValue}%{' '}
64+
{conditionType === DataConditionType.GREATER ? t('higher') : t('lower')}
4365
</div>
4466
);
4567
}
4668

47-
export default function PriorityControl() {
69+
interface PriorityControlProps {
70+
minimumPriority?: DetectorPriorityLevel;
71+
}
72+
73+
export default function PriorityControl({
74+
minimumPriority = DetectorPriorityLevel.LOW,
75+
}: PriorityControlProps) {
4876
// TODO: kind type not yet available from detector types
49-
const detectorKind = useFormField<string>('kind')!;
50-
const conditionResult =
51-
useFormField<PriorityLevel>('conditionGroup.conditions.0.conditionResult') ||
52-
PriorityLevel.LOW;
77+
const detectorKind = useMetricDetectorFormField(METRIC_DETECTOR_FORM_FIELDS.kind);
78+
const initialPriorityLevel = useMetricDetectorFormField(
79+
METRIC_DETECTOR_FORM_FIELDS.initialPriorityLevel
80+
);
5381

5482
return (
5583
<Grid>
@@ -64,9 +92,9 @@ export default function PriorityControl() {
6492
<SecondaryLabel>({t('issue created')})</SecondaryLabel>
6593
</Flex>
6694
}
67-
right={<PrioritySelect />}
95+
right={<InitialPrioritySelect minimumPriority={minimumPriority} />}
6896
/>
69-
{priorityIsConfigurable(conditionResult, PriorityLevel.MEDIUM) && (
97+
{priorityIsConfigurable(initialPriorityLevel, DetectorPriorityLevel.MEDIUM) && (
7098
<PrioritizeRow
7199
left={
72100
<NumberField
@@ -77,14 +105,15 @@ export default function PriorityControl() {
77105
size="sm"
78106
suffix="s"
79107
placeholder="0"
80-
name={`conditionGroup.conditions.1.comparison`}
108+
name={METRIC_DETECTOR_FORM_FIELDS.mediumThreshold}
81109
data-test-id="priority-control-medium"
110+
required
82111
/>
83112
}
84113
right={<GroupPriorityBadge showLabel priority={PriorityLevel.MEDIUM} />}
85114
/>
86115
)}
87-
{priorityIsConfigurable(conditionResult, PriorityLevel.HIGH) && (
116+
{priorityIsConfigurable(initialPriorityLevel, DetectorPriorityLevel.HIGH) && (
88117
<PrioritizeRow
89118
left={
90119
<NumberField
@@ -95,8 +124,9 @@ export default function PriorityControl() {
95124
size="sm"
96125
suffix="s"
97126
placeholder="0"
98-
name={`conditionGroup.conditions.2.comparison`}
127+
name={METRIC_DETECTOR_FORM_FIELDS.highThreshold}
99128
data-test-id="priority-control-high"
129+
required
100130
/>
101131
}
102132
right={<GroupPriorityBadge showLabel priority={PriorityLevel.HIGH} />}
@@ -107,18 +137,10 @@ export default function PriorityControl() {
107137
}
108138

109139
function priorityIsConfigurable(
110-
createdPriority: PriorityLevel,
111-
targetPriority: PriorityLevel
140+
initialPriorityLevel: DetectorPriorityLevel,
141+
targetPriority: DetectorPriorityLevel
112142
): boolean {
113-
if (createdPriority === PriorityLevel.LOW) {
114-
return (
115-
targetPriority === PriorityLevel.MEDIUM || targetPriority === PriorityLevel.HIGH
116-
);
117-
}
118-
if (createdPriority === PriorityLevel.MEDIUM) {
119-
return targetPriority === PriorityLevel.HIGH;
120-
}
121-
return false;
143+
return targetPriority > initialPriorityLevel;
122144
}
123145

124146
function PrioritizeRow({left, right}: {left: React.ReactNode; right: React.ReactNode}) {
@@ -135,35 +157,50 @@ function PrioritizeRow({left, right}: {left: React.ReactNode; right: React.React
135157
);
136158
}
137159

138-
const priorities = [PriorityLevel.LOW, PriorityLevel.MEDIUM, PriorityLevel.HIGH];
139-
140-
function PrioritySelect() {
160+
function InitialPrioritySelect({
161+
minimumPriority,
162+
}: {
163+
minimumPriority: DetectorPriorityLevel;
164+
}) {
141165
const formContext = useContext(FormContext);
142-
const conditionResult =
143-
useFormField<PriorityLevel>('conditionGroup.conditions.0.conditionResult') ||
144-
PriorityLevel.LOW;
166+
const initialPriorityLevel = useMetricDetectorFormField(
167+
METRIC_DETECTOR_FORM_FIELDS.initialPriorityLevel
168+
);
145169

146170
return (
147171
<CompactSelect
148172
size="xs"
149173
trigger={(props, isOpen) => {
150174
return (
151-
<EmptyButton {...props}>
152-
<GroupPriorityBadge showLabel priority={conditionResult}>
175+
<EmptyButton type="button" {...props}>
176+
<GroupPriorityBadge
177+
showLabel
178+
priority={DETECTOR_PRIORITY_LEVEL_TO_PRIORITY_LEVEL[initialPriorityLevel]}
179+
>
153180
<InteractionStateLayer isPressed={isOpen} />
154181
<IconChevron direction={isOpen ? 'up' : 'down'} size="xs" />
155182
</GroupPriorityBadge>
156183
</EmptyButton>
157184
);
158185
}}
159-
options={priorities.map(priority => ({
160-
label: <GroupPriorityBadge showLabel priority={priority} />,
161-
value: priority,
162-
textValue: priority,
163-
}))}
164-
value={conditionResult}
186+
options={priorities
187+
.filter(priority => priority >= minimumPriority)
188+
.map(priority => ({
189+
label: (
190+
<GroupPriorityBadge
191+
showLabel
192+
priority={DETECTOR_PRIORITY_LEVEL_TO_PRIORITY_LEVEL[priority]}
193+
/>
194+
),
195+
value: priority,
196+
textValue: DETECTOR_PRIORITY_LEVEL_TO_PRIORITY_LEVEL[priority],
197+
}))}
198+
value={initialPriorityLevel}
165199
onChange={({value}) => {
166-
formContext.form?.setValue('conditionGroup.conditions.0.conditionResult', value);
200+
formContext.form?.setValue(
201+
METRIC_DETECTOR_FORM_FIELDS.initialPriorityLevel,
202+
value
203+
);
167204
}}
168205
/>
169206
);

static/app/types/workflowEngine/dataConditions.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,21 @@ export enum DataConditionGroupLogicType {
5252
NONE = 'none',
5353
}
5454

55+
export enum DetectorPriorityLevel {
56+
OK = 0,
57+
LOW = 25,
58+
MEDIUM = 50,
59+
HIGH = 75,
60+
}
61+
5562
/**
5663
* See DataConditionSerializer
5764
*/
5865
export interface DataCondition {
5966
comparison: any;
6067
id: string;
6168
type: DataConditionType;
62-
conditionResult?: any;
69+
conditionResult?: DetectorPriorityLevel;
6370
}
6471

6572
export interface DataConditionGroup {

static/app/views/detectors/components/detectorTypeForm.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function getDefaultProject(projects: Project[]) {
1919

2020
export function DetectorTypeForm() {
2121
return (
22-
<Flex column>
22+
<FormContainer>
2323
<Header>
2424
<h3>{t('Project and Environment')}</h3>
2525
</Header>
@@ -35,7 +35,7 @@ export function DetectorTypeForm() {
3535
</p>
3636
</Header>
3737
<MonitorTypeField />
38-
</Flex>
38+
</FormContainer>
3939
);
4040
}
4141

@@ -138,6 +138,12 @@ function MonitorTypeField() {
138138
);
139139
}
140140

141+
const FormContainer = styled('div')`
142+
display: flex;
143+
flex-direction: column;
144+
max-width: ${p => p.theme.breakpoints.xlarge};
145+
`;
146+
141147
const StyledProjectField = styled(SentryProjectSelectorField)`
142148
flex-grow: 1;
143149
max-width: 360px;

static/app/views/detectors/components/forms/editableDetectorTitle.tsx renamed to static/app/views/detectors/components/forms/editableDetectorName.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import EditableText from 'sentry/components/editableText';
22
import FormField from 'sentry/components/forms/formField';
33
import {t} from 'sentry/locale';
44

5-
export function EditableDetectorTitle() {
5+
export function EditableDetectorName() {
66
return (
7-
<FormField name="title" inline={false} flexibleControlStateSize stacked>
7+
<FormField name="name" inline={false} flexibleControlStateSize stacked>
88
{({onChange, value}) => (
99
<EditableText
1010
isDisabled={false}

0 commit comments

Comments
 (0)