From 9522a995d8b5834f35c98b92224d6549de771a39 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Fri, 27 Mar 2026 12:23:10 -0400 Subject: [PATCH 1/2] feat(autofix): Autotrigger root cause if legacy autofix ran If legacy autofix has run, we should at least auto trigger explorer autofix to run root cause. --- .../autofix/v3/useAutoTriggerAutofix.spec.tsx | 63 +++++++++++++++++++ .../autofix/v3/useAutoTriggerAutofix.tsx | 34 ++++++++++ static/app/types/group.tsx | 1 + .../streamline/sidebar/autofixSection.tsx | 3 + 4 files changed, 101 insertions(+) create mode 100644 static/app/components/events/autofix/v3/useAutoTriggerAutofix.spec.tsx create mode 100644 static/app/components/events/autofix/v3/useAutoTriggerAutofix.tsx diff --git a/static/app/components/events/autofix/v3/useAutoTriggerAutofix.spec.tsx b/static/app/components/events/autofix/v3/useAutoTriggerAutofix.spec.tsx new file mode 100644 index 00000000000000..a0116aab7c7513 --- /dev/null +++ b/static/app/components/events/autofix/v3/useAutoTriggerAutofix.spec.tsx @@ -0,0 +1,63 @@ +import {GroupFixture} from 'sentry-fixture/group'; + +import {renderHook} from 'sentry-test/reactTestingLibrary'; + +import type {useExplorerAutofix} from 'sentry/components/events/autofix/useExplorerAutofix'; +import {useAutotriggerAutofix} from 'sentry/components/events/autofix/v3/useAutotriggerAutofix'; + +function makeAutofix( + overrides: Partial> = {} +): ReturnType { + return { + runState: null, + startStep: jest.fn(), + createPR: jest.fn(), + reset: jest.fn(), + triggerCodingAgentHandoff: jest.fn(), + isLoading: false, + isPolling: false, + ...overrides, + } as ReturnType; +} + +describe('useAutotriggerAutofix', () => { + it('starts root_cause when seerAutofixLastTriggered is set but seerExplorerAutofixLastTriggered is not', () => { + const autofix = makeAutofix(); + const group = GroupFixture({ + seerAutofixLastTriggered: '2024-01-01T00:00:00Z', + seerExplorerAutofixLastTriggered: null, + }); + + renderHook(() => useAutotriggerAutofix({autofix, group})); + + expect(autofix.startStep).toHaveBeenCalledWith('root_cause'); + expect(autofix.startStep).toHaveBeenCalledTimes(1); + }); + + it('does not start root_cause when seerExplorerAutofixLastTriggered is set', () => { + const autofix = makeAutofix(); + const group = GroupFixture({ + seerAutofixLastTriggered: '2024-01-01T00:00:00Z', + seerExplorerAutofixLastTriggered: '2024-01-02T00:00:00Z', + }); + + renderHook(() => useAutotriggerAutofix({autofix, group})); + + expect(autofix.startStep).not.toHaveBeenCalled(); + }); + + it('does not trigger root_cause more than once on re-render', () => { + const autofix = makeAutofix(); + const group = GroupFixture({ + seerAutofixLastTriggered: '2024-01-01T00:00:00Z', + seerExplorerAutofixLastTriggered: null, + }); + + const {rerender} = renderHook(() => useAutotriggerAutofix({autofix, group})); + + rerender(); + rerender(); + + expect(autofix.startStep).toHaveBeenCalledTimes(1); + }); +}); diff --git a/static/app/components/events/autofix/v3/useAutoTriggerAutofix.tsx b/static/app/components/events/autofix/v3/useAutoTriggerAutofix.tsx new file mode 100644 index 00000000000000..4cca30ef1bc122 --- /dev/null +++ b/static/app/components/events/autofix/v3/useAutoTriggerAutofix.tsx @@ -0,0 +1,34 @@ +import {useEffect, useRef} from 'react'; + +import {useExplorerAutofix} from 'sentry/components/events/autofix/useExplorerAutofix'; +import type {Group} from 'sentry/types/group'; + +interface UseAutotriggerAutofixOptions { + autofix: ReturnType; + group: Group; +} + +export function useAutotriggerAutofix({autofix, group}: UseAutotriggerAutofixOptions) { + const alreadyTriggered = useRef(false); + + // extract startStep first here so we can depend on it directly as `autofix` itself is unstable. + const startStep = autofix.startStep; + + useEffect(() => { + if (alreadyTriggered.current) { + return; + } + + // In order to have a smooth transition from legacy to explorer autofix, we want to automatically + // trigger autofix when users view an issue that had legacy but not explorer autofix. + const shouldAutotriggerAutofix = + !!group.seerAutofixLastTriggered && !group.seerExplorerAutofixLastTriggered; + + if (!shouldAutotriggerAutofix) { + return; + } + + alreadyTriggered.current = true; + startStep('root_cause'); + }, [group, startStep]); +} diff --git a/static/app/types/group.tsx b/static/app/types/group.tsx index a3dd6585b4ec36..8cd5a8d7fde37e 100644 --- a/static/app/types/group.tsx +++ b/static/app/types/group.tsx @@ -1019,6 +1019,7 @@ export interface BaseGroup { latestEventHasAttachments?: boolean; owners?: SuggestedOwner[] | null; seerAutofixLastTriggered?: string | null; + seerExplorerAutofixLastTriggered?: string | null; seerFixabilityScore?: number | null; sentryAppIssues?: PlatformExternalIssue[]; substatus?: GroupSubstatus | null; diff --git a/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx b/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx index cc82c9a6f407a8..e5900235aab782 100644 --- a/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx @@ -29,6 +29,7 @@ import { RootCausePreview, SolutionPreview, } from 'sentry/components/events/autofix/v3/autofixPreviews'; +import {useAutotriggerAutofix} from 'sentry/components/events/autofix/v3/useAutotriggerAutofix'; import {useGroupSummaryData} from 'sentry/components/group/groupSummary'; import {HookOrDefault} from 'sentry/components/hookOrDefault'; import {Placeholder} from 'sentry/components/placeholder'; @@ -125,6 +126,8 @@ export function AutofixContent({aiConfig, group, project, event}: AutofixContent const autofix = useExplorerAutofix(group.id); const {data: setupCheck, isPending} = useSeerOnboardingCheck(); + useAutotriggerAutofix({autofix, group}); + if ( // waiting on the onboarding checks to load isPending || From 23f4592a26ceb9ae59eb33658f35ac5cd859f324 Mon Sep 17 00:00:00 2001 From: Tony Xiao Date: Fri, 27 Mar 2026 12:29:43 -0400 Subject: [PATCH 2/2] fix typo --- .../events/autofix/v3/useAutoTriggerAutofix.spec.tsx | 10 +++++----- .../events/autofix/v3/useAutoTriggerAutofix.tsx | 4 ++-- .../issueDetails/streamline/sidebar/autofixSection.tsx | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/static/app/components/events/autofix/v3/useAutoTriggerAutofix.spec.tsx b/static/app/components/events/autofix/v3/useAutoTriggerAutofix.spec.tsx index a0116aab7c7513..4590f902580153 100644 --- a/static/app/components/events/autofix/v3/useAutoTriggerAutofix.spec.tsx +++ b/static/app/components/events/autofix/v3/useAutoTriggerAutofix.spec.tsx @@ -3,7 +3,7 @@ import {GroupFixture} from 'sentry-fixture/group'; import {renderHook} from 'sentry-test/reactTestingLibrary'; import type {useExplorerAutofix} from 'sentry/components/events/autofix/useExplorerAutofix'; -import {useAutotriggerAutofix} from 'sentry/components/events/autofix/v3/useAutotriggerAutofix'; +import {useAutoTriggerAutofix} from 'sentry/components/events/autofix/v3/useAutoTriggerAutofix'; function makeAutofix( overrides: Partial> = {} @@ -20,7 +20,7 @@ function makeAutofix( } as ReturnType; } -describe('useAutotriggerAutofix', () => { +describe('useAutoTriggerAutofix', () => { it('starts root_cause when seerAutofixLastTriggered is set but seerExplorerAutofixLastTriggered is not', () => { const autofix = makeAutofix(); const group = GroupFixture({ @@ -28,7 +28,7 @@ describe('useAutotriggerAutofix', () => { seerExplorerAutofixLastTriggered: null, }); - renderHook(() => useAutotriggerAutofix({autofix, group})); + renderHook(() => useAutoTriggerAutofix({autofix, group})); expect(autofix.startStep).toHaveBeenCalledWith('root_cause'); expect(autofix.startStep).toHaveBeenCalledTimes(1); @@ -41,7 +41,7 @@ describe('useAutotriggerAutofix', () => { seerExplorerAutofixLastTriggered: '2024-01-02T00:00:00Z', }); - renderHook(() => useAutotriggerAutofix({autofix, group})); + renderHook(() => useAutoTriggerAutofix({autofix, group})); expect(autofix.startStep).not.toHaveBeenCalled(); }); @@ -53,7 +53,7 @@ describe('useAutotriggerAutofix', () => { seerExplorerAutofixLastTriggered: null, }); - const {rerender} = renderHook(() => useAutotriggerAutofix({autofix, group})); + const {rerender} = renderHook(() => useAutoTriggerAutofix({autofix, group})); rerender(); rerender(); diff --git a/static/app/components/events/autofix/v3/useAutoTriggerAutofix.tsx b/static/app/components/events/autofix/v3/useAutoTriggerAutofix.tsx index 4cca30ef1bc122..51fa9db989c39f 100644 --- a/static/app/components/events/autofix/v3/useAutoTriggerAutofix.tsx +++ b/static/app/components/events/autofix/v3/useAutoTriggerAutofix.tsx @@ -3,12 +3,12 @@ import {useEffect, useRef} from 'react'; import {useExplorerAutofix} from 'sentry/components/events/autofix/useExplorerAutofix'; import type {Group} from 'sentry/types/group'; -interface UseAutotriggerAutofixOptions { +interface UseAutoTriggerAutofixOptions { autofix: ReturnType; group: Group; } -export function useAutotriggerAutofix({autofix, group}: UseAutotriggerAutofixOptions) { +export function useAutoTriggerAutofix({autofix, group}: UseAutoTriggerAutofixOptions) { const alreadyTriggered = useRef(false); // extract startStep first here so we can depend on it directly as `autofix` itself is unstable. diff --git a/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx b/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx index e5900235aab782..5416d04c20e570 100644 --- a/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/autofixSection.tsx @@ -29,7 +29,7 @@ import { RootCausePreview, SolutionPreview, } from 'sentry/components/events/autofix/v3/autofixPreviews'; -import {useAutotriggerAutofix} from 'sentry/components/events/autofix/v3/useAutotriggerAutofix'; +import {useAutoTriggerAutofix} from 'sentry/components/events/autofix/v3/useAutoTriggerAutofix'; import {useGroupSummaryData} from 'sentry/components/group/groupSummary'; import {HookOrDefault} from 'sentry/components/hookOrDefault'; import {Placeholder} from 'sentry/components/placeholder'; @@ -126,7 +126,7 @@ export function AutofixContent({aiConfig, group, project, event}: AutofixContent const autofix = useExplorerAutofix(group.id); const {data: setupCheck, isPending} = useSeerOnboardingCheck(); - useAutotriggerAutofix({autofix, group}); + useAutoTriggerAutofix({autofix, group}); if ( // waiting on the onboarding checks to load