From 74f6e79e8d231724a65e88937d047a21fc6cf792 Mon Sep 17 00:00:00 2001 From: Mia Hsu Date: Fri, 27 Jun 2025 17:18:41 -0700 Subject: [PATCH 1/5] feat(aci): automation edit scaffolding --- .../automations/components/actionNodeList.tsx | 32 +++- .../automations/components/actionNodes.tsx | 2 +- .../automations/components/actions/email.tsx | 10 +- .../components/automationBuilder.tsx | 6 +- .../components/automationBuilderContext.tsx | 7 +- .../automations/components/automationForm.tsx | 27 ++- .../components/automationFormData.tsx | 18 +- .../components/conditionsPanel.tsx | 12 +- .../components/editableAutomationName.tsx | 25 +++ static/app/views/automations/edit.tsx | 172 +++++++++++++----- static/app/views/automations/new-settings.tsx | 28 +-- 11 files changed, 228 insertions(+), 111 deletions(-) create mode 100644 static/app/views/automations/components/editableAutomationName.tsx diff --git a/static/app/views/automations/components/actionNodeList.tsx b/static/app/views/automations/components/actionNodeList.tsx index 33063ea0a7b909..095f2ee5efd32f 100644 --- a/static/app/views/automations/components/actionNodeList.tsx +++ b/static/app/views/automations/components/actionNodeList.tsx @@ -1,4 +1,4 @@ -import {Fragment, useMemo, useState} from 'react'; +import {Fragment, useMemo} from 'react'; import styled from '@emotion/styled'; import {uuid4} from '@sentry/core'; @@ -8,6 +8,8 @@ import { type Action, ActionGroup, type ActionHandler, + ActionType, + SentryAppIdentifier, } from 'sentry/types/workflowEngine/actions'; import { ActionNodeContext, @@ -31,6 +33,24 @@ interface Option { value: ActionHandler; } +function getActionHandler( + action: Action, + availableActions: ActionHandler[] +): ActionHandler | undefined { + if (action.type === ActionType.SENTRY_APP) { + return availableActions.find( + handler => + handler.type === ActionType.SENTRY_APP && + ((action.config.sentry_app_identifier === SentryAppIdentifier.SENTRY_APP_ID && + action.config.target_identifier === handler.sentryApp?.id) || + (action.config.sentry_app_identifier === + SentryAppIdentifier.SENTRY_APP_INSTALLATION_UUID && + action.config.target_identifier === handler.sentryApp?.installationUuid)) + ); + } + return availableActions.find(handler => handler.type === action.type); +} + export default function ActionNodeList({ group, placeholder, @@ -40,9 +60,6 @@ export default function ActionNodeList({ updateAction, }: ActionNodeListProps) { const {data: availableActions = []} = useAvailableActionsQuery(); - const [actionHandlerMap, setActionHandlerMap] = useState>( - {} - ); const options = useMemo(() => { const notificationActions: Option[] = []; @@ -88,7 +105,7 @@ export default function ActionNodeList({ return ( {actions.map(action => { - const handler = actionHandlerMap[action.id]; + const handler = getActionHandler(action, availableActions); if (!handler) { return null; } @@ -97,7 +114,6 @@ export default function ActionNodeList({ key={`${group}.action.${action.id}`} onDelete={() => { onDeleteRow(action.id); - setActionHandlerMap(({[action.id]: _, ...rest}) => rest); }} > { const actionId = uuid4(); onAddRow(actionId, obj.value); - setActionHandlerMap(currActionHandlerMap => ({ - ...currActionHandlerMap, - [actionId]: obj.value, - })); }} placeholder={placeholder} value={null} diff --git a/static/app/views/automations/components/actionNodes.tsx b/static/app/views/automations/components/actionNodes.tsx index ccba1642feebb8..92a1797145c76d 100644 --- a/static/app/views/automations/components/actionNodes.tsx +++ b/static/app/views/automations/components/actionNodes.tsx @@ -92,7 +92,7 @@ export const actionNodesMap = new Map([ label: t('Notify on preferred channel'), action: EmailNode, details: EmailDetails, - defaultData: {fallthrough_type: 'ActiveMembers'}, + defaultData: {fallthroughType: 'ActiveMembers'}, }, ], [ diff --git a/static/app/views/automations/components/actions/email.tsx b/static/app/views/automations/components/actions/email.tsx index acc8b1bba5563e..3cb68a1d807c1d 100644 --- a/static/app/views/automations/components/actions/email.tsx +++ b/static/app/views/automations/components/actions/email.tsx @@ -39,8 +39,8 @@ export function EmailDetails({action}: {action: Action}) { if (target_type === ActionTarget.ISSUE_OWNERS) { return tct('Notify Suggested Assignees and, if none found, notify [fallthrough]', { fallthrough: - FALLTHROUGH_CHOICES.find(choice => choice.value === action.data.fallthrough_type) - ?.label || String(action.data.fallthrough_type), + FALLTHROUGH_CHOICES.find(choice => choice.value === action.data.fallthroughType) + ?.label || String(action.data.fallthroughType), }); } @@ -129,11 +129,11 @@ function FallthroughField() { const {action, actionId, onUpdate} = useActionNodeContext(); return ( ) => - onUpdate({data: {fallthrough_type: option.value}}) + onUpdate({data: {fallthroughType: option.value}}) } /> ); diff --git a/static/app/views/automations/components/automationBuilder.tsx b/static/app/views/automations/components/automationBuilder.tsx index b50a1b99fbacbb..9ea88d02164955 100644 --- a/static/app/views/automations/components/automationBuilder.tsx +++ b/static/app/views/automations/components/automationBuilder.tsx @@ -103,9 +103,6 @@ export default function AutomationBuilder() { {t('If/Then Block')} - - - ); } @@ -198,6 +195,9 @@ function ActionFilterBlock({ updateAction={(id, data) => actions.updateIfAction(actionFilter.id, id, data)} /> + + + ); } diff --git a/static/app/views/automations/components/automationBuilderContext.tsx b/static/app/views/automations/components/automationBuilderContext.tsx index fd88f12afa2f57..d6e5283dab1c27 100644 --- a/static/app/views/automations/components/automationBuilderContext.tsx +++ b/static/app/views/automations/components/automationBuilderContext.tsx @@ -15,7 +15,7 @@ import { import {actionNodesMap} from 'sentry/views/automations/components/actionNodes'; import {dataConditionNodesMap} from 'sentry/views/automations/components/dataConditionNodes'; -export function useAutomationBuilderReducer() { +export function useAutomationBuilderReducer(initialState?: AutomationBuilderState) { const reducer: Reducer = useCallback( (state, action): AutomationBuilderState => { switch (action.type) { @@ -54,7 +54,10 @@ export function useAutomationBuilderReducer() { [] ); - const [state, dispatch] = useReducer(reducer, initialAutomationBuilderState); + const [state, dispatch] = useReducer( + reducer, + initialState ?? initialAutomationBuilderState + ); const actions: AutomationActions = { addWhenCondition: useCallback( diff --git a/static/app/views/automations/components/automationForm.tsx b/static/app/views/automations/components/automationForm.tsx index eba8ff581121e5..da0793dbfc8328 100644 --- a/static/app/views/automations/components/automationForm.tsx +++ b/static/app/views/automations/components/automationForm.tsx @@ -1,4 +1,4 @@ -import {useEffect, useState} from 'react'; +import {useState} from 'react'; import styled from '@emotion/styled'; import {Button} from 'sentry/components/core/button'; @@ -7,7 +7,6 @@ import {Flex} from 'sentry/components/core/layout'; import SelectField from 'sentry/components/forms/fields/selectField'; import type FormModel from 'sentry/components/forms/model'; import useDrawer from 'sentry/components/globalDrawer'; -import {useDocumentTitle} from 'sentry/components/sentryDocumentTitle'; import {DebugForm} from 'sentry/components/workflowEngine/form/debug'; import {EnvironmentSelector} from 'sentry/components/workflowEngine/form/environmentSelector'; import {useFormField} from 'sentry/components/workflowEngine/form/useFormField'; @@ -23,24 +22,19 @@ import {useDetectorsQuery} from 'sentry/views/detectors/hooks'; import {makeMonitorBasePathname} from 'sentry/views/detectors/pathnames'; const FREQUENCY_OPTIONS = [ - {value: '5', label: t('5 minutes')}, - {value: '10', label: t('10 minutes')}, - {value: '30', label: t('30 minutes')}, - {value: '60', label: t('60 minutes')}, - {value: '180', label: t('3 hours')}, - {value: '720', label: t('12 hours')}, - {value: '1440', label: t('24 hours')}, - {value: '10080', label: t('1 week')}, - {value: '43200', label: t('30 days')}, + {value: 5, label: t('5 minutes')}, + {value: 10, label: t('10 minutes')}, + {value: 30, label: t('30 minutes')}, + {value: 60, label: t('60 minutes')}, + {value: 180, label: t('3 hours')}, + {value: 720, label: t('12 hours')}, + {value: 1440, label: t('24 hours')}, + {value: 10080, label: t('1 week')}, + {value: 43200, label: t('30 days')}, ]; export default function AutomationForm({model}: {model: FormModel}) { const organization = useOrganization(); - const title = useDocumentTitle(); - - useEffect(() => { - model.setValue('name', title); - }, [title, model]); const {data: monitors = []} = useDetectorsQuery(); const initialConnectedIds = useFormField('detectorIds'); @@ -116,6 +110,7 @@ export default function AutomationForm({model}: {model: FormModel}) { {t('Action Interval')} { + return { + detectorIds: automation.detectorIds, + environment: automation.environment, + frequency: automation.config.frequency || null, + name: automation.name, + }; +} diff --git a/static/app/views/automations/components/conditionsPanel.tsx b/static/app/views/automations/components/conditionsPanel.tsx index a4d5099f18d488..db823acad34b43 100644 --- a/static/app/views/automations/components/conditionsPanel.tsx +++ b/static/app/views/automations/components/conditionsPanel.tsx @@ -15,8 +15,10 @@ import type { DataCondition, DataConditionGroup, } from 'sentry/types/workflowEngine/dataConditions'; +import {FILTER_MATCH_OPTIONS} from 'sentry/views/automations/components/actionFilters/constants'; import {actionNodesMap} from 'sentry/views/automations/components/actionNodes'; import {dataConditionNodesMap} from 'sentry/views/automations/components/dataConditionNodes'; +import {TRIGGER_MATCH_OPTIONS} from 'sentry/views/automations/components/triggers/constants'; import {useAvailableActionsQuery} from 'sentry/views/automations/hooks'; type ConditionsPanelProps = { @@ -30,8 +32,11 @@ function ConditionsPanel({triggers, actionFilters}: ConditionsPanelProps) { {triggers && ( - {tct('[when:When] any of the following occur', { + {tct('[when:When] [logicType] of the following occur', { when: , + logicType: + TRIGGER_MATCH_OPTIONS.find(choice => choice.value === triggers.logicType) + ?.label || triggers.logicType, })} {triggers.conditions.map((trigger, index) => ( @@ -82,8 +87,11 @@ function ActionFilter({actionFilter, totalFilters}: ActionFilterProps) { return ( 1}> - {tct('[if:If] any of these filters match', { + {tct('[if:If] [logicType] of these filters match', { if: , + logicType: + FILTER_MATCH_OPTIONS.find(choice => choice.value === actionFilter.logicType) + ?.label || actionFilter.logicType, })} {actionFilter.conditions.length > 0 diff --git a/static/app/views/automations/components/editableAutomationName.tsx b/static/app/views/automations/components/editableAutomationName.tsx new file mode 100644 index 00000000000000..38b8a348f21ec5 --- /dev/null +++ b/static/app/views/automations/components/editableAutomationName.tsx @@ -0,0 +1,25 @@ +import EditableText from 'sentry/components/editableText'; +import FormField from 'sentry/components/forms/formField'; +import {t} from 'sentry/locale'; + +export function EditableAutomationName() { + return ( + + {({onChange, value}) => ( + { + onChange(newValue, { + target: { + value: newValue, + }, + }); + }} + errorMessage={t('Please set a name for your automation.')} + placeholder={t('New Automation')} + /> + )} + + ); +} diff --git a/static/app/views/automations/edit.tsx b/static/app/views/automations/edit.tsx index add2621934fac9..ec56d360c23550 100644 --- a/static/app/views/automations/edit.tsx +++ b/static/app/views/automations/edit.tsx @@ -1,57 +1,145 @@ -/* eslint-disable no-alert */ -import {Fragment} from 'react'; +import {useMemo} from 'react'; +import styled from '@emotion/styled'; +import {Breadcrumbs} from 'sentry/components/breadcrumbs'; import {Button} from 'sentry/components/core/button'; +import {LinkButton} from 'sentry/components/core/button/linkButton'; +import {Flex} from 'sentry/components/core/layout'; +import type {FieldValue} from 'sentry/components/forms/model'; +import FormModel from 'sentry/components/forms/model'; +import * as Layout from 'sentry/components/layouts/thirds'; +import LoadingError from 'sentry/components/loadingError'; +import LoadingIndicator from 'sentry/components/loadingIndicator'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; -import {ActionsProvider} from 'sentry/components/workflowEngine/layout/actions'; -import {BreadcrumbsProvider} from 'sentry/components/workflowEngine/layout/breadcrumbs'; -import EditLayout from 'sentry/components/workflowEngine/layout/edit'; +import {FullHeightForm} from 'sentry/components/workflowEngine/form/fullHeightForm'; +import {useFormField} from 'sentry/components/workflowEngine/form/useFormField'; +import { + StickyFooter, + StickyFooterLabel, +} from 'sentry/components/workflowEngine/ui/footer'; import {useWorkflowEngineFeatureGate} from 'sentry/components/workflowEngine/useWorkflowEngineFeatureGate'; import {t} from 'sentry/locale'; +import {space} from 'sentry/styles/space'; import useOrganization from 'sentry/utils/useOrganization'; -import {makeAutomationBasePathname} from 'sentry/views/automations/pathnames'; +import {useParams} from 'sentry/utils/useParams'; +import type {AutomationBuilderState} from 'sentry/views/automations/components/automationBuilderContext'; +import { + AutomationBuilderContext, + initialAutomationBuilderState, + useAutomationBuilderReducer, +} from 'sentry/views/automations/components/automationBuilderContext'; +import AutomationForm from 'sentry/views/automations/components/automationForm'; +import {getAutomationFormData} from 'sentry/views/automations/components/automationFormData'; +import {EditableAutomationName} from 'sentry/views/automations/components/editableAutomationName'; +import {useAutomationQuery} from 'sentry/views/automations/hooks'; +import { + makeAutomationBasePathname, + makeAutomationDetailsPathname, +} from 'sentry/views/automations/pathnames'; -export default function AutomationEdit() { - const organization = useOrganization(); - useWorkflowEngineFeatureGate({redirect: true}); +function AutomationDocumentTitle() { + const title = useFormField('name'); + return ; +} +function AutomationBreadcrumbs({automationId}: {automationId: string}) { + const title = useFormField('name'); + const organization = useOrganization(); return ( - - - }> - -

Edit Automation

-
-
-
-
+ ); } -function Actions() { - const disable = () => { - window.alert('disable'); - }; - const del = () => { - window.alert('delete'); - }; - const save = () => { - window.alert('save'); - }; +export default function AutomationEdit() { + const organization = useOrganization(); + const params = useParams<{automationId: string}>(); + + useWorkflowEngineFeatureGate({redirect: true}); + + const { + data: automation, + isPending, + isError, + refetch, + } = useAutomationQuery(params.automationId); + + const initialData = useMemo((): Record | undefined => { + if (!automation) { + return undefined; + } + return getAutomationFormData(automation); + }, [automation]); + + const initialState = useMemo((): AutomationBuilderState | undefined => { + if (!automation) { + return undefined; + } + return { + triggers: automation.triggers + ? automation.triggers + : initialAutomationBuilderState.triggers, + actionFilters: automation.actionFilters, + }; + }, [automation]); + + const model = useMemo(() => new FormModel({initialData}), [initialData]); + const {state, actions} = useAutomationBuilderReducer(initialState); + + if (isPending && !initialData) { + return ; + } + + if (isError || !automation || !initialData) { + return ; + } + return ( - - - - - + + + + + + + + + + + + + + + + + + + + + {t('Step 2 of 2')} + + + {t('Cancel')} + + + + + ); } + +const StyledLayoutHeader = styled(Layout.Header)` + background-color: ${p => p.theme.background}; +`; diff --git a/static/app/views/automations/new-settings.tsx b/static/app/views/automations/new-settings.tsx index 47ad83229a64b9..76e74a5e524f47 100644 --- a/static/app/views/automations/new-settings.tsx +++ b/static/app/views/automations/new-settings.tsx @@ -1,13 +1,10 @@ import {useCallback, useMemo} from 'react'; import styled from '@emotion/styled'; -import {flattie} from 'flattie'; import {Breadcrumbs} from 'sentry/components/breadcrumbs'; import {Button} from 'sentry/components/core/button'; import {LinkButton} from 'sentry/components/core/button/linkButton'; import {Flex} from 'sentry/components/core/layout'; -import EditableText from 'sentry/components/editableText'; -import FormField from 'sentry/components/forms/formField'; import FormModel from 'sentry/components/forms/model'; import type {OnSubmitCallback} from 'sentry/components/forms/types'; import * as Layout from 'sentry/components/layouts/thirds'; @@ -26,12 +23,12 @@ import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import { AutomationBuilderContext, - initialAutomationBuilderState, useAutomationBuilderReducer, } from 'sentry/views/automations/components/automationBuilderContext'; import AutomationForm from 'sentry/views/automations/components/automationForm'; import type {AutomationFormData} from 'sentry/views/automations/components/automationFormData'; import {getNewAutomationData} from 'sentry/views/automations/components/automationFormData'; +import {EditableAutomationName} from 'sentry/views/automations/components/editableAutomationName'; import {useCreateAutomation} from 'sentry/views/automations/hooks'; import { makeAutomationBasePathname, @@ -60,30 +57,7 @@ function AutomationBreadcrumbs() { ); } -function EditableAutomationName() { - return ( - - {({onChange, value}) => ( - { - onChange(newValue, { - target: { - value: newValue, - }, - }); - }} - errorMessage={t('Please set a name for your automation.')} - placeholder={t('New Automation')} - /> - )} - - ); -} - const initialData = { - ...flattie(initialAutomationBuilderState), environment: null, frequency: '1440', }; From 4ab211d80a2254abbef0cb701bb6a3836c01a036 Mon Sep 17 00:00:00 2001 From: Mia Hsu Date: Fri, 27 Jun 2025 17:27:32 -0700 Subject: [PATCH 2/5] knip --- package.json | 1 - pnpm-lock.yaml | 9 -- .../components/workflowEngine/layout/edit.tsx | 99 ------------------- 3 files changed, 109 deletions(-) delete mode 100644 static/app/components/workflowEngine/layout/edit.tsx diff --git a/package.json b/package.json index 383e424487e937..f71433db02c363 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,6 @@ "echarts": "5.4.0", "echarts-for-react": "3.0.2", "esbuild": "0.25.3", - "flattie": "^1.1.1", "focus-trap": "^7.3.1", "framer-motion": "12.7.3", "fuse.js": "^6.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5873d7b6069e72..4b53a4bd6b4b0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -345,9 +345,6 @@ importers: esbuild: specifier: 0.25.3 version: 0.25.3 - flattie: - specifier: ^1.1.1 - version: 1.1.1 focus-trap: specifier: ^7.3.1 version: 7.3.1 @@ -4864,10 +4861,6 @@ packages: flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - flattie@1.1.1: - resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} - engines: {node: '>=8'} - focus-trap@7.3.1: resolution: {integrity: sha512-bX/u4FJ+F0Pp6b/8Q9W8Br/JaLJ7rrhOJAzai9JU8bh4BPdOjEATy4pxHcbBBxFjPN4d1oHy7/KqknEdOetm9w==} @@ -13640,8 +13633,6 @@ snapshots: flatted@3.3.2: {} - flattie@1.1.1: {} - focus-trap@7.3.1: dependencies: tabbable: 6.1.1 diff --git a/static/app/components/workflowEngine/layout/edit.tsx b/static/app/components/workflowEngine/layout/edit.tsx deleted file mode 100644 index 09b207130a398c..00000000000000 --- a/static/app/components/workflowEngine/layout/edit.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import styled from '@emotion/styled'; - -import EditableText from 'sentry/components/editableText'; -import FormField from 'sentry/components/forms/formField'; -import * as Layout from 'sentry/components/layouts/thirds'; -import {ActionsFromContext} from 'sentry/components/workflowEngine/layout/actions'; -import {BreadcrumbsFromContext} from 'sentry/components/workflowEngine/layout/breadcrumbs'; -import {t} from 'sentry/locale'; -import {space} from 'sentry/styles/space'; - -interface WorkflowEngineEditLayoutProps { - /** - * The main content for this page - * Expected to include `` and `` components. - */ - children: React.ReactNode; - onTitleChange?: (title: string) => void; -} - -/** - * Precomposed full-width layout for Automations / Monitors edit pages. - */ -function EditLayout({children, onTitleChange}: WorkflowEngineEditLayoutProps) { - return ( - - - - - - - {({onChange, value}) => ( - { - onChange(newValue, { - target: { - value: newValue, - }, - }); - }} - errorMessage={t('Please set a title')} - placeholder={t('New Monitor')} - /> - )} - - - - - - {children} - - ); -} - -const Body = styled('div')` - display: flex; - flex-direction: column; - gap: ${space(3)}; - flex-grow: 1; -`; - -const ChartContainer = styled('div')` - display: flex; - flex-direction: column; - background-color: ${p => p.theme.background}; - gap: ${space(3)}; - width: 100%; - flex-grow: 1; - padding: ${space(1)} ${space(4)}; - border-bottom: 1px solid ${p => p.theme.border}; -`; - -const PanelsContainer = styled('div')` - display: flex; - flex-direction: column; - padding: ${space(3)} ${space(4)}; - gap: ${space(2)}; - width: 100%; - flex-grow: 1; -`; - -function Chart({children}: any) { - return {children}; -} - -function Panels({children}: any) { - return {children}; -} - -const WorkflowEngineEditLayout = Object.assign(EditLayout, {Chart, Panels}); - -export default WorkflowEngineEditLayout; From 83b5e7d58181222387a4f40026925ddb951feb96 Mon Sep 17 00:00:00 2001 From: Mia Hsu Date: Mon, 30 Jun 2025 09:40:28 -0700 Subject: [PATCH 3/5] fix type --- static/app/views/automations/new-settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/app/views/automations/new-settings.tsx b/static/app/views/automations/new-settings.tsx index 76e74a5e524f47..7d39ca0c20f14f 100644 --- a/static/app/views/automations/new-settings.tsx +++ b/static/app/views/automations/new-settings.tsx @@ -59,7 +59,7 @@ function AutomationBreadcrumbs() { const initialData = { environment: null, - frequency: '1440', + frequency: 1440, }; export default function AutomationNewSettings() { From 3f3d1ad2dc12d5f5c399057d169297aaf75f34fc Mon Sep 17 00:00:00 2001 From: Mia Hsu Date: Mon, 30 Jun 2025 11:52:41 -0700 Subject: [PATCH 4/5] make sentry app handler filtering more readable --- .../automations/components/actionNodeList.tsx | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/static/app/views/automations/components/actionNodeList.tsx b/static/app/views/automations/components/actionNodeList.tsx index 095f2ee5efd32f..45b35059876f7f 100644 --- a/static/app/views/automations/components/actionNodeList.tsx +++ b/static/app/views/automations/components/actionNodeList.tsx @@ -38,15 +38,21 @@ function getActionHandler( availableActions: ActionHandler[] ): ActionHandler | undefined { if (action.type === ActionType.SENTRY_APP) { - return availableActions.find( - handler => - handler.type === ActionType.SENTRY_APP && - ((action.config.sentry_app_identifier === SentryAppIdentifier.SENTRY_APP_ID && - action.config.target_identifier === handler.sentryApp?.id) || - (action.config.sentry_app_identifier === - SentryAppIdentifier.SENTRY_APP_INSTALLATION_UUID && - action.config.target_identifier === handler.sentryApp?.installationUuid)) - ); + return availableActions.find(handler => { + if (handler.type !== ActionType.SENTRY_APP) { + return false; + } + const {sentry_app_identifier, target_identifier} = action.config; + const sentryApp = handler.sentryApp; + + const isMatchingAppId = + sentry_app_identifier === SentryAppIdentifier.SENTRY_APP_ID && + target_identifier === sentryApp?.id; + const isMatchingInstallationUuid = + sentry_app_identifier === SentryAppIdentifier.SENTRY_APP_INSTALLATION_UUID && + target_identifier === sentryApp?.installationUuid; + return isMatchingAppId || isMatchingInstallationUuid; + }); } return availableActions.find(handler => handler.type === action.type); } From b159165bebec0514c6eb6f57d17e291566de4692 Mon Sep 17 00:00:00 2001 From: Mia Hsu Date: Mon, 30 Jun 2025 13:37:13 -0700 Subject: [PATCH 5/5] fixes --- .../views/automations/components/automationBuilderContext.tsx | 4 ++++ static/app/views/automations/edit.tsx | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/static/app/views/automations/components/automationBuilderContext.tsx b/static/app/views/automations/components/automationBuilderContext.tsx index d6e5283dab1c27..46185b2339af30 100644 --- a/static/app/views/automations/components/automationBuilderContext.tsx +++ b/static/app/views/automations/components/automationBuilderContext.tsx @@ -6,6 +6,7 @@ import { type ActionHandler, ActionTarget, ActionType, + SentryAppIdentifier, } from 'sentry/types/workflowEngine/actions'; import { type DataConditionGroup, @@ -522,6 +523,9 @@ function getDefaultConfig(actionHandler: ActionHandler): ActionConfig { return { target_type: targetType, ...(targetIdentifier && {target_identifier: targetIdentifier}), + ...(actionHandler.sentryApp?.id && { + sentry_app_identifier: SentryAppIdentifier.SENTRY_APP_ID, + }), }; } diff --git a/static/app/views/automations/edit.tsx b/static/app/views/automations/edit.tsx index ec56d360c23550..4d451e735827ae 100644 --- a/static/app/views/automations/edit.tsx +++ b/static/app/views/automations/edit.tsx @@ -91,7 +91,7 @@ export default function AutomationEdit() { }; }, [automation]); - const model = useMemo(() => new FormModel({initialData}), [initialData]); + const model = useMemo(() => new FormModel(), []); const {state, actions} = useAutomationBuilderReducer(initialState); if (isPending && !initialData) { @@ -103,7 +103,7 @@ export default function AutomationEdit() { } return ( - +