Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 15 additions & 51 deletions static/app/views/settings/seer/seerAgentHooks.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
});

Comment thread
cursor[bot] marked this conversation as resolved.
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,
Expand All @@ -117,7 +82,6 @@ describe('seerAgentHooks', () => {
integration_id: 99,
},
},
project,
integrations,
},
organization,
Expand All @@ -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: {
Expand All @@ -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', () => {
Expand Down
41 changes: 2 additions & 39 deletions static/app/views/settings/seer/seerAgentHooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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({
Expand All @@ -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';
Expand All @@ -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}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -44,14 +44,11 @@ function AgentSpecificFields({
integration,
...props
}: Props & {
integration: 'seer' | 'none' | CodingAgentIntegration;
integration: 'seer' | CodingAgentIntegration;
}) {
if (integration === 'seer') {
return <SeerAgentSettings {...props} />;
}
if (integration === 'none') {
return null;
}
if (integration.provider === 'cursor' || integration.provider === 'claude_code') {
return <CodingAgentSettings integration={integration} {...props} />;
}
Expand All @@ -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});
Expand Down Expand Up @@ -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: (
<strong>
{integration === 'seer'
? t('Seer Agent')
: integration.name}
</strong>
),
})
tct('Started using [name] as coding agent', {
name: (
<strong>
{integration === 'seer' ? t('Seer Agent') : integration.name}
</strong>
),
})
),
onError: () =>
addErrorMessage(
integration === 'none'
? t('Failed to update coding agent')
: tct('Failed to set [name] as coding agent', {
name: (
<strong>
{integration === 'seer'
? t('Seer Agent')
: integration.name}
</strong>
),
})
tct('Failed to set [name] as coding agent', {
name: (
<strong>
{integration === 'seer' ? t('Seer Agent') : integration.name}
</strong>
),
})
),
});
}}
Expand All @@ -150,7 +138,7 @@ export function AutofixAgent({canWrite, preference, project}: Props) {
/>
) : null}

{selected && selected !== 'none' ? (
{selected ? (
<StoppingPointField
agent={selected}
canWrite={canWrite}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {ApiQueryKey} from 'sentry/utils/queryClient';
import {parseAsSort} from 'sentry/utils/url/parseAsSort';
import {useOrganization} from 'sentry/utils/useOrganization';
import {useProjects} from 'sentry/utils/useProjects';
import {useFetchAgentOptions} from 'sentry/views/settings/seer/overview/utils/seerPreferredAgent';

import {ProjectTableHeader} from 'getsentry/views/seerAutomation/components/projectTable/seerProjectTableHeader';
import {SeerProjectTableRow} from 'getsentry/views/seerAutomation/components/projectTable/seerProjectTableRow';
Expand All @@ -34,6 +35,8 @@ export function SeerProjectTable() {
const organization = useOrganization();
const {projects, fetching, fetchError} = useProjects();

const agentOptions = useFetchAgentOptions({organization});

const autofixSettingsQueryOptions = bulkAutofixAutomationSettingsInfiniteOptions({
organization,
});
Expand Down Expand Up @@ -128,19 +131,24 @@ export function SeerProjectTable() {
: b.name.localeCompare(a.name);
}

// TODO: if we can bulk-fetch all the preferences, then it'll be easier to sort by fixes, pr creation, and repos
// if (sort.field === 'fixes') {
// return a.slug.localeCompare(b.slug);
// }
// if (sort.field === 'pr_creation') {
// return a.platform.localeCompare(b.platform);
// }
// if (sort.field === 'repos') {
// return a.status.localeCompare(b.status);
// }
const aSettings = autofixSettingsByProjectId.get(a.id);
const bSettings = autofixSettingsByProjectId.get(b.id);
if (sort.field === 'agent') {
const aAgent = aSettings?.automationHandoff?.target ?? 'seer';
const bAgent = bSettings?.automationHandoff?.target ?? 'seer';
return sort.kind === 'asc'
? aAgent.localeCompare(bAgent)
: bAgent.localeCompare(aAgent);
}

if (sort.field === 'repo_count') {
return sort.kind === 'asc'
? (aSettings?.reposCount ?? 0) - (bSettings?.reposCount ?? 0)
: (bSettings?.reposCount ?? 0) - (aSettings?.reposCount ?? 0);
}
return 0;
});
}, [projects, sort]);
}, [projects, sort, autofixSettingsByProjectId]);

const filteredProjects = useMemo(() => {
const lowerCase = searchTerm?.toLowerCase() ?? '';
Expand Down Expand Up @@ -216,6 +224,7 @@ export function SeerProjectTable() {
integrations={integrations ?? []}
isPendingIntegrations={isPendingIntegrations}
project={project}
agentOptions={agentOptions}
/>
))
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: (
<Flex gap="sm" align="center">
Expand All @@ -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) {
Expand Down
Loading
Loading