Skip to content

Commit a8d17a8

Browse files
authored
fix(aci): add validation to automation edit view (#95091)
pulled the automation builder validation function out so that it can be called by automation create + edit also fixed a bug with the automation edit view where the form would be blank when the page was refreshed by putting the form in a separate component that is only rendered once the existing automation data has finished fetching
1 parent 3a4611b commit a8d17a8

File tree

3 files changed

+55
-39
lines changed

3 files changed

+55
-39
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type {FieldValue} from 'sentry/components/forms/model';
2+
import {t} from 'sentry/locale';
23
import type {Automation, NewAutomation} from 'sentry/types/workflowEngine/automations';
4+
import {actionNodesMap} from 'sentry/views/automations/components/actionNodes';
35
import type {AutomationBuilderState} from 'sentry/views/automations/components/automationBuilderContext';
46

57
export interface AutomationFormData {
@@ -55,3 +57,21 @@ export function getAutomationFormData(
5557
name: automation.name,
5658
};
5759
}
60+
61+
export function validateAutomationBuilderState(state: AutomationBuilderState) {
62+
const errors: Record<string, string> = {};
63+
64+
for (const actionFilter of state.actionFilters) {
65+
if (actionFilter.actions?.length === 0) {
66+
errors[actionFilter.id] = t('You must add an action for this automation to run.');
67+
continue;
68+
}
69+
for (const action of actionFilter.actions || []) {
70+
const validationResult = actionNodesMap.get(action.type)?.validate?.(action);
71+
if (validationResult) {
72+
errors[action.id] = validationResult;
73+
}
74+
}
75+
}
76+
return errors;
77+
}

static/app/views/automations/edit.tsx

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import {useWorkflowEngineFeatureGate} from 'sentry/components/workflowEngine/useWorkflowEngineFeatureGate';
2222
import {t} from 'sentry/locale';
2323
import {space} from 'sentry/styles/space';
24+
import type {Automation} from 'sentry/types/workflowEngine/automations';
2425
import {useNavigate} from 'sentry/utils/useNavigate';
2526
import useOrganization from 'sentry/utils/useOrganization';
2627
import {useParams} from 'sentry/utils/useParams';
@@ -36,6 +37,7 @@ import type {AutomationFormData} from 'sentry/views/automations/components/autom
3637
import {
3738
getAutomationFormData,
3839
getNewAutomationData,
40+
validateAutomationBuilderState,
3941
} from 'sentry/views/automations/components/automationFormData';
4042
import {EditableAutomationName} from 'sentry/views/automations/components/editableAutomationName';
4143
import {useAutomationQuery, useUpdateAutomation} from 'sentry/views/automations/hooks';
@@ -67,10 +69,7 @@ function AutomationBreadcrumbs({automationId}: {automationId: string}) {
6769
}
6870

6971
export default function AutomationEdit() {
70-
const navigate = useNavigate();
71-
const organization = useOrganization();
7272
const params = useParams<{automationId: string}>();
73-
const {mutateAsync: updateAutomation} = useUpdateAutomation();
7473

7574
useWorkflowEngineFeatureGate({redirect: true});
7675

@@ -81,6 +80,23 @@ export default function AutomationEdit() {
8180
refetch,
8281
} = useAutomationQuery(params.automationId);
8382

83+
if (isPending) {
84+
return <LoadingIndicator />;
85+
}
86+
87+
if (isError || !automation) {
88+
return <LoadingError onRetry={refetch} />;
89+
}
90+
91+
return <AutomationEditForm automation={automation} />;
92+
}
93+
94+
function AutomationEditForm({automation}: {automation: Automation}) {
95+
const navigate = useNavigate();
96+
const organization = useOrganization();
97+
const params = useParams<{automationId: string}>();
98+
const {mutateAsync: updateAutomation} = useUpdateAutomation();
99+
84100
const initialData = useMemo((): Record<string, FieldValue> | undefined => {
85101
if (!automation) {
86102
return undefined;
@@ -115,25 +131,22 @@ export default function AutomationEdit() {
115131

116132
const handleFormSubmit = useCallback<OnSubmitCallback>(
117133
async (data, _, __, ___, ____) => {
118-
const formData = getNewAutomationData(data as AutomationFormData, state);
119-
const updatedData = {
120-
automationId: params.automationId,
121-
...formData,
122-
};
123-
const updatedAutomation = await updateAutomation(updatedData);
124-
navigate(makeAutomationDetailsPathname(organization.slug, updatedAutomation.id));
134+
const errors = validateAutomationBuilderState(state);
135+
setAutomationBuilderErrors(errors);
136+
137+
if (Object.keys(errors).length === 0) {
138+
const formData = getNewAutomationData(data as AutomationFormData, state);
139+
const updatedData = {
140+
automationId: params.automationId,
141+
...formData,
142+
};
143+
const updatedAutomation = await updateAutomation(updatedData);
144+
navigate(makeAutomationDetailsPathname(organization.slug, updatedAutomation.id));
145+
}
125146
},
126147
[params.automationId, organization.slug, navigate, updateAutomation, state]
127148
);
128149

129-
if (isPending && !initialData) {
130-
return <LoadingIndicator />;
131-
}
132-
133-
if (isError || !automation || !initialData) {
134-
return <LoadingError onRetry={refetch} />;
135-
}
136-
137150
return (
138151
<FullHeightForm
139152
hideFooter

static/app/views/automations/new-settings.tsx

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,17 @@ import {space} from 'sentry/styles/space';
2121
import {useLocation} from 'sentry/utils/useLocation';
2222
import {useNavigate} from 'sentry/utils/useNavigate';
2323
import useOrganization from 'sentry/utils/useOrganization';
24-
import {actionNodesMap} from 'sentry/views/automations/components/actionNodes';
25-
import type {AutomationBuilderState} from 'sentry/views/automations/components/automationBuilderContext';
2624
import {
2725
AutomationBuilderContext,
2826
useAutomationBuilderReducer,
2927
} from 'sentry/views/automations/components/automationBuilderContext';
3028
import {AutomationBuilderErrorContext} from 'sentry/views/automations/components/automationBuilderErrorContext';
3129
import AutomationForm from 'sentry/views/automations/components/automationForm';
3230
import type {AutomationFormData} from 'sentry/views/automations/components/automationFormData';
33-
import {getNewAutomationData} from 'sentry/views/automations/components/automationFormData';
31+
import {
32+
getNewAutomationData,
33+
validateAutomationBuilderState,
34+
} from 'sentry/views/automations/components/automationFormData';
3435
import {EditableAutomationName} from 'sentry/views/automations/components/editableAutomationName';
3536
import {useCreateAutomation} from 'sentry/views/automations/hooks';
3637
import {
@@ -65,23 +66,6 @@ const initialData = {
6566
frequency: 1440,
6667
};
6768

68-
function validateAutomationBuilderState(state: AutomationBuilderState) {
69-
const errors: Record<string, string> = {};
70-
for (const actionFilter of state.actionFilters) {
71-
if (actionFilter.actions?.length === 0) {
72-
errors[actionFilter.id] = t('You must add an action for this automation to run.');
73-
continue;
74-
}
75-
for (const action of actionFilter.actions || []) {
76-
const validationResult = actionNodesMap.get(action.type)?.validate?.(action);
77-
if (validationResult) {
78-
errors[action.id] = validationResult;
79-
}
80-
}
81-
}
82-
return errors;
83-
}
84-
8569
export default function AutomationNewSettings() {
8670
const navigate = useNavigate();
8771
const location = useLocation();
@@ -118,7 +102,6 @@ export default function AutomationNewSettings() {
118102

119103
const handleSubmit = useCallback<OnSubmitCallback>(
120104
async (data, _, __, ___, ____) => {
121-
// TODO: add form validation
122105
const errors = validateAutomationBuilderState(state);
123106
setAutomationBuilderErrors(errors);
124107

0 commit comments

Comments
 (0)