Skip to content

Commit 870dc45

Browse files
[ft] Add an org seer toggle for PR review and test gen in Seer (#95547)
<!-- Describe your PR here. --> Is consumed by `seer_rpc` while communicating with Seer. This PR must be merged AFTER #95456 is deployed. <img width="1011" height="558" alt="Screenshot 2025-07-14 at 1 06 24 PM" src="https://github.com/user-attachments/assets/0822e352-0a04-473d-b477-57ef3e3a8e3f" /> <!-- Sentry employees and contractors can delete or ignore the following. --> ### Legal Boilerplate Look, I get it. The entity doing business as "Sentry" was incorporated in the State of Delaware in 2015 as Functional Software, Inc. and is gonna need some rights from me in order to utilize my contributions in this here PR. So here's the deal: I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Sentry can use, modify, copy, and redistribute my contributions, under Sentry's choice of terms.
1 parent 4ebcfbf commit 870dc45

File tree

4 files changed

+117
-1
lines changed

4 files changed

+117
-1
lines changed

api-docs/components/schemas/organization-details.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@
7575
"enhancedPrivacy": {
7676
"type": "boolean"
7777
},
78+
"enablePrReviewTestGeneration": {
79+
"type": "boolean"
80+
},
7881
"experiments": {
7982
"type": "object"
8083
},

static/app/data/forms/organizationGeneralSettings.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {FeatureBadge} from 'sentry/components/core/badge/featureBadge';
12
import type {JsonFormObject} from 'sentry/components/forms/types';
23
import ExternalLink from 'sentry/components/links/externalLink';
34
import {t, tct} from 'sentry/locale';
@@ -43,6 +44,26 @@ const formGroups: JsonFormObject[] = [
4344
}),
4445
visible: () => !ConfigStore.get('isSelfHostedErrorsOnly'),
4546
},
47+
{
48+
name: 'enablePrReviewTestGeneration',
49+
type: 'boolean',
50+
label: tct('Enable PR Review and Test Generation [badge]', {
51+
badge: <FeatureBadge type="beta" style={{marginBottom: '2px'}} />,
52+
}),
53+
help: tct(
54+
'Use AI to generate feedback and tests in pull requests [link:Learn more]',
55+
{
56+
link: (
57+
<ExternalLink href="https://docs.sentry.io/product/ai-in-sentry/sentry-prevent-ai/" />
58+
),
59+
}
60+
),
61+
visible: ({model}) => {
62+
// Show field when AI features are enabled (hideAiFeatures is false)
63+
const hideAiFeatures = model.getValue('hideAiFeatures');
64+
return hideAiFeatures;
65+
},
66+
},
4667
],
4768
},
4869
];

static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.spec.tsx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,95 @@ describe('OrganizationSettingsForm', function () {
248248
const toggle = screen.getByRole('checkbox', {name: 'Show Generative AI Features'});
249249
expect(toggle).toBeEnabled();
250250
});
251+
252+
it('renders PR Review and Test Generation field', function () {
253+
render(
254+
<OrganizationSettingsForm
255+
{...routerProps}
256+
initialData={OrganizationFixture({hideAiFeatures: true})}
257+
onSave={onSave}
258+
/>
259+
);
260+
261+
expect(screen.getByText('Enable PR Review and Test Generation')).toBeInTheDocument();
262+
263+
expect(screen.getByText('beta')).toBeInTheDocument();
264+
265+
expect(
266+
screen.getByText('Use AI to generate feedback and tests in pull requests')
267+
).toBeInTheDocument();
268+
269+
const learnMoreLink = screen.getByRole('link', {name: 'Learn more'});
270+
expect(learnMoreLink).toBeInTheDocument();
271+
expect(learnMoreLink).toHaveAttribute(
272+
'href',
273+
'https://docs.sentry.io/product/ai-in-sentry/sentry-prevent-ai/'
274+
);
275+
});
276+
277+
it('hides PR Review and Test Generation field when AI features are disabled', function () {
278+
render(
279+
<OrganizationSettingsForm
280+
{...routerProps}
281+
// This logic is inverted from the variable name
282+
initialData={OrganizationFixture({hideAiFeatures: false})}
283+
onSave={onSave}
284+
/>
285+
);
286+
287+
expect(
288+
screen.queryByText('Enable PR Review and Test Generation')
289+
).not.toBeInTheDocument();
290+
expect(
291+
screen.queryByText('Use AI to generate feedback and tests in pull requests')
292+
).not.toBeInTheDocument();
293+
});
294+
295+
it('shows PR Review and Test Generation field when AI features are enabled', function () {
296+
render(
297+
<OrganizationSettingsForm
298+
{...routerProps}
299+
initialData={OrganizationFixture({hideAiFeatures: true})}
300+
onSave={onSave}
301+
/>
302+
);
303+
304+
expect(screen.getByText('Enable PR Review and Test Generation')).toBeInTheDocument();
305+
expect(
306+
screen.getByText('Use AI to generate feedback and tests in pull requests')
307+
).toBeInTheDocument();
308+
});
309+
310+
it('shows/hides PR Review field when toggling AI features', async function () {
311+
render(
312+
<OrganizationSettingsForm
313+
{...routerProps}
314+
initialData={OrganizationFixture({hideAiFeatures: false})}
315+
onSave={onSave}
316+
/>
317+
);
318+
319+
MockApiClient.addMockResponse({
320+
url: `/organizations/${organization.slug}/`,
321+
method: 'PUT',
322+
});
323+
324+
// Initially AI features are disabled, so PR Review field should be hidden
325+
expect(
326+
screen.queryByText('Enable PR Review and Test Generation')
327+
).not.toBeInTheDocument();
328+
329+
const aiToggle = screen.getByRole('checkbox', {name: 'Show Generative AI Features'});
330+
await userEvent.click(aiToggle);
331+
332+
// PR Review field should now be visible
333+
expect(screen.getByText('Enable PR Review and Test Generation')).toBeInTheDocument();
334+
335+
await userEvent.click(aiToggle);
336+
337+
// PR Review field should be hidden again
338+
expect(
339+
screen.queryByText('Enable PR Review and Test Generation')
340+
).not.toBeInTheDocument();
341+
});
251342
});

static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ function OrganizationSettingsForm({initialData, onSave}: Props) {
7575
formsConfig[0]!.fields = [
7676
...formsConfig[0]!.fields.slice(0, 2),
7777
organizationIdField,
78-
...formsConfig[0]!.fields.slice(2),
78+
...formsConfig[0]!.fields.slice(2, 3),
7979
makeHideAiFeaturesField(organization),
8080
{
8181
name: 'codecovAccess',
@@ -120,6 +120,7 @@ function OrganizationSettingsForm({initialData, onSave}: Props) {
120120
</PoweredByCodecov>
121121
),
122122
},
123+
...formsConfig[0]!.fields.slice(3),
123124
];
124125
return formsConfig;
125126
}, [access, organization]);

0 commit comments

Comments
 (0)