From d6052cfd6fe8ef700d9b40392049208d0b4d1b0d Mon Sep 17 00:00:00 2001 From: Malachi Willey Date: Wed, 1 Apr 2026 16:42:32 -0700 Subject: [PATCH 1/2] fix(aci): Gracefully handle missing actions in Alerts UI --- .../components/actionNodeList.spec.tsx | 24 +++++++++++++++++++ .../automations/components/actionNodeList.tsx | 17 ++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/static/app/views/automations/components/actionNodeList.spec.tsx b/static/app/views/automations/components/actionNodeList.spec.tsx index 4c429928b9ef99..456e85430fc8d1 100644 --- a/static/app/views/automations/components/actionNodeList.spec.tsx +++ b/static/app/views/automations/components/actionNodeList.spec.tsx @@ -151,6 +151,30 @@ describe('ActionNodeList', () => { expect(mockOnDeleteRow).toHaveBeenCalledWith(slackAction.id); }); + it('shows an error for actions with unavailable handlers', async () => { + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/available-actions/`, + body: [], // No available actions + }); + + const slackAction = ActionFixture(); + render( + + + , + { + organization, + } + ); + + expect( + await screen.findByText( + 'The Slack action is no longer available. Please remove and reconfigure this action.' + ) + ).toBeInTheDocument(); + expect(screen.getByRole('button', {name: 'Delete row'})).toBeInTheDocument(); + }); + it('shows a warning message for an incompatible action', async () => { const model = new FormModel(); model.setInitialData({ diff --git a/static/app/views/automations/components/actionNodeList.tsx b/static/app/views/automations/components/actionNodeList.tsx index 540ec67154887c..8f2e2a7220f5b5 100644 --- a/static/app/views/automations/components/actionNodeList.tsx +++ b/static/app/views/automations/components/actionNodeList.tsx @@ -112,7 +112,22 @@ export function ActionNodeList({ {actions.map(action => { const handler = getActionHandler(action, availableActions); if (!handler) { - return null; + const actionLabel = actionNodesMap.get(action.type)?.label || action.type; + return ( + { + onDeleteRow(action.id); + }} + hasError + errorMessage={t( + 'The %s action is no longer available. Please remove and reconfigure this action.', + actionLabel + )} + > + {actionLabel} + + ); } const error = errors?.[action.id]; const warningMessage = getIncompatibleActionWarning(action, { From bbfbcf48b6de60802f621405a980f8a04b5c8fbd Mon Sep 17 00:00:00 2001 From: Malachi Willey Date: Wed, 1 Apr 2026 17:01:17 -0700 Subject: [PATCH 2/2] Handle loading state and improve labels for sentry apps --- .../automations/components/actionNodeList.tsx | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/static/app/views/automations/components/actionNodeList.tsx b/static/app/views/automations/components/actionNodeList.tsx index 8f2e2a7220f5b5..6c3eb49896e3c6 100644 --- a/static/app/views/automations/components/actionNodeList.tsx +++ b/static/app/views/automations/components/actionNodeList.tsx @@ -62,7 +62,8 @@ export function ActionNodeList({ onDeleteRow, updateAction, }: ActionNodeListProps) { - const {data: availableActions = []} = useAvailableActionsQuery(); + const {data: availableActions = [], isLoading: isLoadingActions} = + useAvailableActionsQuery(); const {errors, removeError} = useAutomationBuilderErrorContext(); const {connectedDetectors} = useConnectedDetectors(); @@ -110,9 +111,12 @@ export function ActionNodeList({ return ( {actions.map(action => { + if (isLoadingActions) { + return null; + } const handler = getActionHandler(action, availableActions); if (!handler) { - const actionLabel = actionNodesMap.get(action.type)?.label || action.type; + const actionLabel = actionNodesMap.get(action.type)?.label; return ( - {actionLabel} + {actionLabel ?? t('Unknown integration')} ); }