Skip to content

Commit cc2b447

Browse files
authored
feat(aci): automation edit scaffolding (#94595)
- added automation form to the automation edit view - the form + AutomationBuilderReducer are now able to use an existing automation to populate the form fields - setting up the edit actions (save + delete) will come in the next PR we can also remove `flattie` now since we're maintaining automation builder state outside of the form
1 parent 43ce228 commit cc2b447

File tree

14 files changed

+239
-221
lines changed

14 files changed

+239
-221
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@
119119
"echarts": "5.4.0",
120120
"echarts-for-react": "3.0.2",
121121
"esbuild": "0.25.3",
122-
"flattie": "^1.1.1",
123122
"focus-trap": "^7.3.1",
124123
"framer-motion": "12.7.3",
125124
"fuse.js": "^6.6.2",

pnpm-lock.yaml

Lines changed: 0 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

static/app/components/workflowEngine/layout/edit.tsx

Lines changed: 0 additions & 99 deletions
This file was deleted.

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

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Fragment, useMemo, useState} from 'react';
1+
import {Fragment, useMemo} from 'react';
22
import styled from '@emotion/styled';
33
import {uuid4} from '@sentry/core';
44

@@ -8,6 +8,8 @@ import {
88
type Action,
99
ActionGroup,
1010
type ActionHandler,
11+
ActionType,
12+
SentryAppIdentifier,
1113
} from 'sentry/types/workflowEngine/actions';
1214
import {
1315
ActionNodeContext,
@@ -31,6 +33,30 @@ interface Option {
3133
value: ActionHandler;
3234
}
3335

36+
function getActionHandler(
37+
action: Action,
38+
availableActions: ActionHandler[]
39+
): ActionHandler | undefined {
40+
if (action.type === ActionType.SENTRY_APP) {
41+
return availableActions.find(handler => {
42+
if (handler.type !== ActionType.SENTRY_APP) {
43+
return false;
44+
}
45+
const {sentry_app_identifier, target_identifier} = action.config;
46+
const sentryApp = handler.sentryApp;
47+
48+
const isMatchingAppId =
49+
sentry_app_identifier === SentryAppIdentifier.SENTRY_APP_ID &&
50+
target_identifier === sentryApp?.id;
51+
const isMatchingInstallationUuid =
52+
sentry_app_identifier === SentryAppIdentifier.SENTRY_APP_INSTALLATION_UUID &&
53+
target_identifier === sentryApp?.installationUuid;
54+
return isMatchingAppId || isMatchingInstallationUuid;
55+
});
56+
}
57+
return availableActions.find(handler => handler.type === action.type);
58+
}
59+
3460
export default function ActionNodeList({
3561
group,
3662
placeholder,
@@ -40,9 +66,6 @@ export default function ActionNodeList({
4066
updateAction,
4167
}: ActionNodeListProps) {
4268
const {data: availableActions = []} = useAvailableActionsQuery();
43-
const [actionHandlerMap, setActionHandlerMap] = useState<Record<string, ActionHandler>>(
44-
{}
45-
);
4669

4770
const options = useMemo(() => {
4871
const notificationActions: Option[] = [];
@@ -88,7 +111,7 @@ export default function ActionNodeList({
88111
return (
89112
<Fragment>
90113
{actions.map(action => {
91-
const handler = actionHandlerMap[action.id];
114+
const handler = getActionHandler(action, availableActions);
92115
if (!handler) {
93116
return null;
94117
}
@@ -97,7 +120,6 @@ export default function ActionNodeList({
97120
key={`${group}.action.${action.id}`}
98121
onDelete={() => {
99122
onDeleteRow(action.id);
100-
setActionHandlerMap(({[action.id]: _, ...rest}) => rest);
101123
}}
102124
>
103125
<ActionNodeContext.Provider
@@ -118,10 +140,6 @@ export default function ActionNodeList({
118140
onChange={(obj: any) => {
119141
const actionId = uuid4();
120142
onAddRow(actionId, obj.value);
121-
setActionHandlerMap(currActionHandlerMap => ({
122-
...currActionHandlerMap,
123-
[actionId]: obj.value,
124-
}));
125143
}}
126144
placeholder={placeholder}
127145
value={null}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const actionNodesMap = new Map<ActionType, ActionNode>([
9292
label: t('Notify on preferred channel'),
9393
action: EmailNode,
9494
details: EmailDetails,
95-
defaultData: {fallthrough_type: 'ActiveMembers'},
95+
defaultData: {fallthroughType: 'ActiveMembers'},
9696
},
9797
],
9898
[

static/app/views/automations/components/actions/email.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ export function EmailDetails({action}: {action: Action}) {
3939
if (target_type === ActionTarget.ISSUE_OWNERS) {
4040
return tct('Notify Suggested Assignees and, if none found, notify [fallthrough]', {
4141
fallthrough:
42-
FALLTHROUGH_CHOICES.find(choice => choice.value === action.data.fallthrough_type)
43-
?.label || String(action.data.fallthrough_type),
42+
FALLTHROUGH_CHOICES.find(choice => choice.value === action.data.fallthroughType)
43+
?.label || String(action.data.fallthroughType),
4444
});
4545
}
4646

@@ -129,11 +129,11 @@ function FallthroughField() {
129129
const {action, actionId, onUpdate} = useActionNodeContext();
130130
return (
131131
<AutomationBuilderSelect
132-
name={`${actionId}.data.fallthrough_type`}
133-
value={action.data.fallthrough_type}
132+
name={`${actionId}.data.fallthroughType`}
133+
value={action.data.fallthroughType}
134134
options={FALLTHROUGH_CHOICES}
135135
onChange={(option: SelectValue<string>) =>
136-
onUpdate({data: {fallthrough_type: option.value}})
136+
onUpdate({data: {fallthroughType: option.value}})
137137
}
138138
/>
139139
);

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,6 @@ export default function AutomationBuilder() {
103103
{t('If/Then Block')}
104104
</PurpleTextButton>
105105
</span>
106-
<span>
107-
<Button icon={<IconMail />}>{t('Send Test Notification')}</Button>
108-
</span>
109106
</Flex>
110107
);
111108
}
@@ -198,6 +195,9 @@ function ActionFilterBlock({
198195
updateAction={(id, data) => actions.updateIfAction(actionFilter.id, id, data)}
199196
/>
200197
</Step>
198+
<span>
199+
<Button icon={<IconMail />}>{t('Send Test Notification')}</Button>
200+
</span>
201201
</IfThenWrapper>
202202
);
203203
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type ActionHandler,
77
ActionTarget,
88
ActionType,
9+
SentryAppIdentifier,
910
} from 'sentry/types/workflowEngine/actions';
1011
import {
1112
type DataConditionGroup,
@@ -15,7 +16,7 @@ import {
1516
import {actionNodesMap} from 'sentry/views/automations/components/actionNodes';
1617
import {dataConditionNodesMap} from 'sentry/views/automations/components/dataConditionNodes';
1718

18-
export function useAutomationBuilderReducer() {
19+
export function useAutomationBuilderReducer(initialState?: AutomationBuilderState) {
1920
const reducer: Reducer<AutomationBuilderState, AutomationBuilderAction> = useCallback(
2021
(state, action): AutomationBuilderState => {
2122
switch (action.type) {
@@ -54,7 +55,10 @@ export function useAutomationBuilderReducer() {
5455
[]
5556
);
5657

57-
const [state, dispatch] = useReducer(reducer, initialAutomationBuilderState);
58+
const [state, dispatch] = useReducer(
59+
reducer,
60+
initialState ?? initialAutomationBuilderState
61+
);
5862

5963
const actions: AutomationActions = {
6064
addWhenCondition: useCallback(
@@ -519,6 +523,9 @@ function getDefaultConfig(actionHandler: ActionHandler): ActionConfig {
519523
return {
520524
target_type: targetType,
521525
...(targetIdentifier && {target_identifier: targetIdentifier}),
526+
...(actionHandler.sentryApp?.id && {
527+
sentry_app_identifier: SentryAppIdentifier.SENTRY_APP_ID,
528+
}),
522529
};
523530
}
524531

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

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {useEffect, useState} from 'react';
1+
import {useState} from 'react';
22
import styled from '@emotion/styled';
33

44
import {Button} from 'sentry/components/core/button';
@@ -7,7 +7,6 @@ import {Flex} from 'sentry/components/core/layout';
77
import SelectField from 'sentry/components/forms/fields/selectField';
88
import type FormModel from 'sentry/components/forms/model';
99
import useDrawer from 'sentry/components/globalDrawer';
10-
import {useDocumentTitle} from 'sentry/components/sentryDocumentTitle';
1110
import {DebugForm} from 'sentry/components/workflowEngine/form/debug';
1211
import {EnvironmentSelector} from 'sentry/components/workflowEngine/form/environmentSelector';
1312
import {useFormField} from 'sentry/components/workflowEngine/form/useFormField';
@@ -23,24 +22,19 @@ import {useDetectorsQuery} from 'sentry/views/detectors/hooks';
2322
import {makeMonitorBasePathname} from 'sentry/views/detectors/pathnames';
2423

2524
const FREQUENCY_OPTIONS = [
26-
{value: '5', label: t('5 minutes')},
27-
{value: '10', label: t('10 minutes')},
28-
{value: '30', label: t('30 minutes')},
29-
{value: '60', label: t('60 minutes')},
30-
{value: '180', label: t('3 hours')},
31-
{value: '720', label: t('12 hours')},
32-
{value: '1440', label: t('24 hours')},
33-
{value: '10080', label: t('1 week')},
34-
{value: '43200', label: t('30 days')},
25+
{value: 5, label: t('5 minutes')},
26+
{value: 10, label: t('10 minutes')},
27+
{value: 30, label: t('30 minutes')},
28+
{value: 60, label: t('60 minutes')},
29+
{value: 180, label: t('3 hours')},
30+
{value: 720, label: t('12 hours')},
31+
{value: 1440, label: t('24 hours')},
32+
{value: 10080, label: t('1 week')},
33+
{value: 43200, label: t('30 days')},
3534
];
3635

3736
export default function AutomationForm({model}: {model: FormModel}) {
3837
const organization = useOrganization();
39-
const title = useDocumentTitle();
40-
41-
useEffect(() => {
42-
model.setValue('name', title);
43-
}, [title, model]);
4438

4539
const {data: monitors = []} = useDetectorsQuery();
4640
const initialConnectedIds = useFormField('detectorIds');
@@ -116,6 +110,7 @@ export default function AutomationForm({model}: {model: FormModel}) {
116110
<Card>
117111
<Heading>{t('Action Interval')}</Heading>
118112
<EmbeddedSelectField
113+
required
119114
name="frequency"
120115
inline={false}
121116
clearable={false}

0 commit comments

Comments
 (0)