Skip to content

Commit 59e26ba

Browse files
authored
feat(aci): add data condition nodes to automation builder (#89928)
added more data condition nodes: - event attribute - issue priority - latest adopted release - latest release - level - tagged event also added a `dataCondition` prop to `ruleNodeList` to specify which conditions to display as dropdown options
1 parent e9387f6 commit 59e26ba

File tree

10 files changed

+535
-11
lines changed

10 files changed

+535
-11
lines changed

static/app/views/automations/components/actionFilters/constants.tsx

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,133 @@
11
import {t} from 'sentry/locale';
2-
import {DataConditionGroupLogicType} from 'sentry/types/workflowEngine/dataConditions';
2+
import {
3+
DataConditionGroupLogicType,
4+
DataConditionType,
5+
} from 'sentry/types/workflowEngine/dataConditions';
36

47
export const FILTER_MATCH_OPTIONS = [
58
{value: DataConditionGroupLogicType.ALL, label: t('all')},
69
{value: DataConditionGroupLogicType.ANY_SHORT_CIRCUIT, label: t('any')},
710
{value: DataConditionGroupLogicType.NONE, label: t('none')},
811
];
912

13+
export const FILTER_DATA_CONDITION_TYPES = [
14+
DataConditionType.AGE_COMPARISON,
15+
DataConditionType.ISSUE_OCCURRENCES,
16+
DataConditionType.ASSIGNED_TO,
17+
DataConditionType.ISSUE_PRIORITY_EQUALS,
18+
DataConditionType.LATEST_ADOPTED_RELEASE,
19+
DataConditionType.LATEST_RELEASE,
20+
DataConditionType.EVENT_ATTRIBUTE,
21+
DataConditionType.TAGGED_EVENT,
22+
DataConditionType.LEVEL,
23+
];
24+
25+
export enum MatchType {
26+
CONTAINS = 'co',
27+
ENDS_WITH = 'ew',
28+
EQUAL = 'eq',
29+
GREATER_OR_EQUAL = 'gte',
30+
GREATER = 'gt',
31+
IS_SET = 'is',
32+
IS_IN = 'in',
33+
LESS_OR_EQUAL = 'lte',
34+
LESS = 'lt',
35+
NOT_CONTAINS = 'nc',
36+
NOT_ENDS_WITH = 'new',
37+
NOT_EQUAL = 'ne',
38+
NOT_SET = 'ns',
39+
NOT_STARTS_WITH = 'nsw',
40+
NOT_IN = 'nin',
41+
STARTS_WITH = 'sw',
42+
}
43+
44+
export enum Assignee {
45+
UNASSIGNED = 'Unassigned',
46+
TEAM = 'Team',
47+
MEMBER = 'Member',
48+
}
49+
50+
export enum Priority {
51+
LOW = 25,
52+
MEDIUM = 50,
53+
HIGH = 75,
54+
}
55+
1056
export enum AgeComparison {
1157
OLDER = 'older',
1258
NEWER = 'newer',
1359
}
1460

61+
export enum ModelAge {
62+
OLDEST = 'oldest',
63+
NEWEST = 'newest',
64+
}
65+
66+
export enum Attributes {
67+
MESSAGE = 'message',
68+
PLATFORM = 'platform',
69+
ENVIRONMENT = 'environment',
70+
TYPE = 'type',
71+
ERROR_HANDLED = 'error.handled',
72+
ERROR_UNHANDLED = 'error.unhandled',
73+
ERROR_MAIN_THREAD = 'error.main_thread',
74+
EXCEPTION_TYPE = 'exception.type',
75+
ERROR_VALUE = 'exception.value',
76+
USER_ID = 'user.id',
77+
USER_EMAIL = 'user.email',
78+
USER_USERNAME = 'user.username',
79+
USER_IP_ADDRESS = 'user.ip_address',
80+
HTTP_METHOD = 'http.method',
81+
HTTP_URL = 'http.url',
82+
HTTP_STATUS_CODE = 'http.status_code',
83+
SDK_NAME = 'sdk.name',
84+
STACKTRACE_CODE = 'stacktrace.code',
85+
STACKTRACE_MODULE = 'stacktrace.module',
86+
STACKTRACE_FILENAME = 'stacktrace.filename',
87+
STACKTRACE_ABS_PATH = 'stacktrace.abs_path',
88+
STACKTRACE_PACKAGE = 'stacktrace.package',
89+
UNREAL_CRASH_TYPE = 'unreal.crash_type',
90+
APP_IN_FOREGROUND = 'app.in_foreground',
91+
OS_DISTRIBUTION_NAME = 'os.distribution_name',
92+
OS_DISTRIBUTION_VERSION = 'os.distribution_version',
93+
}
94+
95+
export enum Level {
96+
FATAL = 50,
97+
ERROR = 40,
98+
WARNING = 30,
99+
INFO = 20,
100+
DEBUG = 10,
101+
SAMPLING = 0,
102+
}
103+
104+
export const MATCH_CHOICES = [
105+
{value: MatchType.CONTAINS, label: 'contains'},
106+
{value: MatchType.EQUAL, label: 'equals'},
107+
{value: MatchType.STARTS_WITH, label: 'starts with'},
108+
{value: MatchType.ENDS_WITH, label: 'ends with'},
109+
{value: MatchType.NOT_CONTAINS, label: 'does not contain'},
110+
{value: MatchType.NOT_EQUAL, label: 'does not equal'},
111+
{value: MatchType.NOT_STARTS_WITH, label: 'does not start with'},
112+
{value: MatchType.NOT_ENDS_WITH, label: 'does not end with'},
113+
{value: MatchType.IS_SET, label: 'is set'},
114+
{value: MatchType.NOT_SET, label: 'is not set'},
115+
{value: MatchType.IS_IN, label: 'is one of'},
116+
{value: MatchType.NOT_IN, label: 'is not one of'},
117+
];
118+
119+
export const ASSIGNEE_CHOICES = [
120+
{value: Assignee.UNASSIGNED, label: t('unassigned')},
121+
{value: Assignee.MEMBER, label: t('member')},
122+
{value: Assignee.TEAM, label: t('team')},
123+
];
124+
125+
export const PRIORITY_CHOICES = [
126+
{value: Priority.HIGH, label: t('high')},
127+
{value: Priority.MEDIUM, label: t('medium')},
128+
{value: Priority.LOW, label: t('low')},
129+
];
130+
15131
export const AGE_COMPARISON_CHOICES = [
16132
{
17133
value: AgeComparison.OLDER,
@@ -22,3 +138,28 @@ export const AGE_COMPARISON_CHOICES = [
22138
label: t('newer than'),
23139
},
24140
];
141+
142+
export const MODEL_AGE_CHOICES = [
143+
{
144+
value: ModelAge.OLDEST,
145+
label: t('oldest'),
146+
},
147+
{
148+
value: ModelAge.NEWEST,
149+
label: t('newest'),
150+
},
151+
];
152+
153+
export const LEVEL_MATCH_CHOICES = [
154+
{value: MatchType.EQUAL, label: t('equals')},
155+
{value: MatchType.NOT_EQUAL, label: t('does not equal')},
156+
];
157+
158+
export const LEVEL_CHOICES = [
159+
{value: Level.FATAL, label: t('fatal')},
160+
{value: Level.ERROR, label: t('error')},
161+
{value: Level.WARNING, label: t('warning')},
162+
{value: Level.INFO, label: t('info')},
163+
{value: Level.DEBUG, label: t('debug')},
164+
{value: Level.SAMPLING, label: t('sampling')},
165+
];
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {t, tct} from 'sentry/locale';
2+
import {
3+
Attributes,
4+
MATCH_CHOICES,
5+
type MatchType,
6+
} from 'sentry/views/automations/components/actionFilters/constants';
7+
import {
8+
InlineInputField,
9+
InlineSelectControl,
10+
selectControlStyles,
11+
useDataConditionNodeContext,
12+
} from 'sentry/views/automations/components/dataConditionNodes';
13+
14+
export default function EventAttributeNode() {
15+
return tct("The event's [attribute] [match] [value]", {
16+
attribute: <AttributeField />,
17+
match: <MatchField />,
18+
value: <ValueField />,
19+
});
20+
}
21+
22+
function AttributeField() {
23+
const {condition, condition_id, onUpdate} = useDataConditionNodeContext();
24+
return (
25+
<InlineSelectControl
26+
styles={selectControlStyles}
27+
name={`${condition_id}.comparison.attribute`}
28+
placeholder={t('attribute')}
29+
value={condition.comparison.attribute}
30+
options={Object.values(Attributes).map(attribute => ({
31+
value: attribute,
32+
label: attribute,
33+
}))}
34+
onChange={(value: Attributes) => {
35+
onUpdate({
36+
attribute: value,
37+
});
38+
}}
39+
/>
40+
);
41+
}
42+
43+
function MatchField() {
44+
const {condition, condition_id, onUpdate} = useDataConditionNodeContext();
45+
return (
46+
<InlineSelectControl
47+
styles={selectControlStyles}
48+
name={`${condition_id}.comparison.match`}
49+
value={condition.comparison.match}
50+
options={MATCH_CHOICES}
51+
onChange={(value: MatchType) => {
52+
onUpdate({
53+
match: value,
54+
});
55+
}}
56+
/>
57+
);
58+
}
59+
60+
function ValueField() {
61+
const {condition, condition_id, onUpdate} = useDataConditionNodeContext();
62+
return (
63+
<InlineInputField
64+
name={`${condition_id}.comparison.value`}
65+
placeholder={t('value')}
66+
value={condition.comparison.value}
67+
onChange={(value: string) => {
68+
onUpdate({
69+
value,
70+
});
71+
}}
72+
/>
73+
);
74+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import {tct} from 'sentry/locale';
2+
import {
3+
type Priority,
4+
PRIORITY_CHOICES,
5+
} from 'sentry/views/automations/components/actionFilters/constants';
6+
import {
7+
InlineSelectControl,
8+
selectControlStyles,
9+
useDataConditionNodeContext,
10+
} from 'sentry/views/automations/components/dataConditionNodes';
11+
12+
export default function IssuePriorityNode() {
13+
const {condition, condition_id, onUpdate} = useDataConditionNodeContext();
14+
return tct('Current issue priority is [level]', {
15+
level: (
16+
<InlineSelectControl
17+
styles={selectControlStyles}
18+
name={`${condition_id}.comparison`}
19+
value={condition.comparison.match}
20+
options={PRIORITY_CHOICES}
21+
onChange={(value: Priority) => {
22+
onUpdate({
23+
match: value,
24+
});
25+
}}
26+
/>
27+
),
28+
});
29+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {t, tct} from 'sentry/locale';
2+
import type {Environment} from 'sentry/types/project';
3+
import {useApiQuery} from 'sentry/utils/queryClient';
4+
import useOrganization from 'sentry/utils/useOrganization';
5+
import {
6+
AGE_COMPARISON_CHOICES,
7+
type AgeComparison,
8+
MODEL_AGE_CHOICES,
9+
type ModelAge,
10+
} from 'sentry/views/automations/components/actionFilters/constants';
11+
import {
12+
InlineSelectControl,
13+
selectControlStyles,
14+
useDataConditionNodeContext,
15+
} from 'sentry/views/automations/components/dataConditionNodes';
16+
17+
export default function LatestAdoptedReleaseNode() {
18+
return tct(
19+
"The [releaseAgeType] adopted release associated with the event's issue is [ageComparison] the latest adopted release in [environment]",
20+
{
21+
releaseAgeType: <ReleaseAgeTypeField />,
22+
ageComparison: <AgeComparisonField />,
23+
environment: <EnvironmentField />,
24+
}
25+
);
26+
}
27+
28+
function ReleaseAgeTypeField() {
29+
const {condition, condition_id, onUpdate} = useDataConditionNodeContext();
30+
return (
31+
<InlineSelectControl
32+
styles={selectControlStyles}
33+
name={`${condition_id}.comparison.releaseAgeType`}
34+
value={condition.comparison.match}
35+
options={MODEL_AGE_CHOICES}
36+
onChange={(value: ModelAge) => {
37+
onUpdate({
38+
match: value,
39+
});
40+
}}
41+
/>
42+
);
43+
}
44+
45+
function AgeComparisonField() {
46+
const {condition, condition_id, onUpdate} = useDataConditionNodeContext();
47+
return (
48+
<InlineSelectControl
49+
styles={selectControlStyles}
50+
name={`${condition_id}.comparison.ageComparison`}
51+
value={condition.comparison.match}
52+
options={AGE_COMPARISON_CHOICES}
53+
onChange={(value: AgeComparison) => {
54+
onUpdate({
55+
match: value,
56+
});
57+
}}
58+
/>
59+
);
60+
}
61+
62+
function EnvironmentField() {
63+
const {condition, condition_id, onUpdate} = useDataConditionNodeContext();
64+
65+
const {environments} = useOrganizationEnvironments();
66+
const environmentOptions = environments.map(({id, name}) => ({
67+
value: id,
68+
label: name,
69+
}));
70+
71+
return (
72+
<InlineSelectControl
73+
name={`${condition_id}.comparison.environment`}
74+
value={condition.comparison.environment}
75+
options={environmentOptions}
76+
placeholder={t('environment')}
77+
onChange={(value: string) => {
78+
onUpdate({
79+
environment: value,
80+
});
81+
}}
82+
/>
83+
);
84+
}
85+
86+
function useOrganizationEnvironments() {
87+
const organization = useOrganization();
88+
const {data: environments = [], isLoading} = useApiQuery<Environment[]>(
89+
[
90+
`/organizations/${organization.slug}/environments/`,
91+
{query: {visibility: 'visible'}},
92+
],
93+
{
94+
staleTime: 30_000,
95+
}
96+
);
97+
return {environments, isLoading};
98+
}

0 commit comments

Comments
 (0)