Skip to content
Draft
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
70 changes: 70 additions & 0 deletions static/app/types/prevent.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type {PreventAIFeatureConfig, Sensitivity} from 'sentry/types/prevent';

Check failure on line 1 in static/app/types/prevent.test.tsx

View workflow job for this annotation

GitHub Actions / typescript

'"sentry/types/prevent"' has no exported member named 'PreventAIFeatureConfig'. Did you mean 'PreventAIFeatureConfigsByName'?

describe('Prevent Types', () => {
describe('Sensitivity', () => {
it('should accept all valid sensitivity values', () => {
const validSensitivities: Sensitivity[] = ['low', 'medium', 'high', 'critical'];

validSensitivities.forEach(sensitivity => {
expect(typeof sensitivity).toBe('string');
expect(['low', 'medium', 'high', 'critical']).toContain(sensitivity);
});
});

it('should be assignable to string but maintain type safety', () => {
const lowSensitivity: Sensitivity = 'low';
const mediumSensitivity: Sensitivity = 'medium';
const highSensitivity: Sensitivity = 'high';
const criticalSensitivity: Sensitivity = 'critical';

expect(lowSensitivity).toBe('low');
expect(mediumSensitivity).toBe('medium');
expect(highSensitivity).toBe('high');
expect(criticalSensitivity).toBe('critical');
});
});

describe('PreventAIFeatureConfig with Sensitivity', () => {
it('should accept sensitivity as optional Sensitivity type', () => {
const configWithSensitivity: PreventAIFeatureConfig = {
enabled: true,
triggers: {
on_command_phrase: false,
on_ready_for_review: true,
},
sensitivity: 'high',
};

expect(configWithSensitivity.sensitivity).toBe('high');
});

it('should work without sensitivity field', () => {
const configWithoutSensitivity: PreventAIFeatureConfig = {
enabled: false,
triggers: {
on_command_phrase: true,
on_ready_for_review: false,
},
};

expect(configWithoutSensitivity.sensitivity).toBeUndefined();
});

it('should handle all sensitivity levels in feature config', () => {
const sensitivityLevels: Sensitivity[] = ['low', 'medium', 'high', 'critical'];

sensitivityLevels.forEach(sensitivity => {
const config: PreventAIFeatureConfig = {
enabled: true,
triggers: {
on_command_phrase: false,
on_ready_for_review: false,
},
sensitivity,
};

expect(config.sensitivity).toBe(sensitivity);
});
});
});
});
4 changes: 3 additions & 1 deletion static/app/types/prevent.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Add any new providers here e.g., 'github' | 'bitbucket' | 'gitlab'
export type PreventAIProvider = 'github';

export type Sensitivity = 'low' | 'medium' | 'high' | 'critical';

interface PreventAIRepo {
fullName: string;
id: string;
Expand All @@ -18,7 +20,7 @@ export interface PreventAIOrg {
interface PreventAIFeatureConfig {
enabled: boolean;
triggers: PreventAIFeatureTriggers;
sensitivity?: string;
sensitivity?: Sensitivity;
}

export interface PreventAIFeatureTriggers {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import {updateOrganization} from 'sentry/actionCreators/organizations';
import type {Organization} from 'sentry/types/organization';
import type {PreventAIConfig, PreventAIFeatureTriggers} from 'sentry/types/prevent';
import type {
PreventAIConfig,
PreventAIFeatureTriggers,
Sensitivity,
} from 'sentry/types/prevent';
import {fetchMutation, useMutation} from 'sentry/utils/queryClient';
import useOrganization from 'sentry/utils/useOrganization';

Expand All @@ -10,6 +14,7 @@ interface UpdatePreventAIFeatureParams {
orgName: string;
// if repoName is provided, edit repo_overrides for that repo, otherwise edit org_defaults
repoName?: string;
sensitivity?: Sensitivity;
trigger?: Partial<PreventAIFeatureTriggers>;
}

Expand Down Expand Up @@ -73,7 +78,7 @@ export function makePreventAIConfig(
featureConfig[params.feature] = {
enabled: params.enabled,
triggers: {...featureConfig[params.feature].triggers, ...params.trigger},
sensitivity: featureConfig[params.feature].sensitivity,
sensitivity: params.sensitivity ?? featureConfig[params.feature].sensitivity,
};

return updatedConfig;
Expand Down
240 changes: 221 additions & 19 deletions static/app/views/prevent/preventAI/manageReposPanel.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import selectEvent from 'react-select-event';

Check failure on line 1 in static/app/views/prevent/preventAI/manageReposPanel.spec.tsx

View workflow job for this annotation

GitHub Actions / typescript

Cannot find module 'react-select-event' or its corresponding type declarations.
import {OrganizationFixture} from 'sentry-fixture/organization';

import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';

import type {PreventAIOrgConfig} from 'sentry/types/prevent';
import ManageReposPanel, {
Expand All @@ -9,7 +10,10 @@

let mockUpdatePreventAIFeatureReturn: any = {};
jest.mock('sentry/views/prevent/preventAI/hooks/useUpdatePreventAIFeature', () => ({
useUpdatePreventAIFeature: () => mockUpdatePreventAIFeatureReturn,
useUpdatePreventAIFeature: () => ({
enableFeature: jest.fn(),
...mockUpdatePreventAIFeatureReturn,
}),
}));

describe('ManageReposPanel', () => {
Expand All @@ -22,6 +26,7 @@
};

beforeEach(() => {
MockApiClient.clearMockResponses();
jest.clearAllMocks();
mockUpdatePreventAIFeatureReturn = {};
});
Expand Down Expand Up @@ -152,30 +157,227 @@
},
});
});
});

it('returns org defaults when repo override is not present', () => {
const orgConfig: PreventAIOrgConfig = {
org_defaults: {
bug_prediction: {
enabled: true,
triggers: {on_command_phrase: true, on_ready_for_review: false},
describe('Sensitivity Dropdowns', () => {
const mockOrganizationWithEnabledFeatures = OrganizationFixture({
preventAiConfigGithub: {
schema_version: 'v1',
github_organizations: {},
default_org_config: {
org_defaults: {
bug_prediction: {
enabled: true,
triggers: {on_command_phrase: true, on_ready_for_review: false},
sensitivity: 'medium',
},
test_generation: {
enabled: false,
triggers: {on_command_phrase: false, on_ready_for_review: false},
},
vanilla: {
enabled: true,
triggers: {on_command_phrase: false, on_ready_for_review: false},
sensitivity: 'high',
},
},
test_generation: {
enabled: false,
triggers: {on_command_phrase: false, on_ready_for_review: false},
repo_overrides: {},
},
},
});

it('shows sensitivity dropdown for enabled vanilla feature', async () => {
render(<ManageReposPanel {...defaultProps} />, {
organization: mockOrganizationWithEnabledFeatures,
});

const dropdown = await screen.findByTestId('pr-review-sensitivity-dropdown');
expect(dropdown).toBeInTheDocument();

// Check that the current value is displayed
expect(screen.getByDisplayValue('High')).toBeInTheDocument();
});

it('shows sensitivity dropdown for enabled bug prediction feature', async () => {
render(<ManageReposPanel {...defaultProps} />, {
organization: mockOrganizationWithEnabledFeatures,
});

const dropdown = await screen.findByTestId('error-prediction-sensitivity-dropdown');
expect(dropdown).toBeInTheDocument();

// Check that the current value is displayed
expect(screen.getByDisplayValue('Medium')).toBeInTheDocument();
});

it('does not show sensitivity dropdown when vanilla feature is disabled', async () => {

Check failure on line 213 in static/app/views/prevent/preventAI/manageReposPanel.spec.tsx

View workflow job for this annotation

GitHub Actions / pre-commit lint

Async arrow function has no 'await' expression

Check failure on line 213 in static/app/views/prevent/preventAI/manageReposPanel.spec.tsx

View workflow job for this annotation

GitHub Actions / eslint

Async arrow function has no 'await' expression
const orgWithDisabledVanilla = OrganizationFixture({
preventAiConfigGithub: {

Check failure on line 215 in static/app/views/prevent/preventAI/manageReposPanel.spec.tsx

View workflow job for this annotation

GitHub Actions / typescript

Type '{ default_org_config: { org_defaults: { vanilla: { enabled: false; triggers: { on_command_phrase: false; on_ready_for_review: false; }; sensitivity: "medium"; }; bug_prediction: PreventAIFeatureConfig; test_generation: PreventAIFeatureConfig; }; repo_overrides: Record<string, PreventAIFeatureConfigsByName>; }; githu...' is not assignable to type 'PreventAIConfig'.
...mockOrganizationWithEnabledFeatures.preventAiConfigGithub,
default_org_config: {
...mockOrganizationWithEnabledFeatures.preventAiConfigGithub!
.default_org_config,
org_defaults: {
...mockOrganizationWithEnabledFeatures.preventAiConfigGithub!
.default_org_config.org_defaults,
vanilla: {
enabled: false,
triggers: {on_command_phrase: false, on_ready_for_review: false},
sensitivity: 'medium',
},
},
},
vanilla: {
enabled: true,
triggers: {on_command_phrase: false, on_ready_for_review: false},
},
});

render(<ManageReposPanel {...defaultProps} />, {
organization: orgWithDisabledVanilla,
});

expect(
screen.queryByTestId('pr-review-sensitivity-dropdown')
).not.toBeInTheDocument();
});

it('does not show sensitivity dropdown when bug prediction feature is disabled', async () => {

Check failure on line 242 in static/app/views/prevent/preventAI/manageReposPanel.spec.tsx

View workflow job for this annotation

GitHub Actions / pre-commit lint

Async arrow function has no 'await' expression

Check failure on line 242 in static/app/views/prevent/preventAI/manageReposPanel.spec.tsx

View workflow job for this annotation

GitHub Actions / eslint

Async arrow function has no 'await' expression
const orgWithDisabledBugPrediction = OrganizationFixture({
preventAiConfigGithub: {

Check failure on line 244 in static/app/views/prevent/preventAI/manageReposPanel.spec.tsx

View workflow job for this annotation

GitHub Actions / typescript

Type '{ default_org_config: { org_defaults: { bug_prediction: { enabled: false; triggers: { on_command_phrase: false; on_ready_for_review: false; }; sensitivity: "medium"; }; test_generation: PreventAIFeatureConfig; vanilla: PreventAIFeatureConfig; }; repo_overrides: Record<string, PreventAIFeatureConfigsByName>; }; githu...' is not assignable to type 'PreventAIConfig'.
...mockOrganizationWithEnabledFeatures.preventAiConfigGithub,
default_org_config: {
...mockOrganizationWithEnabledFeatures.preventAiConfigGithub!
.default_org_config,
org_defaults: {
...mockOrganizationWithEnabledFeatures.preventAiConfigGithub!
.default_org_config.org_defaults,
bug_prediction: {
enabled: false,
triggers: {on_command_phrase: false, on_ready_for_review: false},
sensitivity: 'medium',
},
},
},
},
repo_overrides: {},
});

render(<ManageReposPanel {...defaultProps} />, {
organization: orgWithDisabledBugPrediction,
});

expect(
screen.queryByTestId('error-prediction-sensitivity-dropdown')
).not.toBeInTheDocument();
});

it('calls enableFeature with correct sensitivity when vanilla sensitivity is changed', async () => {
const mockEnableFeature = jest.fn();
mockUpdatePreventAIFeatureReturn = {
enableFeature: mockEnableFeature,
isLoading: false,
};
const result = getRepoConfig(orgConfig, 'repo-2');
expect(result).toEqual({
doesUseOrgDefaults: true,
repoConfig: orgConfig.org_defaults,

render(<ManageReposPanel {...defaultProps} />, {
organization: mockOrganizationWithEnabledFeatures,
});

const dropdown = await screen.findByTestId('pr-review-sensitivity-dropdown');
await selectEvent.openMenu(dropdown);
await selectEvent.select(dropdown, 'Low');

await waitFor(() => {
expect(mockEnableFeature).toHaveBeenCalledWith({
feature: 'vanilla',
enabled: true,
orgName: 'org-1',
repoName: 'repo-1',
sensitivity: 'low',
});
});
});

it('calls enableFeature with correct sensitivity when bug prediction sensitivity is changed', async () => {
const mockEnableFeature = jest.fn();
mockUpdatePreventAIFeatureReturn = {
enableFeature: mockEnableFeature,
isLoading: false,
};

render(<ManageReposPanel {...defaultProps} />, {
organization: mockOrganizationWithEnabledFeatures,
});

const dropdown = await screen.findByTestId('error-prediction-sensitivity-dropdown');
await selectEvent.openMenu(dropdown);
await selectEvent.select(dropdown, 'Critical');

await waitFor(() => {
expect(mockEnableFeature).toHaveBeenCalledWith({
feature: 'bug_prediction',
enabled: true,
orgName: 'org-1',
repoName: 'repo-1',
sensitivity: 'critical',
});
});
});

it('disables sensitivity dropdowns when loading', async () => {
mockUpdatePreventAIFeatureReturn = {
enableFeature: jest.fn(),
isLoading: true,
};

render(<ManageReposPanel {...defaultProps} />, {
organization: mockOrganizationWithEnabledFeatures,
});

const vanillaDropdown = await screen.findByTestId('pr-review-sensitivity-dropdown');
const bugPredictionDropdown = await screen.findByTestId(
'error-prediction-sensitivity-dropdown'
);

expect(vanillaDropdown).toBeDisabled();
expect(bugPredictionDropdown).toBeDisabled();
});

it('shows default sensitivity value when sensitivity is undefined', async () => {
const orgWithUndefinedSensitivity = OrganizationFixture({
preventAiConfigGithub: {
schema_version: 'v1',
github_organizations: {},
default_org_config: {
org_defaults: {
bug_prediction: {
enabled: true,
triggers: {on_command_phrase: true, on_ready_for_review: false},
// sensitivity is undefined
},
test_generation: {
enabled: false,
triggers: {on_command_phrase: false, on_ready_for_review: false},
},
vanilla: {
enabled: true,
triggers: {on_command_phrase: false, on_ready_for_review: false},
// sensitivity is undefined
},
},
repo_overrides: {},
},
},
});

render(<ManageReposPanel {...defaultProps} />, {
organization: orgWithUndefinedSensitivity,
});

// Should default to 'medium' when sensitivity is undefined
const vanillaDropdown = await screen.findByTestId('pr-review-sensitivity-dropdown');
const bugPredictionDropdown = await screen.findByTestId(
'error-prediction-sensitivity-dropdown'
);

expect(vanillaDropdown).toHaveDisplayValue('Medium');
expect(bugPredictionDropdown).toHaveDisplayValue('Medium');
});
});
});
Loading
Loading