diff --git a/static/app/components/events/autofix/useExplorerAutofix.tsx b/static/app/components/events/autofix/useExplorerAutofix.tsx index 0efe3993f7a3fa..e327e4808976bc 100644 --- a/static/app/components/events/autofix/useExplorerAutofix.tsx +++ b/static/app/components/events/autofix/useExplorerAutofix.tsx @@ -541,10 +541,20 @@ export function useExplorerAutofix( * @param runId - Optional run ID to continue an existing run */ const startStep = useCallback( - async (step: AutofixExplorerStep, runId?: number) => { + async (step: AutofixExplorerStep, runId?: number, userContext?: string) => { setWaitingForResponse(true); try { + const data: Record = {step}; + + if (defined(runId)) { + data.run_id = runId; + } + + if (userContext) { + data.user_context = userContext; + } + const response = await api.requestPromise( getApiUrl('/organizations/$organizationIdOrSlug/issues/$issueId/autofix/', { path: {organizationIdOrSlug: orgSlug, issueId: groupId}, @@ -552,11 +562,7 @@ export function useExplorerAutofix( { method: 'POST', query: {mode: 'explorer'}, - data: { - step, - intelligence_level: 'low', - ...(runId !== undefined && {run_id: runId}), - }, + data, } ); diff --git a/static/app/components/events/autofix/v3/nextStep.spec.tsx b/static/app/components/events/autofix/v3/nextStep.spec.tsx index 02e675cec2a638..5424cc653627bd 100644 --- a/static/app/components/events/autofix/v3/nextStep.spec.tsx +++ b/static/app/components/events/autofix/v3/nextStep.spec.tsx @@ -69,13 +69,46 @@ describe('SeerDrawerNextStep', () => { expect(autofix.startStep).toHaveBeenCalledWith('solution', 1); }); - it('calls startStep with root_cause on no click', async () => { + it('shows feedback UI on no click', async () => { const autofix = makeAutofix(); render( ); await userEvent.click(screen.getByRole('button', {name: 'No'})); - expect(autofix.startStep).toHaveBeenCalledWith('root_cause', 1); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + expect( + screen.getByRole('button', {name: 'Rethink root cause'}) + ).toBeInTheDocument(); + expect( + screen.getByRole('button', {name: 'Nevermind, make an implementation plan'}) + ).toBeInTheDocument(); + }); + + it('calls startStep with root_cause and feedback on rethink click', async () => { + const autofix = makeAutofix(); + render( + + ); + await userEvent.click(screen.getByRole('button', {name: 'No'})); + await userEvent.type(screen.getByRole('textbox'), 'Try a different approach'); + await userEvent.click(screen.getByRole('button', {name: 'Rethink root cause'})); + expect(autofix.startStep).toHaveBeenCalledWith( + 'root_cause', + 1, + 'Try a different approach' + ); + }); + + it('proceeds like yes on nevermind click', async () => { + const autofix = makeAutofix(); + render( + + ); + await userEvent.click(screen.getByRole('button', {name: 'No'})); + await userEvent.click( + screen.getByRole('button', {name: 'Nevermind, make an implementation plan'}) + ); + expect(autofix.startStep).toHaveBeenCalledWith('solution', 1); }); }); @@ -103,13 +136,48 @@ describe('SeerDrawerNextStep', () => { expect(autofix.startStep).toHaveBeenCalledWith('code_changes', 1); }); - it('calls startStep with solution on no click', async () => { + it('shows feedback UI on no click', async () => { const autofix = makeAutofix(); render( ); await userEvent.click(screen.getByRole('button', {name: 'No'})); - expect(autofix.startStep).toHaveBeenCalledWith('solution', 1); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + expect( + screen.getByRole('button', {name: 'Rethink implementation plan'}) + ).toBeInTheDocument(); + expect( + screen.getByRole('button', {name: 'Nevermind, write a code fix'}) + ).toBeInTheDocument(); + }); + + it('calls startStep with solution and feedback on rethink click', async () => { + const autofix = makeAutofix(); + render( + + ); + await userEvent.click(screen.getByRole('button', {name: 'No'})); + await userEvent.type(screen.getByRole('textbox'), 'Consider edge cases'); + await userEvent.click( + screen.getByRole('button', {name: 'Rethink implementation plan'}) + ); + expect(autofix.startStep).toHaveBeenCalledWith( + 'solution', + 1, + 'Consider edge cases' + ); + }); + + it('proceeds like yes on nevermind click', async () => { + const autofix = makeAutofix(); + render( + + ); + await userEvent.click(screen.getByRole('button', {name: 'No'})); + await userEvent.click( + screen.getByRole('button', {name: 'Nevermind, write a code fix'}) + ); + expect(autofix.startStep).toHaveBeenCalledWith('code_changes', 1); }); }); @@ -135,13 +203,44 @@ describe('SeerDrawerNextStep', () => { expect(autofix.createPR).toHaveBeenCalledWith(1); }); - it('calls startStep with code_changes on no click', async () => { + it('shows feedback UI on no click', async () => { const autofix = makeAutofix(); render( ); await userEvent.click(screen.getByRole('button', {name: 'No'})); - expect(autofix.startStep).toHaveBeenCalledWith('code_changes', 1); + expect(screen.getByRole('textbox')).toBeInTheDocument(); + expect( + screen.getByRole('button', {name: 'Rethink code changes'}) + ).toBeInTheDocument(); + expect( + screen.getByRole('button', {name: 'Nevermind, draft a PR'}) + ).toBeInTheDocument(); + }); + + it('calls startStep with code_changes and feedback on rethink click', async () => { + const autofix = makeAutofix(); + render( + + ); + await userEvent.click(screen.getByRole('button', {name: 'No'})); + await userEvent.type(screen.getByRole('textbox'), 'Fix the error handling'); + await userEvent.click(screen.getByRole('button', {name: 'Rethink code changes'})); + expect(autofix.startStep).toHaveBeenCalledWith( + 'code_changes', + 1, + 'Fix the error handling' + ); + }); + + it('proceeds like yes on nevermind click', async () => { + const autofix = makeAutofix(); + render( + + ); + await userEvent.click(screen.getByRole('button', {name: 'No'})); + await userEvent.click(screen.getByRole('button', {name: 'Nevermind, draft a PR'})); + expect(autofix.createPR).toHaveBeenCalledWith(1); }); }); }); diff --git a/static/app/components/events/autofix/v3/nextStep.tsx b/static/app/components/events/autofix/v3/nextStep.tsx index 264af9ededc951..2aaa0160868c9a 100644 --- a/static/app/components/events/autofix/v3/nextStep.tsx +++ b/static/app/components/events/autofix/v3/nextStep.tsx @@ -1,8 +1,9 @@ -import {useCallback, type ReactNode} from 'react'; +import {useCallback, useState, type ReactNode} from 'react'; import {Button} from '@sentry/scraps/button'; import {Flex} from '@sentry/scraps/layout'; import {Text} from '@sentry/scraps/text'; +import {TextArea} from '@sentry/scraps/textarea'; import { isCodeChangesSection, @@ -54,18 +55,24 @@ function RootCauseNextStep({autofix, runId}: NextStepProps) { startStep('solution', runId); }, [startStep, runId]); - const handleNoClick = useCallback(() => { - // for now, just re run the current step - startStep('root_cause', runId); - }, [startStep, runId]); + const handleNoClick = useCallback( + (userContext: string) => { + startStep('root_cause', runId, userContext); + }, + [startStep, runId] + ); return ( - ); } @@ -77,18 +84,26 @@ function SolutionNextStep({autofix, runId}: NextStepProps) { startStep('code_changes', runId); }, [startStep, runId]); - const handleNoClick = useCallback(() => { - // for now, just re run the current step - startStep('solution', runId); - }, [startStep, runId]); + const handleNoClick = useCallback( + (userContext: string) => { + startStep('solution', runId, userContext); + }, + [startStep, runId] + ); return ( - ); } @@ -100,41 +115,80 @@ function CodeChangesNextStep({autofix, runId}: NextStepProps) { createPR(runId); }, [createPR, runId]); - const handleNoClick = useCallback(() => { - startStep('code_changes', runId); - }, [startStep, runId]); + const handleNoClick = useCallback( + (userContext: string) => { + startStep('code_changes', runId, userContext); + }, + [startStep, runId] + ); return ( - ); } interface NextStepTemplateProps { + labelNevermind: ReactNode; labelNo: ReactNode; + labelRethink: ReactNode; labelYes: ReactNode; - onClickNo: () => void; + onClickNo: (prompt: string) => void; onClickYes: () => void; + placeholderPrompt: string; prompt: ReactNode; + rethinkPrompt: ReactNode; } -function NextStep({ +function NextStepTemplate({ prompt, labelYes, onClickYes, labelNo, onClickNo, + placeholderPrompt, + rethinkPrompt, + labelNevermind, + labelRethink, }: NextStepTemplateProps) { + const [clickedNo, handleClickedNo] = useState(false); + const [userContext, setUserContext] = useState(''); + + if (clickedNo) { + return ( + + {rethinkPrompt} +