Skip to content

Commit 275354d

Browse files
authored
feat(aci): set up "Send Test Notification" button in automation builder (#95370)
connecting the "Send Test Notification" to the OrganizationTestFireAction API (tests for `AutomationBuilder` component coming soon 🙏🏼 )
1 parent da5ed47 commit 275354d

File tree

3 files changed

+74
-4
lines changed

3 files changed

+74
-4
lines changed

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

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

44
import {fetchOrgMembers} from 'sentry/actionCreators/members';
@@ -22,8 +22,11 @@ import {FILTER_MATCH_OPTIONS} from 'sentry/views/automations/components/actionFi
2222
import ActionNodeList from 'sentry/views/automations/components/actionNodeList';
2323
import {AutomationBuilderConflictContext} from 'sentry/views/automations/components/automationBuilderConflictContext';
2424
import {useAutomationBuilderContext} from 'sentry/views/automations/components/automationBuilderContext';
25+
import {useAutomationBuilderErrorContext} from 'sentry/views/automations/components/automationBuilderErrorContext';
26+
import {validateActions} from 'sentry/views/automations/components/automationFormData';
2527
import DataConditionNodeList from 'sentry/views/automations/components/dataConditionNodeList';
2628
import {TRIGGER_MATCH_OPTIONS} from 'sentry/views/automations/components/triggers/constants';
29+
import {useSendTestNotification} from 'sentry/views/automations/hooks';
2730
import {findConflictingConditions} from 'sentry/views/automations/hooks/utils';
2831

2932
export default function AutomationBuilder() {
@@ -114,6 +117,26 @@ interface ActionFilterBlockProps {
114117

115118
function ActionFilterBlock({actionFilter}: ActionFilterBlockProps) {
116119
const {actions} = useAutomationBuilderContext();
120+
const {mutateAsync: sendTestNotification} = useSendTestNotification();
121+
const {errors, setErrors} = useAutomationBuilderErrorContext();
122+
123+
const handleSendTestNotification = useCallback(async () => {
124+
const actionFilterActions = actionFilter.actions || [];
125+
126+
// Validate actions before sending test notification
127+
const actionErrors = validateActions({actions: actionFilterActions});
128+
setErrors({...errors, ...actionErrors});
129+
130+
// Only send test notification if there are no validation errors
131+
if (Object.keys(actionErrors).length === 0) {
132+
await sendTestNotification(
133+
actionFilterActions.map(action => {
134+
const {id: _id, ...actionWithoutId} = action;
135+
return actionWithoutId;
136+
})
137+
);
138+
}
139+
}, [actionFilter.actions, sendTestNotification, errors, setErrors]);
117140

118141
return (
119142
<IfThenWrapper>
@@ -188,7 +211,13 @@ function ActionFilterBlock({actionFilter}: ActionFilterBlockProps) {
188211
/>
189212
</Step>
190213
<span>
191-
<Button icon={<IconMail />}>{t('Send Test Notification')}</Button>
214+
<Button
215+
icon={<IconMail />}
216+
onClick={handleSendTestNotification}
217+
disabled={!actionFilter.actions?.length}
218+
>
219+
{t('Send Test Notification')}
220+
</Button>
192221
</span>
193222
</IfThenWrapper>
194223
);

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {FieldValue} from 'sentry/components/forms/model';
22
import {t} from 'sentry/locale';
3+
import type {Action} from 'sentry/types/workflowEngine/actions';
34
import type {Automation, NewAutomation} from 'sentry/types/workflowEngine/automations';
45
import type {DataCondition} from 'sentry/types/workflowEngine/dataConditions';
56
import {actionNodesMap} from 'sentry/views/automations/components/actionNodes';
@@ -117,3 +118,15 @@ export function validateAutomationBuilderState(state: AutomationBuilderState) {
117118
}
118119
return errors;
119120
}
121+
122+
export function validateActions({actions}: {actions: Action[]}): Record<string, string> {
123+
const errors: Record<string, string> = {};
124+
125+
for (const action of actions) {
126+
const validationResult = actionNodesMap.get(action.type)?.validate?.(action);
127+
if (validationResult) {
128+
errors[action.id] = validationResult;
129+
}
130+
}
131+
return errors;
132+
}

static/app/views/automations/hooks/index.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import {t} from 'sentry/locale';
1+
import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
2+
import {t, tn} from 'sentry/locale';
23
import AlertStore from 'sentry/stores/alertStore';
3-
import type {ActionHandler} from 'sentry/types/workflowEngine/actions';
4+
import type {Action, ActionHandler} from 'sentry/types/workflowEngine/actions';
45
import type {Automation, NewAutomation} from 'sentry/types/workflowEngine/automations';
56
import type {
67
DataConditionHandler,
@@ -133,3 +134,30 @@ export function useUpdateAutomation() {
133134
},
134135
});
135136
}
137+
138+
export function useSendTestNotification() {
139+
const org = useOrganization();
140+
const api = useApi({persistInFlight: true});
141+
const queryClient = useQueryClient();
142+
143+
return useMutation<void, void, Array<Omit<Action, 'id'>>>({
144+
mutationFn: data =>
145+
api.requestPromise(`/organizations/${org.slug}/test-fire-actions/`, {
146+
method: 'POST',
147+
data: {actions: data},
148+
}),
149+
onSuccess: (_, variables, __) => {
150+
queryClient.invalidateQueries({
151+
queryKey: [`/organizations/${org.slug}/workflows/`],
152+
});
153+
addSuccessMessage(
154+
tn('Notification fired!', 'Notifications sent!', variables.length)
155+
);
156+
},
157+
onError: (_, variables, __) => {
158+
addErrorMessage(
159+
tn('Notification failed', 'Notifications failed', variables.length)
160+
);
161+
},
162+
});
163+
}

0 commit comments

Comments
 (0)