Skip to content

Conversation

@jasonyuezhang
Copy link
Owner

@jasonyuezhang jasonyuezhang commented Nov 20, 2025

We can iterate on this design, but I wanted to add a better empty state and a way to edit connected automations directly from the monitor details page

CleanShot 2025-11-20 at 11 51 47

Copied from getsentry#103757
Original PR: getsentry#103757

Summary by CodeRabbit

  • New Features

    • Added interactive automation connection management for detectors with a new drawer UI for selecting and connecting automations.
    • Implemented permission-based controls to manage automation editing access.
    • Added ability to create new automations directly from the detector workflow.
  • Improvements

    • Enhanced section header layouts with improved action button placement.

✏️ Tip: You can customize this high-level summary in your review settings.

@propel-test-bot
Copy link

Allow editing of connected alerts (automations) directly from monitor details

This PR introduces a drawer-based workflow that lets users add or remove alert automations from a monitor (detector) without leaving the details page. It centralises connection logic in a new ConnectAutomationsDrawer, refactors existing detector screens and form sections to use this drawer, introduces richer empty-state messaging and permission gating, and updates the common Section UI component to support trailing action items. Supporting utilities, access checks, path helpers and extensive tests have been updated or added.

Key Changes

• Added static/app/views/detectors/components/connectAutomationsDrawer.tsx to fetch, search, connect, and disconnect automations via drawer UI
• Re-implemented DetectorDetailsAutomations empty state and edit flow to open the drawer; now shows Create a New Alert link with connectedIds pre-filled
• Replaced in-form drawer implementation with shared drawer in AutomateSection, removing duplicated code
• Extended useCanEditDetector with useCanEditDetectorWorkflowConnections and applied to UI enable/disable logic
• Enhanced Section component (now supports trailingItems, uses Heading, drops hard-coded flex wrapper)
• Updated path helper makeAutomationCreatePathname to accept query params, including connectedIds
• Refactored tests and added new coverage (automations.spec.tsx) for connect/disconnect flows, permission states, and drawer UI
• Updated ConnectedAutomationsList to accept React emptyMessage and dynamic connect/disconnect button rendering

Affected Areas

static/app/views/detectors/components/details/*
static/app/views/detectors/components/forms/automateSection.tsx
static/app/views/detectors/components/connectedAutomationList.tsx
static/app/components/workflowEngine/ui/section.tsx
• Permission helper useCanEditDetector.tsx
• Automation URL helpers

This summary was automatically generated by @propel-code-bot

@coderabbitai
Copy link

coderabbitai bot commented Nov 20, 2025

Walkthrough

This pull request refactors and extends automation connection management across the detector UI. It introduces a reusable ConnectAutomationsDrawer component, adds trailingItems support to the Section component, implements permission-based controls via a new hook, updates path generation to support query parameters, and refactors multiple detail views to use the new automation connection patterns with comprehensive test coverage.

Changes

Cohort / File(s) Summary
Section component enhancement
static/app/components/workflowEngine/ui/section.tsx
Added trailingItems prop to Section component; replaced inline SectionHeading with Flex layout containing Heading on left and optional trailing items on right; imported Heading from Sentry scraps.
Automation pathway utilities
static/app/views/automations/pathnames.tsx
Modified makeAutomationCreatePathname to accept optional query parameter with connectedIds array; appends serialized query string to generated URL using query-string.
New automation connection drawer
static/app/views/detectors/components/connectAutomationsDrawer.tsx
Introduced ConnectAutomationsDrawer component managing two subviews (ConnectedAutomations, AllAutomations) with local state for selected workflow IDs; integrates with React Query cache to toggle automation connections; wires toggle logic through shared handler.
Connected automation list type updates
static/app/views/detectors/components/connectedAutomationList.tsx
Changed emptyMessage prop type from string to React.ReactNode for richer empty state content.
Detector details automations feature
static/app/views/detectors/components/details/common/automations.tsx, automations.spec.tsx
Added interactive automation management UI with drawer control, permission-aware Edit Connected Alerts button, empty state with connect/create actions, loading/success messaging; comprehensive test suite covering render, permissions, connections, and disconnections.
Section trailing items refactoring
static/app/views/detectors/components/details/common/ongoingIssues.tsx, openPeriodIssues.tsx
Replaced inline Flex layouts with simple titles and moved View All links to trailingItems prop for consistent header composition.
Automation connection hook
static/app/views/detectors/utils/useCanEditDetector.tsx
Added useCanEditDetectorWorkflowConnections hook checking alerts:write permission for workflow connection edits.
Form and test updates
static/app/views/detectors/components/forms/automateSection.tsx, details/cron/index.spec.tsx, detail.spec.tsx
Simplified AutomateSection to delegate to external ConnectAutomationsDrawer; adjusted DOM traversal in Cron test; removed connected automations test block from metric detector tests.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Automations UI<br/>(automations.tsx)
    participant Drawer as ConnectAutomationsDrawer
    participant Cache as React Query<br/>Cache
    participant API as API
    
    User->>UI: Click "Edit Connected Alerts"
    UI->>Drawer: Open drawer with initialWorkflowIds
    Drawer->>Drawer: Render ConnectedAutomations + AllAutomations
    User->>Drawer: Select automation to connect
    Drawer->>Cache: toggleConnected(workflowId)
    Cache->>Cache: Update cached automations list
    Drawer->>UI: Call setWorkflowIds with new IDs
    UI->>API: PUT detector with updated workflowIds
    API-->>UI: Success response
    UI->>User: Show success message
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30-40 minutes

  • Multiple coordinated changes across files: The refactoring spans UI components, utilities, permissions, and tests with a cohesive automation connection theme, requiring holistic understanding of the feature architecture.
  • New component logic: ConnectAutomationsDrawer manages state, query cache mutations, and multiple subviews; requires careful review of state management and cache integration patterns.
  • Permission-based access control: New useCanEditDetectorWorkflowConnections hook introduces permission checking that should be verified against existing patterns and requirements.
  • Test coverage comprehensiveness: New test suite in automations.spec.tsx is substantial and requires verification of mock setup, API call assertions, and user interaction flows.
  • Refactoring consistency: Multiple files refactored to use trailingItems pattern and external drawer; verify all refactoring was applied consistently and removed inline patterns completely.

Poem

🐰✨ A drawer springs open, smooth and bright,
Automations connect with permission in sight,
Trailing items nest in headers with grace,
Cache whispers updates through cyberspace,
Workflows align where they should be—
A tidy refactor, hopping with glee! 🎯

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: enabling direct editing of connected alerts from the monitor details page, which aligns with the primary changes across multiple files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch copy/pr-103757-malwilley/feat/edit-connected-alerts-directly-from-monitor-details

Comment @coderabbitai help to get the list of available commands and usage tips.

@jason-test-bot
Copy link

try trigger propel code review

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
static/app/views/detectors/components/details/cron/index.spec.tsx (1)

205-206: Consider a more robust approach to locate the table.

The test now traverses up two parent elements to find the table container. While this works, DOM traversal based on parent elements is brittle and couples the test to the specific component structure. If the Section component structure changes again, this test will break.

Consider these more maintainable alternatives:

Option 1: Direct table query (if only one table exists in the view)

-const recentCheckInsHeading = await screen.findByText('Recent Check-Ins');
-const container = recentCheckInsHeading.parentElement!.parentElement!;
-const checkInTable = within(container).getByRole('table');
+await screen.findByText('Recent Check-Ins');
+const checkInTable = screen.getByRole('table');

Option 2: Use an accessible name on the table

If the table component can accept an aria-label or aria-labelledby, you could query directly:

const checkInTable = screen.getByRole('table', { name: 'Recent Check-Ins' });

Option 3: Add a test ID to the table container

In the component code, add a data-testid to the table or its container, then query:

const checkInTable = within(screen.getByTestId('check-ins-table')).getByRole('table');

These approaches would make the test more resilient to structural changes while still validating the correct behavior.

static/app/views/detectors/components/forms/automateSection.tsx (1)

1-1: Form integration with ConnectAutomationsDrawer is cleaner; consider normalizing workflowIds

Delegating all the connection logic to ConnectAutomationsDrawer and reusing ConnectedAutomationsList for the read‑only view makes this section much easier to follow.

One defensive improvement to consider: workflowIds is assumed to be a string[], but if the form ever supplies undefined/null (e.g. a missing default), workflowIds.length will throw. You could normalize it cheaply:

const rawWorkflowIds = useFormField('workflowIds') as string[] | undefined;
const workflowIds = rawWorkflowIds ?? [];

This keeps the render logic safe even if form defaults change later.

Also applies to: 14-15, 17-98

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 85be266 and bc54802.

📒 Files selected for processing (12)
  • static/app/components/workflowEngine/ui/section.tsx (1 hunks)
  • static/app/views/automations/pathnames.tsx (1 hunks)
  • static/app/views/detectors/components/connectAutomationsDrawer.tsx (1 hunks)
  • static/app/views/detectors/components/connectedAutomationList.tsx (1 hunks)
  • static/app/views/detectors/components/details/common/automations.spec.tsx (1 hunks)
  • static/app/views/detectors/components/details/common/automations.tsx (1 hunks)
  • static/app/views/detectors/components/details/common/ongoingIssues.tsx (1 hunks)
  • static/app/views/detectors/components/details/common/openPeriodIssues.tsx (1 hunks)
  • static/app/views/detectors/components/details/cron/index.spec.tsx (1 hunks)
  • static/app/views/detectors/components/forms/automateSection.tsx (1 hunks)
  • static/app/views/detectors/detail.spec.tsx (0 hunks)
  • static/app/views/detectors/utils/useCanEditDetector.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • static/app/views/detectors/detail.spec.tsx
🧰 Additional context used
🧬 Code graph analysis (5)
static/app/views/automations/pathnames.tsx (1)
static/app/utils/url/normalizeUrl.tsx (1)
  • normalizeUrl (49-89)
static/app/views/detectors/components/details/common/automations.spec.tsx (5)
tests/js/fixtures/organization.ts (1)
  • OrganizationFixture (5-93)
tests/js/fixtures/automations.ts (1)
  • AutomationFixture (14-34)
tests/js/fixtures/detectors.ts (1)
  • UptimeDetectorFixture (102-119)
static/app/views/detectors/components/details/common/automations.tsx (1)
  • DetectorDetailsAutomations (27-155)
tests/js/fixtures/project.ts (1)
  • ProjectFixture (6-65)
static/app/components/workflowEngine/ui/section.tsx (1)
static/app/components/core/text/heading.tsx (1)
  • Heading (25-72)
static/app/views/detectors/components/details/common/automations.tsx (9)
static/app/views/detectors/hooks/index.ts (1)
  • useUpdateDetector (125-148)
static/app/views/detectors/utils/useCanEditDetector.tsx (1)
  • useCanEditDetectorWorkflowConnections (8-12)
static/app/views/detectors/components/connectAutomationsDrawer.tsx (1)
  • ConnectAutomationsDrawer (72-128)
static/app/components/core/link/link.tsx (1)
  • Link (64-74)
static/app/components/workflowEngine/ui/section.tsx (1)
  • Section (15-32)
static/app/views/detectors/components/connectedAutomationList.tsx (1)
  • ConnectedAutomationsList (63-183)
static/app/components/core/layout/stack.tsx (1)
  • Stack (78-80)
static/app/components/core/text/text.tsx (1)
  • Text (113-179)
static/app/views/automations/pathnames.tsx (1)
  • makeAutomationCreatePathname (9-18)
static/app/views/detectors/components/connectAutomationsDrawer.tsx (6)
static/app/components/workflowEngine/ui/section.tsx (1)
  • Section (15-32)
static/app/views/detectors/components/connectedAutomationList.tsx (1)
  • ConnectedAutomationsList (63-183)
static/app/views/automations/components/automationListTable/search.tsx (1)
  • AutomationSearch (48-63)
static/app/views/automations/hooks/index.tsx (1)
  • makeAutomationsQueryKey (24-43)
static/app/components/globalDrawer/components.tsx (1)
  • DrawerHeader (123-156)
static/app/utils/theme/scraps/size.tsx (1)
  • space (4-20)
🔇 Additional comments (15)
static/app/views/detectors/utils/useCanEditDetector.tsx (1)

8-12: Clarify project handling in useCanEditDetectorWorkflowConnections

This hook delegates entirely to hasEveryAccess(['alerts:write'], {organization, project}) without guarding against a missing project, whereas useCanEditDetector explicitly returns false when project is undefined. If org‑level alerts:write should be sufficient to edit workflow connections even before the project loads, this is fine; otherwise, consider mirroring the !project -> false guard for consistency.

static/app/views/detectors/components/details/common/ongoingIssues.tsx (1)

33-43: Header refactor to trailingItems looks good

Using a simple string title plus trailingItems for the “View All” link aligns with the updated Section API and keeps the header layout clean. No behavioral changes to the issues query.

static/app/views/detectors/components/connectedAutomationList.tsx (1)

31-31: emptyMessage as ReactNode is a nice flexibility bump

Broadening emptyMessage to React.ReactNode while keeping the existing default preserves current behavior and allows the new richer empty state composition from detector details.

static/app/views/detectors/components/details/common/openPeriodIssues.tsx (1)

270-283: Consistent header with pluralization and trailing action

Using tn for “Ongoing Issue(s)” plus the trailingItems View All link matches the new Section pattern and keeps behavior of the issues query the same.

static/app/components/workflowEngine/ui/section.tsx (1)

3-4: Section’s trailingItems API and new header layout look solid

The addition of trailingItems and the switch to a Heading as="h3" within a Flex header provide a clean, reusable pattern for right-aligned actions. Call sites in detectors now pass simple titles and move buttons/links into trailingItems, which keeps the section markup structured and accessible.

Also applies to: 12-13, 15-27

static/app/views/detectors/components/details/common/automations.spec.tsx (1)

1-228: Good end‑to‑end coverage of automations drawer and permissions

This suite nicely validates the main flows: rendering connected alerts, empty state messaging with the connectedIds create link, connect/disconnect via the drawer, and alerts:write‑based disabling. The use of matchQuery({ids: []}) to distinguish connected vs all‑automations fetches is also clear.

If you see flakiness later, consider resetting ProjectsStore between permission tests, but as written these tests are coherent and valuable.

static/app/views/detectors/components/connectAutomationsDrawer.tsx (4)

1-13: LGTM! Imports are clean and necessary.

All imports are used appropriately in the component implementation.


40-70: LGTM! Search and pagination logic is correct.

The onSearch callback properly resets the cursor when the query changes, preventing stale pagination state.


130-135: LGTM! Styling follows theme conventions.

The DrawerContent styling correctly uses theme spacing values and provides appropriate layout for the drawer sections.


15-38: The review comment is based on an incorrect assumption about the props' purpose.

The connectedAutomationIds prop must be a Set because ConnectedAutomationsList uses it for membership checks at line 165 (connectedAutomationIds?.has(automation.id)), while automationIds is an array used to filter the API query. These serve different purposes and require different data structures. Converting the array to a Set in connectAutomationsDrawer.tsx (line 29) is correct and necessary.

Likely an incorrect or invalid review comment.

static/app/views/detectors/components/details/common/automations.tsx (5)

1-21: LGTM! New imports support the enhanced functionality.

All imports are used appropriately to implement the drawer-based automation connection workflow and permission-based controls.


27-35: LGTM! Hooks are properly initialized.

The hooks setup correctly integrates organization context, drawer management, detector updates, and permission checks.


55-78: LGTM! Drawer toggle logic is well-implemented.

The shouldCloseOnInteractOutside logic correctly prevents the drawer from closing when clicking the trigger button (lines 70-76), and the drawer receives appropriate accessibility attributes.


80-94: LGTM! Permission tooltip provides helpful guidance.

The tooltip text clearly directs users to the appropriate settings page with a deep link to the alertsMemberWrite setting (line 89).


97-153: LGTM! Empty state provides clear user guidance.

The rich empty state (lines 116-150) clearly explains the situation and offers actionable next steps. The ref sharing between the Edit button (line 101) and Connect button (line 128) is correct since they're mutually exclusive (Edit shows when automations exist, Connect shows in empty state). Line 138 correctly prefills connectedIds with the detector ID for seamless alert creation.

Comment on lines +1 to +2
import qs from 'query-string';

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Avoid trailing ? and align array handling for connectedIds

The new query support is useful, but two edge cases are worth tightening up:

  • When query is {} or only contains undefined values, qs.stringify(query) is empty, so the URL becomes /new/?. Consider only appending the ? when the serialized query is non‑empty.
  • connectedIds is typed as string[], but qs.stringify’s default array format will produce a comma‑joined string (e.g. connectedIds=a,b). If the create page later parses this into an array, make sure its parsing options match how you serialize (or set an explicit arrayFormat here).

A small refactor keeps the API flexible without changing current behavior:

export const makeAutomationCreatePathname = (
  orgSlug: string,
  query: {connectedIds?: string[]} = {}
) => {
  const queryString = qs.stringify(query);
  const base = `${makeAutomationBasePathname(orgSlug)}new/`;
  return normalizeUrl(queryString ? `${base}?${queryString}` : base);
};

Also applies to: 9-17


🌐 Web query:

What is the default array serialization/parse behavior of the `query-string` (qs) package for values like `{connectedIds: ['a','b']}`?

💡 Result:

Default: arrayFormat = "none" — arrays are serialized as duplicate keys.
Example: { connectedIds: ['a','b'] } → "connectedIds=a&connectedIds=b", and parse will read duplicate keys back into ['a','b']. (github.com)

Citations:


Avoid trailing ? when query is empty

The new query support is useful. One edge case worth tightening: when query is {} or only contains undefined values, the URL becomes /new/? with a bare trailing ?. Conditionally append the query string only when non-empty:

export const makeAutomationCreatePathname = (
  orgSlug: string,
  query: {connectedIds?: string[]} = {}
) => {
  const queryString = qs.stringify(query);
  const base = `${makeAutomationBasePathname(orgSlug)}new/`;
  return normalizeUrl(queryString ? `${base}?${queryString}` : base);
};

The default query-string behavior handles array serialization correctly (duplicate keys like connectedIds=a&connectedIds=b), so no additional configuration needed.

Also applies to: 9-17

🤖 Prompt for AI Agents
In static/app/views/automations/pathnames.tsx around lines 1-2 (and apply same
change to lines 9-17), the pathname builder always appends a '?' when the query
object is empty which yields URLs like '/new/?'; fix by stringifying the query
with qs.stringify into a variable and only append `?${queryString}` to the base
pathname when that string is non-empty, otherwise return the base pathname
unchanged (leave qs.stringify default array behavior intact).

}) {
const organization = useOrganization();
const queryClient = useQueryClient();
const [localWorkflowIds, setLocalWorkflowIds] = useState(initialWorkflowIds);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Stale local state if prop changes.

localWorkflowIds is initialized with initialWorkflowIds but never re-synced if the prop changes while the drawer is open. If the detector is updated externally (e.g., by another user or tab), the drawer will show stale data.

Consider syncing local state when initialWorkflowIds changes:

  const [localWorkflowIds, setLocalWorkflowIds] = useState(initialWorkflowIds);
+
+ useEffect(() => {
+   setLocalWorkflowIds(initialWorkflowIds);
+ }, [initialWorkflowIds]);

Add the import:

import {Fragment, useCallback, useEffect, useState} from 'react';
🤖 Prompt for AI Agents
In static/app/views/detectors/components/connectAutomationsDrawer.tsx around
line 81, localWorkflowIds is initialized from initialWorkflowIds but never
updated when that prop changes; add useEffect to watch initialWorkflowIds and
call setLocalWorkflowIds(initialWorkflowIds) so the drawer resyncs when the prop
updates, and add useEffect to the component imports (e.g., import {Fragment,
useCallback, useEffect, useState} from 'react'); keep the effect simple and
synchronous (no extra logic) so it only updates local state when the incoming
prop changes.

Comment on lines +83 to +111
const toggleConnected = ({automation}: {automation: Automation}) => {
const oldAutomationsData =
getApiQueryData<Automation[]>(
queryClient,
makeAutomationsQueryKey({
orgSlug: organization.slug,
ids: localWorkflowIds,
})
) ?? [];

const newAutomations = (
oldAutomationsData.some(a => a.id === automation.id)
? oldAutomationsData.filter(a => a.id !== automation.id)
: [...oldAutomationsData, automation]
).sort((a, b) => a.id.localeCompare(b.id));
const newWorkflowIds = newAutomations.map(a => a.id);

setApiQueryData<Automation[]>(
queryClient,
makeAutomationsQueryKey({
orgSlug: organization.slug,
ids: newWorkflowIds,
}),
newAutomations
);

setLocalWorkflowIds(newWorkflowIds);
setWorkflowIds(newWorkflowIds);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for cache operations.

The toggleConnected function updates the query cache optimistically without error handling or rollback logic. If the cache operations fail or if setWorkflowIds fails, the UI state could become inconsistent with the server state.

Consider wrapping cache operations in try-catch and implementing rollback on error:

  const toggleConnected = ({automation}: {automation: Automation}) => {
+   try {
      const oldAutomationsData =
        getApiQueryData<Automation[]>(
          queryClient,
          makeAutomationsQueryKey({
            orgSlug: organization.slug,
            ids: localWorkflowIds,
          })
        ) ?? [];

      const newAutomations = (
        oldAutomationsData.some(a => a.id === automation.id)
          ? oldAutomationsData.filter(a => a.id !== automation.id)
          : [...oldAutomationsData, automation]
      ).sort((a, b) => a.id.localeCompare(b.id));
      const newWorkflowIds = newAutomations.map(a => a.id);

      setApiQueryData<Automation[]>(
        queryClient,
        makeAutomationsQueryKey({
          orgSlug: organization.slug,
          ids: newWorkflowIds,
        }),
        newAutomations
      );

      setLocalWorkflowIds(newWorkflowIds);
      setWorkflowIds(newWorkflowIds);
+   } catch (error) {
+     // Consider adding error notification here
+     console.error('Failed to toggle automation connection:', error);
+   }
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const toggleConnected = ({automation}: {automation: Automation}) => {
const oldAutomationsData =
getApiQueryData<Automation[]>(
queryClient,
makeAutomationsQueryKey({
orgSlug: organization.slug,
ids: localWorkflowIds,
})
) ?? [];
const newAutomations = (
oldAutomationsData.some(a => a.id === automation.id)
? oldAutomationsData.filter(a => a.id !== automation.id)
: [...oldAutomationsData, automation]
).sort((a, b) => a.id.localeCompare(b.id));
const newWorkflowIds = newAutomations.map(a => a.id);
setApiQueryData<Automation[]>(
queryClient,
makeAutomationsQueryKey({
orgSlug: organization.slug,
ids: newWorkflowIds,
}),
newAutomations
);
setLocalWorkflowIds(newWorkflowIds);
setWorkflowIds(newWorkflowIds);
};
const toggleConnected = ({automation}: {automation: Automation}) => {
try {
const oldAutomationsData =
getApiQueryData<Automation[]>(
queryClient,
makeAutomationsQueryKey({
orgSlug: organization.slug,
ids: localWorkflowIds,
})
) ?? [];
const newAutomations = (
oldAutomationsData.some(a => a.id === automation.id)
? oldAutomationsData.filter(a => a.id !== automation.id)
: [...oldAutomationsData, automation]
).sort((a, b) => a.id.localeCompare(b.id));
const newWorkflowIds = newAutomations.map(a => a.id);
setApiQueryData<Automation[]>(
queryClient,
makeAutomationsQueryKey({
orgSlug: organization.slug,
ids: newWorkflowIds,
}),
newAutomations
);
setLocalWorkflowIds(newWorkflowIds);
setWorkflowIds(newWorkflowIds);
} catch (error) {
// Consider adding error notification here
console.error('Failed to toggle automation connection:', error);
}
};
🤖 Prompt for AI Agents
In static/app/views/detectors/components/connectAutomationsDrawer.tsx around
lines 83 to 111, the toggleConnected function updates the query cache and local
state optimistically without any error handling or rollback; wrap the cache and
state updates in a try-catch, first capture the previous automations and
workflowIds, then perform setApiQueryData, setLocalWorkflowIds and
setWorkflowIds inside the try; in the catch restore the previous cache and state
(setApiQueryData with the old automations, setLocalWorkflowIds and
setWorkflowIds with the old ids) and surface or log the error so
callers/telemetry can observe the failure.

Comment on lines +37 to +53
const setWorkflowIds = useCallback(
(newWorkflowIds: string[]) => {
addLoadingMessage();
updateDetector(
{
detectorId: detector.id,
workflowIds: newWorkflowIds,
},
{
onSuccess: () => {
addSuccessMessage(t('Connected alerts updated'));
},
}
);
},
[detector.id, updateDetector]
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Consider handling loading message cleanup on error.

Line 39 calls addLoadingMessage() before the mutation, and line 47 shows success. However, if the mutation fails, the loading message may not be automatically cleared. While useUpdateDetector shows error handling via AlertStore.addAlert (from the code snippet), verify that the loading indicator is properly dismissed on error.


🏁 Script executed:

#!/bin/bash
# Description: Check if addLoadingMessage auto-clears or needs manual cleanup

rg -nP -C3 'addLoadingMessage|clearIndicators' --type=ts

Length of output: 182028


I need to examine the actual file to verify the complete implementation and error handling.Based on my search results and analysis, I can now provide my verification. The web search results confirm that the standard React Query mutation pattern requires error handling cleanup, but I haven't been able to locate the exact useUpdateDetector hook implementation. However, from the patterns observed in the first search output and the React Query documentation from web search, I can see that:

  1. React Query mutations support onSuccess, onError, and onSettled callbacks
  2. A common pattern for error handling in mutations is to use onError callbacks to handle cleanup
  3. From the original rg search output, I can see the file imports addLoadingMessage and addSuccessMessage but the snippet doesn't show clearIndicators imported, and doesn't show an onError handler

The review comment's concern is valid based on standard mutation patterns. The code shows addLoadingMessage() called before the mutation, and addSuccessMessage() in the onSuccess handler, but the provided snippet doesn't reveal whether there's error handling to clear the loading message on failure.


Add error handling to clear the loading message on mutation failure.

The code calls addLoadingMessage() before the mutation starts (line 39) and shows success messaging in the onSuccess handler (line 47), but there's no visible error handling to clear the loading indicator if the mutation fails. React Query provides onError callbacks that fire when a mutation encounters an error, which should call clearIndicators() to dismiss the loading state, ensuring users aren't stuck with a perpetual loading message on failure. Add an onError handler to the updateDetector call to clean up the indicator.


🏁 Script executed:

#!/bin/bash
# Get more context around lines 37-53 in automations.tsx, focusing on error handling

cat static/app/views/detectors/components/details/common/automations.tsx | sed -n '37,60p'

Length of output: 494



🏁 Script executed:

#!/bin/bash
# Check file imports to see what indicator functions are available

head -20 static/app/views/detectors/components/details/common/automations.tsx

Length of output: 1114


Add error handling to clear the loading message on mutation failure.

The code imports only addLoadingMessage and addSuccessMessage, but clearIndicators is missing. Line 39 shows addLoadingMessage() before the mutation, and line 47 shows addSuccessMessage() in the onSuccess handler, but there's no onError callback. If the updateDetector mutation fails, the loading indicator will persist indefinitely. Import clearIndicators from sentry/actionCreators/indicator and add an onError handler to the updateDetector call to ensure the loading state is cleared on failure:

onError: () => {
  clearIndicators();
}
🤖 Prompt for AI Agents
In static/app/views/detectors/components/details/common/automations.tsx around
lines 37 to 53, the mutation call adds a loading indicator but lacks error
handling to clear it; import clearIndicators from
'sentry/actionCreators/indicator' and add an onError handler to the
updateDetector call that invokes clearIndicators() so the loading state is
cleared when the mutation fails, keeping the existing addSuccessMessage in
onSuccess.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants