Skip to content

Commit e2ecd21

Browse files
committed
fix(seer): Convert Seer Repo Details form to new form system, clear onboarding-check cache
1 parent b62f69f commit e2ecd21

File tree

2 files changed

+144
-58
lines changed

2 files changed

+144
-58
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type {Organization} from 'sentry/types/organization';
2+
import {apiOptions} from 'sentry/utils/api/apiOptions';
3+
4+
interface SeerOnboardingCheckResponse {
5+
hasSupportedScmIntegration: boolean;
6+
isAutofixEnabled: boolean;
7+
isCodeReviewEnabled: boolean;
8+
isSeerConfigured: boolean;
9+
needsConfigReminder: boolean;
10+
}
11+
12+
interface Props {
13+
organization: Organization;
14+
staleTime?: number;
15+
}
16+
17+
export function getSeerOnboardingCheckQueryOptions({organization, staleTime = 0}: Props) {
18+
return apiOptions.as<SeerOnboardingCheckResponse>()(
19+
'/organizations/$organizationIdOrSlug/seer/onboarding-check/',
20+
{
21+
path: {
22+
organizationIdOrSlug: organization.slug,
23+
},
24+
staleTime,
25+
}
26+
);
27+
}
Lines changed: 117 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,82 @@
1+
import {mutationOptions} from '@tanstack/react-query';
2+
import {z} from 'zod';
3+
14
import {Alert} from '@sentry/scraps/alert';
2-
import {Stack} from '@sentry/scraps/layout';
5+
import {AutoSaveForm, FieldGroup} from '@sentry/scraps/form';
6+
import {Container, Stack} from '@sentry/scraps/layout';
37

4-
import {Form} from 'sentry/components/forms/form';
5-
import JsonForm from 'sentry/components/forms/jsonForm';
6-
import {type RepositorySettings} from 'sentry/components/repositories/useBulkUpdateRepositorySettings';
8+
import {getRepositoryWithSettingsQueryKey} from 'sentry/components/repositories/useRepositoryWithSettings';
79
import {t, tct} from 'sentry/locale';
8-
import {type RepositoryWithSettings} from 'sentry/types/integrations';
10+
import type {RepositoryWithSettings} from 'sentry/types/integrations';
911
import type {Organization} from 'sentry/types/organization';
12+
import {getSeerOnboardingCheckQueryOptions} from 'sentry/utils/getSeerOnboardingCheckQueryOptions';
13+
import {fetchMutation, useQueryClient} from 'sentry/utils/queryClient';
1014

1115
import {useCanWriteSettings} from 'getsentry/views/seerAutomation/components/useCanWriteSettings';
1216

17+
const schema = z.object({
18+
enabledCodeReview: z.boolean(),
19+
codeReviewTriggers: z.array(z.string()),
20+
});
21+
1322
interface Props {
1423
organization: Organization;
1524
repoWithSettings: RepositoryWithSettings;
1625
}
1726

1827
export function RepoDetailsForm({organization, repoWithSettings}: Props) {
1928
const canWrite = useCanWriteSettings();
29+
const queryClient = useQueryClient();
30+
31+
const repoQueryKey = getRepositoryWithSettingsQueryKey(
32+
organization,
33+
repoWithSettings.id
34+
);
35+
36+
const repoMutationOpts = mutationOptions({
37+
mutationFn: (data: {codeReviewTriggers?: string[]; enabledCodeReview?: boolean}) => {
38+
return fetchMutation<RepositoryWithSettings[]>({
39+
method: 'PUT',
40+
url: `/organizations/${organization.slug}/repos/settings/`,
41+
data: {...data, repositoryIds: [repoWithSettings.id]},
42+
});
43+
},
44+
onMutate: (data: {codeReviewTriggers?: string[]; enabledCodeReview?: boolean}) => {
45+
const previous =
46+
queryClient.getQueryData<
47+
[RepositoryWithSettings, string | undefined, Response | undefined]
48+
>(repoQueryKey);
49+
if (previous) {
50+
const [repo, statusText, resp] = previous;
51+
queryClient.setQueryData(repoQueryKey, [
52+
{
53+
...repo,
54+
settings: {
55+
...repo.settings,
56+
...data,
57+
},
58+
},
59+
statusText,
60+
resp,
61+
]);
62+
}
63+
return {previous};
64+
},
65+
onError: (_error, _data, context) => {
66+
if (context?.previous) {
67+
queryClient.setQueryData(repoQueryKey, context.previous);
68+
}
69+
},
70+
onSettled: () => {
71+
queryClient.invalidateQueries({
72+
queryKey: [`/organizations/${organization.slug}/repos/`],
73+
});
74+
queryClient.invalidateQueries({
75+
queryKey: getSeerOnboardingCheckQueryOptions({organization}).queryKey,
76+
});
77+
queryClient.invalidateQueries({queryKey: repoQueryKey});
78+
},
79+
});
2080

2181
return (
2282
<Stack gap="lg">
@@ -27,59 +87,58 @@ export function RepoDetailsForm({organization, repoWithSettings}: Props) {
2787
)}
2888
</Alert>
2989
)}
30-
<Form
31-
allowUndo
32-
saveOnBlur
33-
apiMethod="PUT"
34-
apiEndpoint={`/organizations/${organization.slug}/repos/settings/`}
35-
initialData={
36-
{
37-
enabledCodeReview: repoWithSettings?.settings?.enabledCodeReview ?? false,
38-
codeReviewTriggers: repoWithSettings?.settings?.codeReviewTriggers ?? [],
39-
repositoryIds: [repoWithSettings.id],
40-
} satisfies RepositorySettings
41-
}
42-
>
43-
<JsonForm
44-
disabled={!canWrite}
45-
forms={[
46-
{
47-
title: t('AI Code Review'),
48-
fields: [
49-
{
50-
name: 'enabledCodeReview',
51-
label: t('Enable Code Review'),
52-
help: t('Seer will review your PRs and flag potential bugs.'),
53-
type: 'boolean',
54-
getData: data => ({
55-
enabledCodeReview: data.enabledCodeReview,
56-
repositoryIds: [repoWithSettings.id],
57-
}),
58-
},
59-
{
60-
name: 'codeReviewTriggers',
61-
label: t('Code Review Triggers'),
62-
help: tct(
63-
'Reviews can always run on demand by calling [code:@sentry review], whenever a PR is opened, or after each commit is pushed to a PR.',
64-
{code: <code />}
65-
),
66-
type: 'choice',
67-
multiple: true,
68-
choices: [
69-
['on_ready_for_review', t('On Ready for Review')],
70-
['on_new_commit', t('On New Commit')],
71-
],
72-
formatMessageValue: false,
73-
getData: data => ({
74-
codeReviewTriggers: data.codeReviewTriggers,
75-
repositoryIds: [repoWithSettings.id],
76-
}),
77-
},
78-
],
79-
},
80-
]}
81-
/>
82-
</Form>
90+
<FieldGroup title={t('AI Code Review')}>
91+
<AutoSaveForm
92+
name="enabledCodeReview"
93+
schema={schema}
94+
initialValue={repoWithSettings?.settings?.enabledCodeReview ?? false}
95+
mutationOptions={repoMutationOpts}
96+
>
97+
{field => (
98+
<field.Layout.Row
99+
label={t('Enable Code Review')}
100+
hintText={t('Seer will review your PRs and flag potential bugs.')}
101+
>
102+
<Container flexGrow={1}>
103+
<field.Switch
104+
checked={field.state.value}
105+
onChange={field.handleChange}
106+
disabled={!canWrite}
107+
/>
108+
</Container>
109+
</field.Layout.Row>
110+
)}
111+
</AutoSaveForm>
112+
<AutoSaveForm
113+
name="codeReviewTriggers"
114+
schema={schema}
115+
initialValue={repoWithSettings?.settings?.codeReviewTriggers ?? []}
116+
mutationOptions={repoMutationOpts}
117+
>
118+
{field => (
119+
<field.Layout.Row
120+
label={t('Code Review Triggers')}
121+
hintText={tct(
122+
'Reviews can always run on demand by calling [code:@sentry review], whenever a PR is opened, or after each commit is pushed to a PR.',
123+
{code: <code />}
124+
)}
125+
>
126+
<Container flexGrow={1}>
127+
<field.Select
128+
multiple
129+
value={field.state.value}
130+
onChange={field.handleChange}
131+
disabled={!canWrite}
132+
options={[
133+
{value: 'on_ready_for_review', label: t('On Ready for Review')},
134+
{value: 'on_new_commit', label: t('On New Commit')},
135+
]}
136+
/>
137+
</Container>
138+
</field.Layout.Row>
139+
)}
140+
</AutoSaveForm>
141+
</FieldGroup>
83142
</Stack>
84143
);
85144
}

0 commit comments

Comments
 (0)