Skip to content

Commit ea0c749

Browse files
authored
feat(seer): Implement the dropdown to save defaultAutomatedRunStoppingPoint (#112211)
Just the basics as a dropdown. Following this will be the summary blurb and bulk-edit button <img width="942" height="205" alt="SCR-20260403-jyzo" src="https://github.com/user-attachments/assets/b1a9af15-0301-42fb-8bf6-1e01de39434c" />
1 parent 05bf2cb commit ea0c749

6 files changed

Lines changed: 171 additions & 7 deletions

File tree

static/app/types/organization.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type {AutofixStoppingPoint} from 'sentry/components/events/autofix/types';
12
import type {AggregationOutputType} from 'sentry/utils/discover/fields';
23
import type {
34
DatasetSource,
@@ -64,6 +65,7 @@ export interface Organization extends OrganizationSummary {
6465
dataScrubber: boolean;
6566
dataScrubberDefaults: boolean;
6667
debugFilesRole: string;
68+
defaultAutomatedRunStoppingPoint: AutofixStoppingPoint;
6769
defaultCodeReviewTriggers: CodeReviewTrigger[];
6870
defaultCodingAgent: string | null;
6971
defaultCodingAgentIntegrationId: string | number | null;

static/app/views/settings/seer/overview/autofixOverviewSection.tsx

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@ import {useProjects} from 'sentry/utils/useProjects';
2929
import {
3030
getPreferredAgentMutationOptions,
3131
useFetchPreferredAgent,
32-
useFetchPreferredAgentOptions,
32+
useFetchAgentOptions,
3333
useBulkMutateSelectedAgent,
3434
} from 'sentry/views/settings/seer/overview/utils/seerPreferredAgent';
3535
import {useBulkMutateCreatePr} from 'sentry/views/settings/seer/seerAgentHooks';
3636

37+
import {
38+
getDefaultStoppingPointMutationOptions,
39+
getDefaultStoppingPointValue,
40+
useFetchStoppingPointOptions,
41+
} from './utils/seerStoppingPoint';
42+
3743
export function useAutofixOverviewData() {
3844
const organization = useOrganization();
3945

@@ -127,6 +133,8 @@ export function AutofixOverviewSection({canWrite, data, isPending, organization}
127133
projects={projects}
128134
projectsWithCreatePr={projectsWithCreatePr}
129135
/>
136+
137+
<StoppingPointForm organization={organization} canWrite={canWrite} />
130138
</FieldGroup>
131139
);
132140
}
@@ -151,7 +159,7 @@ function AgentNameForm({
151159
setIsBulkMutatingAgent: (value: boolean) => void;
152160
}) {
153161
const preferredAgent = useFetchPreferredAgent({organization});
154-
const codingAgentSelectOptions = useFetchPreferredAgentOptions({organization});
162+
const codingAgentSelectOptions = useFetchAgentOptions({organization});
155163
const codingAgentMutationOptions = getPreferredAgentMutationOptions({organization});
156164
const bulkMutateSelectedAgent = useBulkMutateSelectedAgent({
157165
projects: projects.filter(p => !projectsIdsWithPreferredAgent.has(p.id)),
@@ -392,3 +400,58 @@ function CreatePrForm({
392400
</AutoSaveForm>
393401
);
394402
}
403+
404+
function StoppingPointForm({
405+
organization,
406+
canWrite,
407+
}: {
408+
canWrite: boolean;
409+
organization: Organization;
410+
}) {
411+
const stoppingPointMutationOpts = getDefaultStoppingPointMutationOptions({
412+
organization,
413+
});
414+
415+
const initialValue = getDefaultStoppingPointValue(organization);
416+
const preferredAgent = useFetchPreferredAgent({organization});
417+
const options = useFetchStoppingPointOptions({
418+
agent: preferredAgent.data,
419+
organization,
420+
});
421+
422+
return (
423+
<AutoSaveForm
424+
name="stoppingPoint"
425+
schema={z.object({
426+
stoppingPoint: z.enum(['off', 'root_cause', 'code']),
427+
})}
428+
initialValue={initialValue}
429+
mutationOptions={stoppingPointMutationOpts}
430+
>
431+
{field => (
432+
<Stack gap="md">
433+
<field.Layout.Row
434+
label={t('Default Automation Steps')}
435+
hintText={tct(
436+
'For new projects, pick which steps Seer should run as new issues are collected. Depending on how [actionable:actionable] the issue is, Seer may stop at an earlier step.',
437+
{
438+
actionable: (
439+
<ExternalLink href="https://docs.sentry.io/product/ai-in-sentry/seer/autofix/#how-issue-autofix-works" />
440+
),
441+
}
442+
)}
443+
>
444+
<Container flexGrow={1}>
445+
<field.Select
446+
disabled={!canWrite}
447+
value={field.state.value}
448+
onChange={field.handleChange}
449+
options={options}
450+
/>
451+
</Container>
452+
</field.Layout.Row>
453+
</Stack>
454+
)}
455+
</AutoSaveForm>
456+
);
457+
}

static/app/views/settings/seer/overview/utils/seerPreferredAgent.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {useMutation} from 'sentry/utils/queryClient';
1212
import {
1313
useBulkMutateSelectedAgent,
1414
useFetchPreferredAgent,
15-
useFetchPreferredAgentOptions,
15+
useFetchAgentOptions,
1616
getPreferredAgentMutationOptions,
1717
} from 'sentry/views/settings/seer/overview/utils/seerPreferredAgent';
1818

@@ -133,7 +133,7 @@ describe('seerPreferredAgent', () => {
133133
it('includes "seer" as first option plus integration options', async () => {
134134
mockIntegrationsEndpoint();
135135

136-
const {result} = renderHookWithProviders(useFetchPreferredAgentOptions, {
136+
const {result} = renderHookWithProviders(useFetchAgentOptions, {
137137
initialProps: {organization},
138138
organization,
139139
});
@@ -160,7 +160,7 @@ describe('seerPreferredAgent', () => {
160160
],
161161
});
162162

163-
const {result} = renderHookWithProviders(useFetchPreferredAgentOptions, {
163+
const {result} = renderHookWithProviders(useFetchAgentOptions, {
164164
initialProps: {organization},
165165
organization,
166166
});
@@ -177,7 +177,7 @@ describe('seerPreferredAgent', () => {
177177
it('returns only "seer" when there are no integrations', async () => {
178178
mockIntegrationsEndpoint({integrations: []});
179179

180-
const {result} = renderHookWithProviders(useFetchPreferredAgentOptions, {
180+
const {result} = renderHookWithProviders(useFetchAgentOptions, {
181181
initialProps: {organization},
182182
organization,
183183
});

static/app/views/settings/seer/overview/utils/seerPreferredAgent.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,16 @@ export function useFetchPreferredAgent({organization}: {organization: Organizati
5151
return query;
5252
}
5353

54-
export function useFetchPreferredAgentOptions({
54+
export function useFetchAgentOptions({
5555
organization,
56+
enabled = true,
5657
}: {
5758
organization: Organization;
59+
enabled?: boolean;
5860
}) {
5961
return useQuery({
6062
...organizationIntegrationsCodingAgents(organization),
63+
enabled,
6164
select: data => {
6265
return [
6366
{value: 'seer', label: t('Seer Agent')} as SelectValue<PreferredAgent>,
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import {updateOrganization} from 'sentry/actionCreators/organizations';
2+
import type {CodingAgentIntegration} from 'sentry/components/events/autofix/useAutofix';
3+
import {t} from 'sentry/locale';
4+
import type {Organization} from 'sentry/types/organization';
5+
import {fetchMutation, mutationOptions} from 'sentry/utils/queryClient';
6+
import {useFetchAgentOptions} from 'sentry/views/settings/seer/overview/utils/seerPreferredAgent';
7+
8+
type SelectValue = 'off' | 'root_cause' | 'code';
9+
type SelectOptions = {label: string; value: SelectValue};
10+
11+
export function getDefaultStoppingPointValue(organization: Organization): SelectValue {
12+
if ([null, undefined, 'off'].includes(organization.defaultAutofixAutomationTuning)) {
13+
return 'off';
14+
}
15+
return organization.defaultAutomatedRunStoppingPoint === 'root_cause'
16+
? 'root_cause'
17+
: 'code';
18+
}
19+
20+
export function useFetchStoppingPointOptions({
21+
organization,
22+
agent,
23+
}: {
24+
agent: undefined | 'seer' | CodingAgentIntegration;
25+
organization: Organization;
26+
}): SelectOptions[] {
27+
const autoOpenPrs = organization.autoOpenPrs;
28+
29+
const isSeerAgent = agent === 'seer';
30+
const codingAgentSelectOptions = useFetchAgentOptions({
31+
organization,
32+
enabled: !isSeerAgent,
33+
});
34+
35+
if (isSeerAgent) {
36+
return [
37+
{value: 'off', label: t('No Automation')},
38+
{value: 'root_cause', label: t('Automate Root Cause Analysis')},
39+
{
40+
value: 'code',
41+
label: autoOpenPrs
42+
? t('Draft a Pull Request with Seer')
43+
: t('Write Code Changes with Seer'),
44+
},
45+
];
46+
}
47+
48+
const agentLabel = codingAgentSelectOptions.data?.find(
49+
o => o.value === agent || (typeof o.value === 'object' && o.value.id === agent?.id)
50+
)?.label;
51+
52+
return [
53+
{value: 'off', label: t('No Automation')},
54+
{value: 'root_cause', label: t('Automate Root Cause Analysis')},
55+
{
56+
value: 'code',
57+
label: autoOpenPrs
58+
? agentLabel
59+
? t('Draft a Pull Request with %s', agentLabel)
60+
: t('Draft a Pull Request')
61+
: agentLabel
62+
? t('Propose Changes with %s', agentLabel)
63+
: t('Propose Changes'),
64+
},
65+
];
66+
}
67+
68+
export function getDefaultStoppingPointMutationOptions({
69+
organization,
70+
}: {
71+
organization: Organization;
72+
}) {
73+
return mutationOptions({
74+
mutationFn: ({stoppingPoint}: {stoppingPoint: SelectValue}) => {
75+
return fetchMutation<Organization>({
76+
method: 'PUT',
77+
url: `/organizations/${organization.slug}/`,
78+
data:
79+
stoppingPoint === 'off'
80+
? {defaultAutofixAutomationTuning: 'off'}
81+
: {
82+
defaultAutofixAutomationTuning: 'medium',
83+
defaultAutomatedRunStoppingPoint:
84+
stoppingPoint === 'root_cause'
85+
? 'root_cause'
86+
: organization.autoOpenPrs
87+
? 'open_pr'
88+
: 'code_changes',
89+
},
90+
});
91+
},
92+
onSuccess: updateOrganization,
93+
});
94+
}

tests/js/fixtures/organization.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {OrgRoleListFixture, TeamRoleListFixture} from 'sentry-fixture/roleList';
22

3+
import {AutofixStoppingPoint} from 'sentry/components/events/autofix/types';
34
import type {Organization} from 'sentry/types/organization';
45

56
export function OrganizationFixture(params: Partial<Organization> = {}): Organization {
@@ -54,6 +55,7 @@ export function OrganizationFixture(params: Partial<Organization> = {}): Organiz
5455
dataScrubberDefaults: false,
5556
dateCreated: new Date().toISOString(),
5657
debugFilesRole: '',
58+
defaultAutomatedRunStoppingPoint: AutofixStoppingPoint.ROOT_CAUSE,
5759
defaultCodeReviewTriggers: [],
5860
defaultCodingAgentIntegrationId: null,
5961
defaultCodingAgent: 'seer',

0 commit comments

Comments
 (0)