Skip to content

Commit 8f3b48b

Browse files
authored
feat(aci): add data condition warning about occurrence-based monitors (#95372)
added a `warningMessage` field to the data condition map so that we can add other/different messages in the future if necessary <img width="942" height="405" alt="Screenshot 2025-07-11 at 1 34 24 PM" src="https://github.com/user-attachments/assets/83171270-363d-4b0f-bf0e-613ad9c1ebe0" />
1 parent 95d3bcd commit 8f3b48b

File tree

3 files changed

+95
-16
lines changed

3 files changed

+95
-16
lines changed

static/app/views/automations/components/dataConditionNodeList.spec.tsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {DataConditionFixture} from 'sentry-fixture/automations';
22
import {OrganizationFixture} from 'sentry-fixture/organization';
33
import {DataConditionHandlerFixture} from 'sentry-fixture/workflowEngine';
44

5-
import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
5+
import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
66

77
import type {DataConditionHandler} from 'sentry/types/workflowEngine/dataConditions';
88
import {
@@ -24,7 +24,7 @@ const dataConditionHandlers: DataConditionHandler[] = [
2424
type: DataConditionType.ISSUE_PRIORITY_DEESCALATING,
2525
}),
2626
DataConditionHandlerFixture({
27-
type: DataConditionType.TAGGED_EVENT,
27+
type: DataConditionType.EVENT_FREQUENCY,
2828
handlerSubgroup: DataConditionHandlerSubgroupType.EVENT_ATTRIBUTES,
2929
}),
3030
];
@@ -86,7 +86,9 @@ describe('DataConditionNodeList', function () {
8686
expect(
8787
screen.getByRole('menuitemradio', {name: 'Issue priority'})
8888
).toBeInTheDocument();
89-
expect(screen.getByRole('menuitemradio', {name: 'Tagged event'})).toBeInTheDocument();
89+
expect(
90+
screen.getByRole('menuitemradio', {name: 'Number of events'})
91+
).toBeInTheDocument();
9092
});
9193

9294
it('adds conditions', async function () {
@@ -289,4 +291,33 @@ describe('DataConditionNodeList', function () {
289291

290292
expect(screen.getByText(errorMessage)).toBeInTheDocument();
291293
});
294+
295+
it('shows warning message for occurrence-based monitors', async function () {
296+
render(
297+
<AutomationBuilderErrorContext.Provider
298+
value={{errors: {}, setErrors: jest.fn(), removeError: jest.fn()}}
299+
>
300+
<AutomationBuilderConflictContext.Provider value={defaultConflictContextProps}>
301+
<DataConditionNodeList {...defaultProps} />
302+
</AutomationBuilderConflictContext.Provider>
303+
</AutomationBuilderErrorContext.Provider>,
304+
{organization}
305+
);
306+
307+
// Open dropdown
308+
await userEvent.click(screen.getByRole('textbox', {name: 'Add condition'}));
309+
// Find the "Number of events" option which should have a warning icon
310+
const numberOfEventsOption = screen.getByRole('menuitemradio', {
311+
name: 'Number of events',
312+
});
313+
// Hover over the warning icon within the "Number of events" option
314+
const warningIcon = within(numberOfEventsOption).getByRole('img');
315+
await userEvent.hover(warningIcon);
316+
317+
expect(
318+
await screen.findByText(
319+
'These filters will only apply to some of your monitors and triggers.'
320+
)
321+
).toBeInTheDocument();
322+
});
292323
});

static/app/views/automations/components/dataConditionNodeList.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import styled from '@emotion/styled';
44
import {Alert} from 'sentry/components/core/alert';
55
import {Checkbox} from 'sentry/components/core/checkbox';
66
import {Select} from 'sentry/components/core/select';
7+
import {Tooltip} from 'sentry/components/core/tooltip';
8+
import {IconWarning} from 'sentry/icons';
79
import {t} from 'sentry/locale';
810
import type {DataCondition} from 'sentry/types/workflowEngine/dataConditions';
911
import {
@@ -17,7 +19,6 @@ import AutomationBuilderRow from 'sentry/views/automations/components/automation
1719
import {
1820
DataConditionNodeContext,
1921
dataConditionNodesMap,
20-
frequencyTypeMapping,
2122
useDataConditionNodeContext,
2223
} from 'sentry/views/automations/components/dataConditionNodes';
2324
import {useDataConditionsQuery} from 'sentry/views/automations/hooks';
@@ -80,10 +81,19 @@ export default function DataConditionNodeList({
8081
}
8182

8283
const conditionType = frequencyTypeMapping[handler.type] || handler.type;
84+
const conditionLabel = dataConditionNodesMap.get(conditionType)?.label;
85+
const WarningMessage = dataConditionNodesMap.get(conditionType)?.warningMessage;
8386

8487
const newDataCondition: Option = {
8588
value: conditionType,
86-
label: dataConditionNodesMap.get(handler.type)?.label || handler.type,
89+
label: conditionLabel || handler.type,
90+
...(WarningMessage && {
91+
trailingItems: (
92+
<Tooltip title={<WarningMessage />}>
93+
<IconWarning />
94+
</Tooltip>
95+
),
96+
}),
8797
};
8898

8999
if (handler.handlerSubgroup === DataConditionHandlerSubgroupType.EVENT_ATTRIBUTES) {
@@ -218,6 +228,21 @@ function Node() {
218228
return Component ? <Component /> : node?.label;
219229
}
220230

231+
/**
232+
* Maps COUNT and PERCENT frequency conditions to their base frequency type.
233+
* This is used in the UI to show both conditions as a single branching condition.
234+
*/
235+
const frequencyTypeMapping: Partial<Record<DataConditionType, DataConditionType>> = {
236+
[DataConditionType.PERCENT_SESSIONS_COUNT]: DataConditionType.PERCENT_SESSIONS,
237+
[DataConditionType.PERCENT_SESSIONS_PERCENT]: DataConditionType.PERCENT_SESSIONS,
238+
[DataConditionType.EVENT_FREQUENCY_COUNT]: DataConditionType.EVENT_FREQUENCY,
239+
[DataConditionType.EVENT_FREQUENCY_PERCENT]: DataConditionType.EVENT_FREQUENCY,
240+
[DataConditionType.EVENT_UNIQUE_USER_FREQUENCY_COUNT]:
241+
DataConditionType.EVENT_UNIQUE_USER_FREQUENCY,
242+
[DataConditionType.EVENT_UNIQUE_USER_FREQUENCY_PERCENT]:
243+
DataConditionType.EVENT_UNIQUE_USER_FREQUENCY,
244+
};
245+
221246
const StyledSelectControl = styled(Select)`
222247
width: 100%;
223248
`;

static/app/views/automations/components/dataConditionNodes.tsx

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type React from 'react';
22
import {createContext, useContext} from 'react';
3+
import styled from '@emotion/styled';
34

5+
import {Flex} from 'sentry/components/core/layout';
46
import {t} from 'sentry/locale';
7+
import {space} from 'sentry/styles/space';
58
import {
69
type DataCondition,
710
DataConditionType,
@@ -109,6 +112,7 @@ type DataConditionNode = {
109112
dataCondition?: React.ComponentType<any>;
110113
defaultComparison?: any;
111114
details?: React.ComponentType<any>;
115+
warningMessage?: React.ComponentType<any>;
112116
};
113117

114118
export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNode>([
@@ -212,6 +216,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
212216
environment: '',
213217
},
214218
validate: validateLatestAdoptedReleaseCondition,
219+
warningMessage: OccurenceBasedMonitorsWarning,
215220
},
216221
],
217222
[
@@ -221,6 +226,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
221226
dataCondition: LatestReleaseNode,
222227
details: LatestReleaseNode,
223228
validate: undefined,
229+
warningMessage: OccurenceBasedMonitorsWarning,
224230
},
225231
],
226232
[
@@ -234,6 +240,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
234240
match: MatchType.CONTAINS,
235241
},
236242
validate: validateEventAttributeCondition,
243+
warningMessage: OccurenceBasedMonitorsWarning,
237244
},
238245
],
239246
[
@@ -264,6 +271,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
264271
label: t('Number of events'),
265272
dataCondition: EventFrequencyNode,
266273
validate: validateEventFrequencyCondition,
274+
warningMessage: OccurenceBasedMonitorsWarning,
267275
},
268276
],
269277
[
@@ -274,6 +282,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
274282
details: EventFrequencyCountDetails,
275283
defaultComparison: {value: 100, interval: Interval.ONE_HOUR},
276284
validate: validateEventFrequencyCondition,
285+
warningMessage: OccurenceBasedMonitorsWarning,
277286
},
278287
],
279288
[
@@ -288,6 +297,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
288297
comparison_interval: Interval.ONE_WEEK,
289298
},
290299
validate: validateEventFrequencyCondition,
300+
warningMessage: OccurenceBasedMonitorsWarning,
291301
},
292302
],
293303
[
@@ -296,6 +306,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
296306
label: t('Number of users affected'),
297307
dataCondition: EventUniqueUserFrequencyNode,
298308
validate: validateEventUniqueUserFrequencyCondition,
309+
warningMessage: OccurenceBasedMonitorsWarning,
299310
},
300311
],
301312
[
@@ -306,6 +317,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
306317
details: EventUniqueUserFrequencyCountDetails,
307318
defaultComparison: {value: 100, interval: Interval.ONE_HOUR},
308319
validate: validateEventUniqueUserFrequencyCondition,
320+
warningMessage: OccurenceBasedMonitorsWarning,
309321
},
310322
],
311323
[
@@ -320,6 +332,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
320332
comparison_interval: Interval.ONE_WEEK,
321333
},
322334
validate: validateEventUniqueUserFrequencyCondition,
335+
warningMessage: OccurenceBasedMonitorsWarning,
323336
},
324337
],
325338
[
@@ -328,6 +341,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
328341
label: t('Percentage of sessions affected'),
329342
dataCondition: PercentSessionsNode,
330343
validate: validatePercentSessionsCondition,
344+
warningMessage: OccurenceBasedMonitorsWarning,
331345
},
332346
],
333347
[
@@ -338,6 +352,7 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
338352
details: PercentSessionsCountDetails,
339353
defaultComparison: {value: 100, interval: Interval.ONE_HOUR},
340354
validate: validatePercentSessionsCondition,
355+
warningMessage: OccurenceBasedMonitorsWarning,
341356
},
342357
],
343358
[
@@ -352,18 +367,26 @@ export const dataConditionNodesMap = new Map<DataConditionType, DataConditionNod
352367
comparison_interval: Interval.ONE_WEEK,
353368
},
354369
validate: validatePercentSessionsCondition,
370+
warningMessage: OccurenceBasedMonitorsWarning,
355371
},
356372
],
357373
]);
358374

359-
export const frequencyTypeMapping: Partial<Record<DataConditionType, DataConditionType>> =
360-
{
361-
[DataConditionType.PERCENT_SESSIONS_COUNT]: DataConditionType.PERCENT_SESSIONS,
362-
[DataConditionType.PERCENT_SESSIONS_PERCENT]: DataConditionType.PERCENT_SESSIONS,
363-
[DataConditionType.EVENT_FREQUENCY_COUNT]: DataConditionType.EVENT_FREQUENCY,
364-
[DataConditionType.EVENT_FREQUENCY_PERCENT]: DataConditionType.EVENT_FREQUENCY,
365-
[DataConditionType.EVENT_UNIQUE_USER_FREQUENCY_COUNT]:
366-
DataConditionType.EVENT_UNIQUE_USER_FREQUENCY,
367-
[DataConditionType.EVENT_UNIQUE_USER_FREQUENCY_PERCENT]:
368-
DataConditionType.EVENT_UNIQUE_USER_FREQUENCY,
369-
};
375+
function OccurenceBasedMonitorsWarning() {
376+
return (
377+
<Flex direction="column" gap={space(1)}>
378+
<WarningLine>
379+
{t('These filters will only apply to some of your monitors and triggers.')}
380+
</WarningLine>
381+
<WarningLine>
382+
{t(
383+
'They are only available for occurrence-based monitors \(errors, N+1, and replay\) and only apply to the triggers "A new event is captured for an issue" and "A new issue is created."'
384+
)}
385+
</WarningLine>
386+
</Flex>
387+
);
388+
}
389+
390+
const WarningLine = styled('p')`
391+
margin: 0;
392+
`;

0 commit comments

Comments
 (0)