Skip to content

Commit 1ac5e9a

Browse files
ryan953claude
andauthored
feat(seer): Add the stopping-point field to project-specific seer settings (#112232)
This brings in the stopping-point dropdown to the project-specific seer settings page Follows: #112211 There's still more to cleanup in here, but we're well on the way. <img width="960" height="383" alt="SCR-20260403-msjw" src="https://github.com/user-attachments/assets/09b86dd8-6a0e-4bf5-89d9-9cd9d9649f6d" /> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b7608da commit 1ac5e9a

File tree

4 files changed

+207
-8
lines changed

4 files changed

+207
-8
lines changed

static/app/components/events/autofix/preferences/hooks/useBulkAutofixAutomationSettings.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,13 +124,14 @@ export function useUpdateBulkAutofixAutomationSettings(
124124
},
125125
...options,
126126
onSettled: (...args) => {
127+
const bulkAutofixAutomationSettingsQueryOptions =
128+
bulkAutofixAutomationSettingsInfiniteOptions({
129+
organization,
130+
});
127131
queryClient.invalidateQueries({
128-
queryKey: [
129-
getApiUrl(`/organizations/$organizationIdOrSlug/autofix/automation-settings/`, {
130-
path: {organizationIdOrSlug: organization.slug},
131-
}),
132-
],
132+
queryKey: bulkAutofixAutomationSettingsQueryOptions.queryKey,
133133
});
134+
134135
const [, , data] = args;
135136
data.projectIds.forEach(projectId => {
136137
const project = projectsById.get(projectId);

static/app/components/events/autofix/preferences/hooks/useUpdateProjectSeerPreferences.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
} from 'sentry/utils/queryClient';
1717
import {useOrganization} from 'sentry/utils/useOrganization';
1818

19+
import {bulkAutofixAutomationSettingsInfiniteOptions} from './useBulkAutofixAutomationSettings';
20+
1921
type Context =
2022
| {
2123
previousPrefs: SeerPreferencesResponse;
@@ -86,8 +88,13 @@ export function useUpdateProjectSeerPreferences(project: Project) {
8688
},
8789
onSettled: () => {
8890
queryClient.invalidateQueries({queryKey});
91+
92+
const bulkAutofixAutomationSettingsQueryOptions =
93+
bulkAutofixAutomationSettingsInfiniteOptions({
94+
organization,
95+
});
8996
queryClient.invalidateQueries({
90-
queryKey: [`/organizations/${organization.slug}/autofix/automation-settings/`],
97+
queryKey: bulkAutofixAutomationSettingsQueryOptions.queryKey,
9198
});
9299
},
93100
});

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

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
1+
import type {QueryClient} from '@tanstack/react-query';
2+
13
import {updateOrganization} from 'sentry/actionCreators/organizations';
4+
import {bulkAutofixAutomationSettingsInfiniteOptions} from 'sentry/components/events/autofix/preferences/hooks/useBulkAutofixAutomationSettings';
5+
import {
6+
makeProjectSeerPreferencesQueryKey,
7+
type SeerPreferencesResponse,
8+
} from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences';
9+
import type {ProjectSeerPreferences} from 'sentry/components/events/autofix/types';
210
import type {CodingAgentIntegration} from 'sentry/components/events/autofix/useAutofix';
311
import {t} from 'sentry/locale';
12+
import {ProjectsStore} from 'sentry/stores/projectsStore';
413
import type {Organization} from 'sentry/types/organization';
5-
import {fetchMutation, mutationOptions} from 'sentry/utils/queryClient';
14+
import type {Project} from 'sentry/types/project';
15+
import {
16+
fetchMutation,
17+
getApiQueryData,
18+
mutationOptions,
19+
setApiQueryData,
20+
} from 'sentry/utils/queryClient';
621
import {useFetchAgentOptions} from 'sentry/views/settings/seer/overview/utils/seerPreferredAgent';
722

823
type SelectValue = 'off' | 'root_cause' | 'code';
@@ -17,6 +32,16 @@ export function getDefaultStoppingPointValue(organization: Organization): Select
1732
: 'code';
1833
}
1934

35+
export function getProjectStoppingPointValue(
36+
project: Project,
37+
preference: ProjectSeerPreferences
38+
): SelectValue {
39+
if ([null, undefined, 'off'].includes(project.autofixAutomationTuning)) {
40+
return 'off';
41+
}
42+
return preference.automated_run_stopping_point === 'root_cause' ? 'root_cause' : 'code';
43+
}
44+
2045
export function useFetchStoppingPointOptions({
2146
organization,
2247
agent,
@@ -92,3 +117,96 @@ export function getDefaultStoppingPointMutationOptions({
92117
onSuccess: updateOrganization,
93118
});
94119
}
120+
121+
export function getProjectStoppingPointMutationOptions({
122+
organization,
123+
preference,
124+
project,
125+
queryClient,
126+
}: {
127+
organization: Organization;
128+
preference: ProjectSeerPreferences;
129+
project: Project;
130+
queryClient: QueryClient;
131+
}) {
132+
const seerPrefsQueryKey = makeProjectSeerPreferencesQueryKey(
133+
organization.slug,
134+
project.slug
135+
);
136+
137+
return mutationOptions({
138+
mutationFn: async ({stoppingPoint}: {stoppingPoint: SelectValue}) => {
139+
const tuning = stoppingPoint === 'off' ? ('off' as const) : ('medium' as const);
140+
141+
const projectPromise = fetchMutation<Project>({
142+
method: 'PUT',
143+
url: `/projects/${organization.slug}/${project.slug}/`,
144+
data: {autofixAutomationTuning: tuning},
145+
});
146+
147+
let preferencePromise: Promise<SeerPreferencesResponse | undefined> =
148+
Promise.resolve(undefined);
149+
if (stoppingPoint !== 'off') {
150+
const repositories = preference?.repositories ?? [];
151+
const automationHandoff = preference?.automation_handoff;
152+
153+
const stoppingPointValue =
154+
stoppingPoint === 'root_cause'
155+
? ('root_cause' as const)
156+
: organization.autoOpenPrs
157+
? ('open_pr' as const)
158+
: ('code_changes' as const);
159+
160+
const preferencePayload = {
161+
repositories,
162+
automated_run_stopping_point: stoppingPointValue,
163+
automation_handoff: automationHandoff
164+
? {
165+
...automationHandoff,
166+
auto_create_pr: stoppingPointValue === 'open_pr',
167+
}
168+
: automationHandoff,
169+
};
170+
171+
preferencePromise = fetchMutation<SeerPreferencesResponse>({
172+
method: 'POST',
173+
url: `/projects/${organization.slug}/${project.slug}/seer/preferences/`,
174+
data: preferencePayload as unknown as Record<string, unknown>,
175+
});
176+
}
177+
178+
return await Promise.all([projectPromise, preferencePromise]);
179+
},
180+
onSuccess: ([updatedProject, preferencePayload]) => {
181+
ProjectsStore.onUpdateSuccess(updatedProject);
182+
183+
if (preferencePayload) {
184+
const previous = getApiQueryData<SeerPreferencesResponse>(
185+
queryClient,
186+
seerPrefsQueryKey
187+
);
188+
if (previous) {
189+
setApiQueryData<SeerPreferencesResponse>(queryClient, seerPrefsQueryKey, {
190+
...previous,
191+
preference: {
192+
repositories: [],
193+
...previous.preference,
194+
...preferencePayload.preference,
195+
},
196+
});
197+
}
198+
}
199+
},
200+
onSettled: () => {
201+
queryClient.invalidateQueries({queryKey: seerPrefsQueryKey});
202+
203+
const bulkAutofixAutomationSettingsQueryOptions =
204+
bulkAutofixAutomationSettingsInfiniteOptions({
205+
organization,
206+
});
207+
queryClient.invalidateQueries({
208+
queryKey: bulkAutofixAutomationSettingsQueryOptions.queryKey,
209+
});
210+
},
211+
});
212+
}

static/gsApp/views/seerAutomation/components/projectDetails/autofixAgent.tsx

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@ import {PanelHeader} from 'sentry/components/panels/panelHeader';
1818
import {Placeholder} from 'sentry/components/placeholder';
1919
import {t, tct} from 'sentry/locale';
2020
import type {Project} from 'sentry/types/project';
21-
import {useQuery} from 'sentry/utils/queryClient';
21+
import {useMutation, useQuery, useQueryClient} from 'sentry/utils/queryClient';
2222
import {useOrganization} from 'sentry/utils/useOrganization';
23+
import {
24+
getProjectStoppingPointMutationOptions,
25+
getProjectStoppingPointValue,
26+
useFetchStoppingPointOptions,
27+
} from 'sentry/views/settings/seer/overview/utils/seerStoppingPoint';
2328
import {
2429
useAgentOptions,
2530
useMutateSelectedAgent,
@@ -144,13 +149,81 @@ export function AutofixAgent({canWrite, preference, project}: Props) {
144149
project={project}
145150
/>
146151
) : null}
152+
153+
{selected && selected !== 'none' ? (
154+
<StoppingPointField
155+
agent={selected}
156+
canWrite={canWrite}
157+
preference={preference}
158+
project={project}
159+
/>
160+
) : null}
147161
</Fragment>
148162
)}
149163
</PanelBody>
150164
</PanelNoMargin>
151165
);
152166
}
153167

168+
function StoppingPointField({
169+
agent,
170+
canWrite,
171+
preference,
172+
project,
173+
}: {
174+
agent: 'seer' | CodingAgentIntegration;
175+
canWrite: boolean;
176+
preference: ProjectSeerPreferences;
177+
project: Project;
178+
}) {
179+
const organization = useOrganization();
180+
const queryClient = useQueryClient();
181+
182+
const stoppingPointMutationOpts = getProjectStoppingPointMutationOptions({
183+
organization,
184+
project,
185+
preference,
186+
queryClient,
187+
});
188+
const {mutate} = useMutation({
189+
...stoppingPointMutationOpts,
190+
onSuccess: (data, variables, onMutateResult, context) => {
191+
stoppingPointMutationOpts.onSuccess?.(data, variables, onMutateResult, context);
192+
addSuccessMessage(t('Stopping point updated'));
193+
},
194+
onError: () => {
195+
addErrorMessage(t('Failed to update stopping point'));
196+
},
197+
});
198+
199+
const initialValue = getProjectStoppingPointValue(project, preference);
200+
const options = useFetchStoppingPointOptions({
201+
agent,
202+
organization,
203+
});
204+
205+
return (
206+
<SelectField
207+
name="stoppingPoint"
208+
disabled={!canWrite}
209+
value={initialValue}
210+
onChange={value => {
211+
mutate({stoppingPoint: value});
212+
}}
213+
options={options}
214+
label={t('Automation Steps')}
215+
help={tct(
216+
'Choose which steps Seer should run automatically on issues. Depending on how [actionable:actionable] the issue is, Seer may stop at an earlier step.',
217+
{
218+
actionable: (
219+
<ExternalLink href="https://docs.sentry.io/product/ai-in-sentry/seer/autofix/#how-issue-autofix-works" />
220+
),
221+
}
222+
)}
223+
/>
224+
);
225+
}
226+
154227
const PanelNoMargin = styled(Panel)`
155228
margin-bottom: 0;
156229
`;

0 commit comments

Comments
 (0)