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
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,29 @@ export const defaultEnableSeerFeaturesValue = (organization: Organization) => {

export const makeHideAiFeaturesField = (organization: Organization): FieldObject => {
const isBaa = false; // TODO: add a check here once we have a way to check if the org is a BAA customer. Leave it as false for now.
const hasFeatureFlag = organization.features.includes('gen-ai-features');

return {
name: 'hideAiFeatures',
type: 'boolean',
label: t('Show Generative AI Features'),
help: tct(
'Allows organization members to access [docs:features] powered by generative AI',
{
docs: (
<ExternalLink href="https://docs.sentry.io/product/ai-in-sentry/#ai-powered-features" />
),
}
),
help: tct('Allows organization members to access [link:generative AI features]', {
link: (
<ExternalLink href="https://docs.sentry.io/product/ai-in-sentry/#ai-powered-features" />
),
}),
defaultValue: defaultEnableSeerFeaturesValue(organization),
disabled: ({access}) => !access.has('org:write'),
disabled: ({access}) => !hasFeatureFlag || !access.has('org:write'),
getValue: value => {
// Reversing value because the field was previously called hideAiFeatures and we've inverted the behavior.
return !value;
},
setValue: value => {
if (!hasFeatureFlag) {
return false;
}
return value;
},
Comment on lines +30 to +35
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary if it's already disabled.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the whole set value fn?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no i think it is, otherwise the value shows as true
Screenshot 2025-11-19 at 4 24 24 PM

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then it should be done in getValue I think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm that doesn't seem to work?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: setValue doesn't invert hideAiFeatures value correctly

The setValue function returns the raw value when the feature flag is enabled, but it should invert it to match the UI semantics. The field stores hideAiFeatures from the backend, but the UI displays "Show Generative AI Features" (the opposite). When hasFeatureFlag is true, setValue should return !value to invert the backend value to the UI value, just like defaultValue does with !organization.hideAiFeatures. Without this inversion, a backend value of hideAiFeatures: false (AI enabled) would incorrectly display as unchecked in the UI.

Fix in Cursor Fix in Web

disabledReason: isBaa
? t(
'To remain HIPAA compliant, Generative AI features are disabled for BAA customers'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,10 @@ describe('OrganizationSettingsForm', () => {

it('can toggle "Show Generative AI Features"', async () => {
// Default org fixture has hideAiFeatures: false, so Seer is enabled by default
const hiddenAiOrg = OrganizationFixture({hideAiFeatures: true});
const hiddenAiOrg = OrganizationFixture({
hideAiFeatures: true,
features: ['gen-ai-features'],
});
render(
<OrganizationSettingsForm
{...routerProps}
Expand Down Expand Up @@ -241,7 +244,7 @@ describe('OrganizationSettingsForm', () => {
{
organization: {
...organization,
features: ['autofix'],
features: ['autofix', 'gen-ai-features'],
},
}
);
Expand All @@ -250,13 +253,42 @@ describe('OrganizationSettingsForm', () => {
expect(toggle).toBeEnabled();
});

it('disables "Show Generative AI Features" toggle when feature flag is off', () => {
render(
<OrganizationSettingsForm
{...routerProps}
initialData={OrganizationFixture()}
onSave={onSave}
/>,
{
organization: {
...organization,
features: [], // No gen-ai-features flag
},
}
);

const checkbox = screen.getByRole('checkbox', {
name: 'Show Generative AI Features',
});

expect(checkbox).toBeDisabled();
expect(checkbox).not.toBeChecked();
});

it('renders AI Code Review field', () => {
render(
<OrganizationSettingsForm
{...routerProps}
initialData={OrganizationFixture({hideAiFeatures: true})}
onSave={onSave}
/>
/>,
{
organization: {
...organization,
features: ['gen-ai-features'],
},
}
);

expect(screen.getByText('Enable AI Code Review')).toBeInTheDocument();
Expand All @@ -282,7 +314,13 @@ describe('OrganizationSettingsForm', () => {
// This logic is inverted from the variable name
initialData={OrganizationFixture({hideAiFeatures: false})}
onSave={onSave}
/>
/>,
{
organization: {
...organization,
features: ['gen-ai-features'],
},
}
);

expect(screen.queryByText('Enable AI Code Review')).not.toBeInTheDocument();
Expand All @@ -299,7 +337,13 @@ describe('OrganizationSettingsForm', () => {
{...routerProps}
initialData={OrganizationFixture({hideAiFeatures: true})}
onSave={onSave}
/>
/>,
{
organization: {
...organization,
features: ['gen-ai-features'],
},
}
);

expect(screen.getByText('Enable AI Code Review')).toBeInTheDocument();
Expand All @@ -314,7 +358,13 @@ describe('OrganizationSettingsForm', () => {
{...routerProps}
initialData={OrganizationFixture({hideAiFeatures: false})}
onSave={onSave}
/>
/>,
{
organization: {
...organization,
features: ['gen-ai-features'],
},
}
);

MockApiClient.addMockResponse({
Expand Down Expand Up @@ -350,7 +400,13 @@ describe('OrganizationSettingsForm', () => {
{...routerProps}
initialData={OrganizationFixture({hideAiFeatures: true})}
onSave={onSave}
/>
/>,
{
organization: {
...organization,
features: ['gen-ai-features'],
},
}
);

const preventAiField = screen.getByRole('checkbox', {
Expand All @@ -361,6 +417,36 @@ describe('OrganizationSettingsForm', () => {
expect(screen.queryByTestId('prevent-ai-disabled-tag')).not.toBeInTheDocument();
});

it('is disabled when feature flag is off', () => {
jest.mocked(RegionUtils.getRegionDataFromOrganization).mockReturnValue({
name: 'us',
displayName: 'United States of America (US)',
url: 'https://sentry.example.com',
});

render(
<OrganizationSettingsForm
{...routerProps}
initialData={OrganizationFixture({hideAiFeatures: true})}
onSave={onSave}
/>,
{
organization: {
...organization,
features: ['gen-ai-features'],
},
}
);

const preventAiField = screen.getByRole('checkbox', {
name: /Enable AI Code Review/i,
});
expect(preventAiField).toBeInTheDocument();
expect(preventAiField).toBeEnabled();

expect(screen.queryByTestId('prevent-ai-disabled-tag')).not.toBeInTheDocument();
});

it('is disabled when non US region', async () => {
jest.mocked(RegionUtils.getRegionDataFromOrganization).mockReturnValue({
name: 'de',
Expand All @@ -373,7 +459,13 @@ describe('OrganizationSettingsForm', () => {
{...routerProps}
initialData={OrganizationFixture({hideAiFeatures: true})}
onSave={onSave}
/>
/>,
{
organization: {
...organization,
features: ['gen-ai-features'],
},
}
);

const preventAiField = screen.getByRole('checkbox', {
Expand Down Expand Up @@ -410,6 +502,7 @@ describe('OrganizationSettingsForm', () => {
organization: {
...organization,
access: ['org:write'],
features: ['gen-ai-features'],
},
}
);
Expand Down Expand Up @@ -439,7 +532,9 @@ describe('OrganizationSettingsForm', () => {
/>,
{
organization: {
...organization,
access: ['org:read'],
features: ['gen-ai-features'],
},
}
);
Expand All @@ -465,7 +560,9 @@ describe('OrganizationSettingsForm', () => {
/>,
{
organization: {
...organization,
access: ['org:write'],
features: ['gen-ai-features'],
},
}
);
Expand Down
Loading