diff --git a/static/app/views/settings/seer/seerAgentHooks.spec.tsx b/static/app/views/settings/seer/seerAgentHooks.spec.tsx index 5a34dbf3596a6c..82691b24dd6765 100644 --- a/static/app/views/settings/seer/seerAgentHooks.spec.tsx +++ b/static/app/views/settings/seer/seerAgentHooks.spec.tsx @@ -13,7 +13,6 @@ import type {CodingAgentIntegration} from 'sentry/components/events/autofix/useA import {ProjectsStore} from 'sentry/stores/projectsStore'; import {useQueryClient} from 'sentry/utils/queryClient'; import { - useAgentOptions, useBulkMutateCreatePr, useMutateCreatePr, useMutateSelectedAgent, @@ -35,65 +34,31 @@ describe('seerAgentHooks', () => { ProjectsStore.reset(); }); - describe('useAgentOptions', () => { - it('returns Seer, integration options', () => { - const integrations: CodingAgentIntegration[] = [ - {id: '42', name: 'Cursor', provider: 'cursor'}, - ]; - - const {result} = renderHookWithProviders(useAgentOptions, { - initialProps: {integrations}, - organization, - }); - - const options = result.current; - expect(options).toHaveLength(3); - expect(options[0]).toEqual({value: 'seer', label: expect.any(String)}); - expect(options[1]).toMatchObject({ - value: {id: '42', name: 'Cursor', provider: 'cursor'}, - label: 'Cursor', - }); - expect(options[2]).toEqual({value: 'none', label: 'No Handoff'}); - }); - - it('filters out integrations without id', () => { - const integrations: CodingAgentIntegration[] = [ - {id: null, name: 'No Id', provider: 'other'}, - {id: '1', name: 'With Id', provider: 'cursor'}, - ]; - - const {result} = renderHookWithProviders(useAgentOptions, { - initialProps: {integrations}, - organization, - }); - - const options = result.current; - expect(options).toHaveLength(3); - expect(options[1]!.value).toMatchObject({id: '1', name: 'With Id'}); - }); - }); - describe('useSelectedAgentFromProjectSettings', () => { - it('returns "none" when project autofixAutomationTuning is off', () => { - const p = ProjectFixture({...project, autofixAutomationTuning: 'off'}); - + it('returns "seer" when no automation_handoff integration_id', () => { const {result} = renderHookWithProviders(useSelectedAgentFromProjectSettings, { initialProps: { - preference: {repositories: []}, - project: p, + preference: { + repositories: [], + }, integrations: [], }, organization, }); - expect(result.current).toBe('none'); + expect(result.current).toBe('seer'); }); - it('returns "seer" when no automation_handoff integration_id', () => { + it('returns "seer" when no automation_handoff integration_id, ignoring autofixAutomationTuning', () => { const {result} = renderHookWithProviders(useSelectedAgentFromProjectSettings, { initialProps: { - preference: {repositories: []}, - project, + preference: { + projectId: '1', + autofixAutomationTuning: 'off', + automatedRunStoppingPoint: 'code_changes', + automation_handoff: undefined, + repositories: [], + }, integrations: [], }, organization, @@ -117,7 +82,6 @@ describe('seerAgentHooks', () => { integration_id: 99, }, }, - project, integrations, }, organization, @@ -128,7 +92,7 @@ describe('seerAgentHooks', () => { }); describe('useSelectedAgentFromBulkSettings', () => { - it('returns "none" when autofixAutomationTuning is off', () => { + it('returns "seer" when automationHandoff undefined, doesnt look at autofixAutomationTuning', () => { const {result} = renderHookWithProviders(useSelectedAgentFromBulkSettings, { initialProps: { autofixSettings: { @@ -143,7 +107,7 @@ describe('seerAgentHooks', () => { organization, }); - expect(result.current).toBe('none'); + expect(result.current).toBe('seer'); }); it('returns "seer" when no automationHandoff integration_id', () => { diff --git a/static/app/views/settings/seer/seerAgentHooks.tsx b/static/app/views/settings/seer/seerAgentHooks.tsx index 770ed07b8e3f24..962b7b52941adf 100644 --- a/static/app/views/settings/seer/seerAgentHooks.tsx +++ b/static/app/views/settings/seer/seerAgentHooks.tsx @@ -23,39 +23,14 @@ import {fetchDataQuery, fetchMutation, useQueryClient} from 'sentry/utils/queryC import {RequestError} from 'sentry/utils/requestError/requestError'; import {useOrganization} from 'sentry/utils/useOrganization'; -export function useAgentOptions({ - integrations, -}: { - integrations: CodingAgentIntegration[]; -}) { - return useMemo(() => { - return [ - {value: 'seer' as const, label: t('Seer Agent')}, - ...integrations - .filter(integration => integration.id) - .map(integration => ({ - value: integration, - label: integration.name, - })), - {value: 'none' as const, label: t('No Handoff')}, - ]; - }, [integrations]); -} - export function useSelectedAgentFromProjectSettings({ integrations, preference, - project, }: { integrations: CodingAgentIntegration[]; preference: ProjectSeerPreferences; - project: Project; }) { return useMemo(() => { - // If we have autofixAutomationTuning==OFF then 'none' is picked - if (project.autofixAutomationTuning === 'off') { - return 'none'; - } // If we have nothing in preferences, then we have Seer if (!preference?.automation_handoff?.integration_id) { return 'seer'; @@ -65,11 +40,7 @@ export function useSelectedAgentFromProjectSettings({ integration => integration.id === String(preference.automation_handoff?.integration_id) ); - }, [ - preference?.automation_handoff?.integration_id, - project.autofixAutomationTuning, - integrations, - ]); + }, [preference.automation_handoff?.integration_id, integrations]); } export function useSelectedAgentFromBulkSettings({ @@ -80,10 +51,6 @@ export function useSelectedAgentFromBulkSettings({ integrations: CodingAgentIntegration[]; }) { return useMemo(() => { - // If we have autofixAutomationTuning==OFF then 'none' is picked - if (autofixSettings.autofixAutomationTuning === 'off') { - return 'none'; - } // If we have nothing in preferences, then we have Seer if (!autofixSettings?.automationHandoff?.integration_id) { return 'seer'; @@ -93,11 +60,7 @@ export function useSelectedAgentFromBulkSettings({ integration => integration.id === String(autofixSettings.automationHandoff?.integration_id) ); - }, [ - autofixSettings.automationHandoff?.integration_id, - autofixSettings.autofixAutomationTuning, - integrations, - ]); + }, [autofixSettings.automationHandoff?.integration_id, integrations]); } function useApplyOptimisticUpdate({project}: {project: Project}) { diff --git a/static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx b/static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx index aae2ff52378e62..958b32849a1050 100644 --- a/static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx +++ b/static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx @@ -20,13 +20,13 @@ import {t, tct} from 'sentry/locale'; import type {Project} from 'sentry/types/project'; import {useMutation, useQuery, useQueryClient} from 'sentry/utils/queryClient'; import {useOrganization} from 'sentry/utils/useOrganization'; +import {useFetchAgentOptions} from 'sentry/views/settings/seer/overview/utils/seerPreferredAgent'; import { getProjectStoppingPointMutationOptions, getProjectStoppingPointValue, useFetchStoppingPointOptions, } from 'sentry/views/settings/seer/overview/utils/seerStoppingPoint'; import { - useAgentOptions, useMutateSelectedAgent, useSelectedAgentFromProjectSettings, } from 'sentry/views/settings/seer/seerAgentHooks'; @@ -44,14 +44,11 @@ function AgentSpecificFields({ integration, ...props }: Props & { - integration: 'seer' | 'none' | CodingAgentIntegration; + integration: 'seer' | CodingAgentIntegration; }) { if (integration === 'seer') { return ; } - if (integration === 'none') { - return null; - } if (integration.provider === 'cursor' || integration.provider === 'claude_code') { return ; } @@ -68,10 +65,9 @@ export function AutofixAgent({canWrite, preference, project}: Props) { ...organizationIntegrationsCodingAgents(organization), select: data => data.json.integrations ?? [], }); - const options = useAgentOptions({integrations: integrations ?? []}); + const options = useFetchAgentOptions({organization}); const selected = useSelectedAgentFromProjectSettings({ preference, - project, integrations: integrations ?? [], }); const mutateSelectedAgent = useMutateSelectedAgent({project}); @@ -106,37 +102,29 @@ export function AutofixAgent({canWrite, preference, project}: Props) { ), } )} - options={options} + options={options.data ?? []} value={selected} - onChange={(integration: 'seer' | 'none' | CodingAgentIntegration) => { + onChange={(integration: 'seer' | CodingAgentIntegration) => { mutateSelectedAgent(integration, { onSuccess: () => addSuccessMessage( - integration === 'none' - ? t('Removed coding agent') - : tct('Started using [name] as coding agent', { - name: ( - - {integration === 'seer' - ? t('Seer Agent') - : integration.name} - - ), - }) + tct('Started using [name] as coding agent', { + name: ( + + {integration === 'seer' ? t('Seer Agent') : integration.name} + + ), + }) ), onError: () => addErrorMessage( - integration === 'none' - ? t('Failed to update coding agent') - : tct('Failed to set [name] as coding agent', { - name: ( - - {integration === 'seer' - ? t('Seer Agent') - : integration.name} - - ), - }) + tct('Failed to set [name] as coding agent', { + name: ( + + {integration === 'seer' ? t('Seer Agent') : integration.name} + + ), + }) ), }); }} @@ -150,7 +138,7 @@ export function AutofixAgent({canWrite, preference, project}: Props) { /> ) : null} - {selected && selected !== 'none' ? ( + {selected ? ( { const lowerCase = searchTerm?.toLowerCase() ?? ''; @@ -216,6 +224,7 @@ export function SeerProjectTable() { integrations={integrations ?? []} isPendingIntegrations={isPendingIntegrations} project={project} + agentOptions={agentOptions} /> )) )} diff --git a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx index b6db2925f8f820..8887ec8e89faa6 100644 --- a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx +++ b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableHeader.tsx @@ -34,7 +34,7 @@ interface Props { const COLUMNS = [ {title: t('Project'), key: 'project', sortKey: 'project'}, - {title: t('Autofix Handoff'), key: 'fixes'}, + {title: t('Agent'), key: 'fixes', sortKey: 'agent'}, { title: ( @@ -48,7 +48,7 @@ const COLUMNS = [ ), key: 'pr_creation', }, - {title: t('Repos'), key: 'repos'}, + {title: t('Repos'), key: 'repos', sortKey: 'repo_count'}, ]; function getMutationCallbacks(count: number) { diff --git a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx index 73c0b6404fd40c..47aca85c1a3b1b 100644 --- a/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx +++ b/static/gsApp/views/seerAutomation/components/projectTable/seerProjectTableRow.tsx @@ -20,8 +20,8 @@ import {t, tct} from 'sentry/locale'; import type {Project} from 'sentry/types/project'; import {useListItemCheckboxContext} from 'sentry/utils/list/useListItemCheckboxState'; import {useOrganization} from 'sentry/utils/useOrganization'; +import type {useFetchAgentOptions} from 'sentry/views/settings/seer/overview/utils/seerPreferredAgent'; import { - useAgentOptions, useMutateCreatePr, useMutateSelectedAgent, useSelectedAgentFromBulkSettings, @@ -30,6 +30,7 @@ import { import {useCanWriteSettings} from 'getsentry/views/seerAutomation/components/useCanWriteSettings'; interface Props { + agentOptions: ReturnType; autofixSettings: undefined | AutofixAutomationSettings; integrations: CodingAgentIntegration[]; isPendingIntegrations: boolean; @@ -37,16 +38,16 @@ interface Props { } export function SeerProjectTableRow({ + agentOptions, autofixSettings, + integrations, isPendingIntegrations, project, - integrations, }: Props) { const organization = useOrganization(); const canWrite = useCanWriteSettings(); const {isSelected, toggleSelected} = useListItemCheckboxContext(); - const options = useAgentOptions({integrations: integrations ?? []}); const autofixAgent = useSelectedAgentFromBulkSettings({ autofixSettings: autofixSettings ?? { autofixAutomationTuning: 'off', @@ -94,28 +95,24 @@ export function SeerProjectTableRow({ size="xs" disabled={!canWrite} name="autofixAgent" - options={options} + options={agentOptions.data ?? []} value={autofixAgent} - onChange={(option: ReturnType[number]) => { + onChange={option => { mutateSelectedAgent(option.value, { onSuccess: () => { addSuccessMessage( - option.value === 'none' - ? t('Removed autofix agent from %s', project.name) - : tct('Started using [name] for [project]', { - name: {option.label}, - project: project.name, - }) + tct('Selected [name] for [project]', { + name: {option.label}, + project: project.name, + }) ); }, onError: () => addErrorMessage( - option.value === 'none' - ? t('Failed to update autofix agent') - : tct('Failed to set [name] for [project]', { - name: {option.label}, - project: project.name, - }) + tct('Failed to set [name] for [project]', { + name: {option.label}, + project: project.name, + }) ), }); }} @@ -125,53 +122,49 @@ export function SeerProjectTableRow({ {autofixSettings ? ( - autofixAgent === 'none' ? ( - {'\u2014'} - ) : ( - - {organization.enableSeerCoding === false && autofixAgent === 'seer' ? ( - - ), - } - )} - size="xs" - /> - ) : null} - - { - const value = e.target.checked; - mutateCreatePr(autofixAgent, value, { - onSuccess: () => - addSuccessMessage( - value - ? t('Enabled auto create pull requests for %s', project.name) - : t('Disabled auto create pull requests for %s', project.name) - ), - onError: () => - addErrorMessage( - t( - 'Failed to update auto create pull requests setting for %s', - project.name - ) - ), - }); - }} + + {organization.enableSeerCoding === false && autofixAgent === 'seer' ? ( + + ), + } + )} + size="xs" /> - - ) + ) : null} + + { + const value = e.target.checked; + mutateCreatePr(autofixAgent, value, { + onSuccess: () => + addSuccessMessage( + value + ? t('Enabled auto create pull requests for %s', project.name) + : t('Disabled auto create pull requests for %s', project.name) + ), + onError: () => + addErrorMessage( + t( + 'Failed to update auto create pull requests setting for %s', + project.name + ) + ), + }); + }} + /> + ) : ( )}