Skip to content

Commit 29615af

Browse files
authored
feat(aci): Add above/below controls to monitor builder (#93039)
1 parent 1774b19 commit 29615af

File tree

4 files changed

+202
-124
lines changed

4 files changed

+202
-124
lines changed

static/app/components/workflowEngine/form/control/index.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default Storybook.story('Form Controls', story => {
1616

1717
<Form hideFooter>
1818
<Flex column gap={space(2)}>
19-
<PriorityControl name="priority" />
19+
<PriorityControl />
2020
</Flex>
2121
</Form>
2222
</Fragment>
Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,89 @@
11
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
22

3+
import Form from 'sentry/components/forms/form';
4+
import FormModel from 'sentry/components/forms/model';
35
import PriorityControl from 'sentry/components/workflowEngine/form/control/priorityControl';
46
import {PriorityLevel} from 'sentry/types/group';
57

68
describe('PriorityControl', function () {
79
it('renders children', async function () {
8-
render(<PriorityControl name="priority" />);
10+
const formModel = new FormModel({
11+
initialData: {
12+
'conditionGroup.conditions.0.type': 'above',
13+
'conditionGroup.conditions.0.comparison': '0',
14+
'conditionGroup.conditions.0.conditionResult': PriorityLevel.LOW,
15+
},
16+
});
17+
render(
18+
<Form model={formModel} hideFooter>
19+
<PriorityControl />
20+
</Form>
21+
);
922

10-
expect(await screen.findByText('Issue created')).toBeInTheDocument();
23+
expect(await screen.findByText('Above 0s')).toBeInTheDocument();
1124
expect(await screen.findByTestId('priority-control-medium')).toBeInTheDocument();
1225
expect(await screen.findByTestId('priority-control-high')).toBeInTheDocument();
1326
});
27+
1428
it('allows configuring priority', async function () {
15-
const mock = jest.fn();
16-
render(<PriorityControl onPriorityChange={mock} name="priority" />);
17-
await userEvent.click(await screen.findByRole('button'));
29+
const formModel = new FormModel({
30+
initialData: {
31+
'conditionGroup.conditions.0.type': 'above',
32+
'conditionGroup.conditions.0.comparison': '0',
33+
'conditionGroup.conditions.0.conditionResult': PriorityLevel.LOW,
34+
},
35+
});
36+
render(
37+
<Form model={formModel} hideFooter>
38+
<PriorityControl />
39+
</Form>
40+
);
41+
expect(await screen.findByRole('button', {name: 'Low'})).toBeInTheDocument();
42+
expect(screen.getByText('Med')).toBeInTheDocument();
43+
expect(screen.getByText('High')).toBeInTheDocument();
44+
45+
await userEvent.click(screen.getByRole('button', {name: 'Low'}));
1846
await userEvent.click(await screen.findByRole('option', {name: 'High'}));
19-
expect(mock).toHaveBeenCalledWith(PriorityLevel.HIGH);
47+
expect(formModel.getValue('conditionGroup.conditions.0.conditionResult')).toBe(
48+
PriorityLevel.HIGH
49+
);
50+
// Check that the medium threshold is not visible
51+
expect(screen.getAllByRole('button')).toHaveLength(1);
2052
});
53+
2154
it('allows configuring medium threshold', async function () {
22-
const mock = jest.fn();
23-
render(<PriorityControl onThresholdChange={mock} name="priority" />);
55+
const formModel = new FormModel({
56+
initialData: {
57+
'conditionGroup.conditions.0.type': 'above',
58+
'conditionGroup.conditions.0.comparison': '0',
59+
'conditionGroup.conditions.0.conditionResult': PriorityLevel.LOW,
60+
},
61+
});
62+
render(
63+
<Form model={formModel} hideFooter>
64+
<PriorityControl />
65+
</Form>
66+
);
2467
const medium = await screen.findByTestId('priority-control-medium');
2568
await userEvent.type(medium, '12');
26-
expect(mock).toHaveBeenCalledWith(PriorityLevel.MEDIUM, 12);
69+
expect(formModel.getValue('conditionGroup.conditions.1.comparison')).toBe('12');
2770
});
2871

2972
it('allows configuring high value', async function () {
30-
const mock = jest.fn();
31-
render(<PriorityControl onThresholdChange={mock} name="priority" />);
73+
const formModel = new FormModel({
74+
initialData: {
75+
'conditionGroup.conditions.0.type': 'above',
76+
'conditionGroup.conditions.0.comparison': '0',
77+
'conditionGroup.conditions.0.conditionResult': PriorityLevel.LOW,
78+
},
79+
});
80+
render(
81+
<Form model={formModel} hideFooter>
82+
<PriorityControl />
83+
</Form>
84+
);
3285
const high = await screen.findByTestId('priority-control-high');
3386
await userEvent.type(high, '12');
34-
expect(mock).toHaveBeenCalledWith(PriorityLevel.HIGH, 12);
87+
expect(formModel.getValue('conditionGroup.conditions.2.comparison')).toBe('12');
3588
});
3689
});
Lines changed: 66 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,72 @@
1-
import {useCallback, useState} from 'react';
1+
import {useContext} from 'react';
22
import styled from '@emotion/styled';
33

44
import {GroupPriorityBadge} from 'sentry/components/badge/groupPriority';
55
import {Flex} from 'sentry/components/container/flex';
6-
import {CompactSelect, type SelectOption} from 'sentry/components/core/compactSelect';
6+
import {CompactSelect} from 'sentry/components/core/compactSelect';
77
import {FieldWrapper} from 'sentry/components/forms/fieldGroup/fieldWrapper';
88
import NumberField from 'sentry/components/forms/fields/numberField';
9+
import FormContext from 'sentry/components/forms/formContext';
910
import InteractionStateLayer from 'sentry/components/interactionStateLayer';
11+
import {useFormField} from 'sentry/components/workflowEngine/form/hooks';
1012
import {IconArrow, IconChevron} from 'sentry/icons';
1113
import {t} from 'sentry/locale';
1214
import {space} from 'sentry/styles/space';
1315
import {PriorityLevel} from 'sentry/types/group';
1416

15-
interface PriorityControlGridProps {
16-
name: string;
17-
onPriorityChange?: (value: PriorityLevel) => void;
18-
onThresholdChange?: (level: PriorityLevel, threshold: number) => void;
19-
priority?: PriorityLevel;
20-
thresholds?: PriorityThresholds;
17+
function ThresholdPriority() {
18+
const lowThresholdDirection = useFormField<string>('conditionGroup.conditions.0.type')!;
19+
const lowThreshold = useFormField<string>('conditionGroup.conditions.0.comparison')!;
20+
return (
21+
<div>
22+
{lowThresholdDirection === ''
23+
? t('Above')
24+
: lowThresholdDirection === 'above'
25+
? t('Above')
26+
: t('Below')}{' '}
27+
{lowThreshold === '' ? '0s' : lowThreshold + 's'}
28+
</div>
29+
);
2130
}
2231

23-
interface PriorityThresholds {
24-
high?: number;
25-
medium?: number;
32+
function ChangePriority() {
33+
const lowThresholdDirection = useFormField<string>('conditionGroup.conditions.0.type')!;
34+
const lowThreshold = useFormField<string>('conditionGroup.conditions.0.comparison')!;
35+
return (
36+
<div>
37+
{lowThreshold === '' ? '0' : lowThreshold}%{' '}
38+
{lowThresholdDirection === ''
39+
? t('higher')
40+
: lowThresholdDirection === 'higher'
41+
? t('higher')
42+
: t('lower')}
43+
</div>
44+
);
2645
}
2746

28-
export default function PriorityControl({
29-
name,
30-
priority: initialPriority,
31-
onPriorityChange,
32-
thresholds: initialThresholds,
33-
onThresholdChange,
34-
}: PriorityControlGridProps) {
35-
const [priority, setPriority] = useState<PriorityLevel>(
36-
initialPriority ?? PriorityLevel.LOW
37-
);
38-
const [thresholds, setThresholds] = useState<PriorityThresholds>(
39-
initialThresholds ?? {}
40-
);
41-
const setCreatedPriority = useCallback(
42-
(level: PriorityLevel) => {
43-
setPriority(level);
44-
onPriorityChange?.(level);
45-
},
46-
[setPriority, onPriorityChange]
47-
);
48-
const setMediumThreshold = useCallback(
49-
(threshold: number) => {
50-
setThresholds(v => ({...v, [PriorityLevel.MEDIUM]: threshold}));
51-
onThresholdChange?.(PriorityLevel.MEDIUM, threshold);
52-
},
53-
[setThresholds, onThresholdChange]
54-
);
55-
const setHighThreshold = useCallback(
56-
(threshold: number) => {
57-
setThresholds(v => ({...v, [PriorityLevel.HIGH]: threshold}));
58-
onThresholdChange?.(PriorityLevel.HIGH, threshold);
59-
},
60-
[setThresholds, onThresholdChange]
61-
);
47+
export default function PriorityControl() {
48+
// 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;
6253

6354
return (
6455
<Grid>
6556
<PrioritizeRow
66-
left={<span style={{textAlign: 'right'}}>{t('Issue created')}</span>}
67-
right={<PrioritySelect value={priority} onChange={setCreatedPriority} />}
57+
left={
58+
<Flex align="center" column>
59+
{!detectorKind || detectorKind === 'threshold' ? (
60+
<ThresholdPriority />
61+
) : (
62+
<ChangePriority />
63+
)}
64+
<SecondaryLabel>({t('issue created')})</SecondaryLabel>
65+
</Flex>
66+
}
67+
right={<PrioritySelect />}
6868
/>
69-
{priorityIsConfigurable(priority, PriorityLevel.MEDIUM) && (
69+
{priorityIsConfigurable(conditionResult, PriorityLevel.MEDIUM) && (
7070
<PrioritizeRow
7171
left={
7272
<NumberField
@@ -77,17 +77,14 @@ export default function PriorityControl({
7777
size="sm"
7878
suffix="s"
7979
placeholder="0"
80-
// empty string required to keep this as a controlled input
81-
value={thresholds[PriorityLevel.MEDIUM] ?? ''}
82-
onChange={threshold => setMediumThreshold(Number(threshold))}
83-
name={`${name}-medium`}
80+
name={`conditionGroup.conditions.1.comparison`}
8481
data-test-id="priority-control-medium"
8582
/>
8683
}
8784
right={<GroupPriorityBadge showLabel priority={PriorityLevel.MEDIUM} />}
8885
/>
8986
)}
90-
{priorityIsConfigurable(priority, PriorityLevel.HIGH) && (
87+
{priorityIsConfigurable(conditionResult, PriorityLevel.HIGH) && (
9188
<PrioritizeRow
9289
left={
9390
<NumberField
@@ -98,10 +95,7 @@ export default function PriorityControl({
9895
size="sm"
9996
suffix="s"
10097
placeholder="0"
101-
// empty string required to keep this as a controlled input
102-
value={thresholds[PriorityLevel.HIGH] ?? ''}
103-
onChange={threshold => setHighThreshold(Number(threshold))}
104-
name={`${name}-high`}
98+
name={`conditionGroup.conditions.2.comparison`}
10599
data-test-id="priority-control-high"
106100
/>
107101
}
@@ -143,29 +137,19 @@ function PrioritizeRow({left, right}: {left: React.ReactNode; right: React.React
143137

144138
const priorities = [PriorityLevel.LOW, PriorityLevel.MEDIUM, PriorityLevel.HIGH];
145139

146-
function PrioritySelect({
147-
value: initialValue,
148-
onChange = () => {},
149-
}: {
150-
onChange?: (value: PriorityLevel) => void;
151-
value?: PriorityLevel;
152-
}) {
153-
const [value, setValue] = useState<PriorityLevel>(initialValue ?? PriorityLevel.HIGH);
154-
const handleChange = useCallback(
155-
(select: SelectOption<PriorityLevel>) => {
156-
onChange(select.value);
157-
setValue(select.value);
158-
},
159-
[onChange, setValue]
160-
);
140+
function PrioritySelect() {
141+
const formContext = useContext(FormContext);
142+
const conditionResult =
143+
useFormField<PriorityLevel>('conditionGroup.conditions.0.conditionResult') ||
144+
PriorityLevel.LOW;
161145

162146
return (
163147
<CompactSelect
164148
size="xs"
165149
trigger={(props, isOpen) => {
166150
return (
167151
<EmptyButton {...props}>
168-
<GroupPriorityBadge showLabel priority={value}>
152+
<GroupPriorityBadge showLabel priority={conditionResult}>
169153
<InteractionStateLayer isPressed={isOpen} />
170154
<IconChevron direction={isOpen ? 'up' : 'down'} size="xs" />
171155
</GroupPriorityBadge>
@@ -177,8 +161,10 @@ function PrioritySelect({
177161
value: priority,
178162
textValue: priority,
179163
}))}
180-
value={value}
181-
onChange={handleChange}
164+
value={conditionResult}
165+
onChange={({value}) => {
166+
formContext.form?.setValue('conditionGroup.conditions.0.conditionResult', value);
167+
}}
182168
/>
183169
);
184170
}
@@ -212,3 +198,8 @@ const Cell = styled(Flex)`
212198
width: 5rem;
213199
}
214200
`;
201+
202+
const SecondaryLabel = styled('div')`
203+
font-size: ${p => p.theme.fontSizeSmall};
204+
color: ${p => p.theme.subText};
205+
`;

0 commit comments

Comments
 (0)