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
@@ -1,5 +1,6 @@
import {useProjectSeerPreferences} from 'sentry/components/events/autofix/preferences/hooks/useProjectSeerPreferences';
import {useOrganizationSeerSetup} from 'sentry/components/events/autofix/useOrganizationSeerSetup';
import type {Project} from 'sentry/types/project';
import {getSelectedProjectList} from 'sentry/utils/project/useSelectedProjectsHaveField';
import useOrganization from 'sentry/utils/useOrganization';
import usePageFilters from 'sentry/utils/usePageFilters';
Expand All @@ -9,15 +10,15 @@ import useProjects from 'sentry/utils/useProjects';
// - Org has web vitals suggestions feature enabled
// - Org has ai features enabled and has given consent
// - Project has a github repository set up
export function useHasSeerWebVitalsSuggestions() {
export function useHasSeerWebVitalsSuggestions(selectedProject?: Project) {
const organization = useOrganization();

const {
selection: {projects},
} = usePageFilters();
const {projects: allProjects} = useProjects();
const selectedProjects = getSelectedProjectList(projects, allProjects);
const project = selectedProjects[0];
const project = selectedProject ?? selectedProjects[0]; // By default, use the first selected project if no project is provided

const {preference, codeMappingRepos} = useProjectSeerPreferences(project!);
const hasConfiguredRepos = Boolean(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
userEvent,
} from 'sentry-test/reactTestingLibrary';

import ProjectsStore from 'sentry/stores/projectsStore';
import {IssueTitle} from 'sentry/types/group';
import * as utils from 'sentry/utils/isActiveSuperuser';
import ProjectPerformance, {
Expand Down Expand Up @@ -49,10 +50,17 @@ const manageDetectorData = [
label: 'HTTP/1.1 Overhead Detection',
key: 'http_overhead_detection_enabled',
},
{label: 'Web Vitals Detection', key: 'web_vitals_detection_enabled'},
];

describe('projectPerformance', () => {
const org = OrganizationFixture({features: ['performance-view']});
const org = OrganizationFixture({
features: [
'performance-view',
'performance-web-vitals-seer-suggestions',
'gen-ai-features',
],
});
const project = ProjectFixture();
const configUrl = '/projects/org-slug/project-slug/transaction-threshold/configure/';
let getMock: jest.Mock;
Expand All @@ -65,10 +73,15 @@ describe('projectPerformance', () => {
pathname: `/organizations/${org.slug}/settings/projects/${project.slug}/performance/`,
query: {},
},
params: {
orgId: org.slug,
projectId: project.slug,
},
};

beforeEach(() => {
MockApiClient.clearMockResponses();
ProjectsStore.loadInitialData([project]);
getMock = MockApiClient.addMockResponse({
url: configUrl,
method: 'GET',
Expand Down Expand Up @@ -97,7 +110,7 @@ describe('projectPerformance', () => {
MockApiClient.addMockResponse({
url: '/projects/org-slug/project-slug/',
method: 'GET',
body: {},
body: project,
statusCode: 200,
});
MockApiClient.addMockResponse({
Expand All @@ -112,6 +125,25 @@ describe('projectPerformance', () => {
body: {},
statusCode: 200,
});
MockApiClient.addMockResponse({
url: '/projects/org-slug/project-slug/seer/preferences/',
method: 'GET',
body: {
code_mapping_repos: [
{provider: 'github', owner: 'owner', name: 'repo', externalId: '123'},
],
},
statusCode: 200,
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/seer/setup-check/',
method: 'GET',
body: {
setupAcknowledgement: {
orgHasAcknowledged: true,
},
},
});
});

it('renders the fields', async () => {
Expand Down Expand Up @@ -327,6 +359,17 @@ describe('projectPerformance', () => {
index: 1,
},
},
{
title: IssueTitle.WEB_VITALS,
threshold: DetectorConfigCustomer.WEB_VITALS_COUNT,
allowedValues: allowedCountValues,
defaultValue: 10,
newValue: 20,
sliderIdentifier: {
label: 'Minimum Sample Count',
index: 0,
},
},
])(
'renders detector thresholds settings for $title issue',
async ({
Expand All @@ -350,6 +393,7 @@ describe('projectPerformance', () => {
large_http_payload_detection_enabled: true,
n_plus_one_api_calls_detection_enabled: true,
consecutive_http_spans_detection_enabled: true,
web_vitals_detection_enabled: true,
};
const performanceIssuesGetMock = MockApiClient.addMockResponse({
url: '/projects/org-slug/project-slug/performance-issues/configure/',
Expand Down Expand Up @@ -463,7 +507,11 @@ describe('projectPerformance', () => {

render(<ProjectPerformance />, {
organization: OrganizationFixture({
features: ['performance-view'],
features: [
'performance-view',
'performance-web-vitals-seer-suggestions',
'gen-ai-features',
],
}),
initialRouterConfig,
});
Expand Down Expand Up @@ -526,7 +574,11 @@ describe('projectPerformance', () => {

render(<ProjectPerformance />, {
organization: OrganizationFixture({
features: ['performance-view'],
features: [
'performance-view',
'performance-web-vitals-seer-suggestions',
'gen-ai-features',
],
access: ['project:read'],
}),
initialRouterConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import useApi from 'sentry/utils/useApi';
import {useDetailedProject} from 'sentry/utils/useDetailedProject';
import useOrganization from 'sentry/utils/useOrganization';
import {useParams} from 'sentry/utils/useParams';
import {useHasSeerWebVitalsSuggestions} from 'sentry/views/insights/browser/webVitals/utils/useHasSeerWebVitalsSuggestions';
import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
import {ProjectPermissionAlert} from 'sentry/views/settings/project/projectPermissionAlert';

Expand Down Expand Up @@ -90,6 +91,7 @@ enum DetectorConfigAdmin {
TRANSACTION_DURATION_REGRESSION_ENABLED = 'transaction_duration_regression_detection_enabled',
FUNCTION_DURATION_REGRESSION_ENABLED = 'function_duration_regression_detection_enabled',
DB_QUERY_INJECTION_ENABLED = 'db_query_injection_detection_enabled',
WEB_VITALS_ENABLED = 'web_vitals_detection_enabled',
}

export enum DetectorConfigCustomer {
Expand All @@ -108,6 +110,7 @@ export enum DetectorConfigCustomer {
CONSECUTIVE_HTTP_MIN_TIME_SAVED = 'consecutive_http_spans_min_time_saved_threshold',
HTTP_OVERHEAD_REQUEST_DELAY = 'http_request_delay_threshold',
SQL_INJECTION_QUERY_VALUE_LENGTH = 'sql_injection_query_value_length_threshold',
WEB_VITALS_COUNT = 'web_vitals_count',
}

type ProjectThreshold = {
Expand Down Expand Up @@ -177,6 +180,8 @@ function ProjectPerformance() {
orgSlug: organization.slug,
});

const hasWebVitalsSeerSuggestions = useHasSeerWebVitalsSuggestions(project);

const {
data: threshold,
isPending: isPendingThreshold,
Expand Down Expand Up @@ -549,6 +554,23 @@ function ProjectPerformance() {
'issue-query-injection-vulnerability-visible'
),
},
[IssueTitle.WEB_VITALS]: {
name: DetectorConfigAdmin.WEB_VITALS_ENABLED,
type: 'boolean',
label: t('Web Vitals Detection'),
defaultValue: true,
onChange: value => {
setApiQueryData<ProjectPerformanceSettings>(
queryClient,
getPerformanceIssueSettingsQueryKey(organization.slug, projectSlug),
data => ({
...data!,
web_vitals_detection_enabled: value,
})
);
},
visible: hasWebVitalsSeerSuggestions,
},
};

const performanceRegressionAdminFields: Field[] = [
Expand Down Expand Up @@ -957,6 +979,32 @@ function ProjectPerformance() {
],
initiallyCollapsed: issueType !== IssueType.QUERY_INJECTION_VULNERABILITY,
},
{
title: IssueTitle.WEB_VITALS,
fields: [
{
name: DetectorConfigCustomer.WEB_VITALS_COUNT,
type: 'range',
label: t('Minimum Sample Count'),
defaultValue: 10,
help: t(
'Setting the value to 10, means that web vital issues will only be created if there are at least 10 samples of the web vital type.'
),
tickValues: [0, allowedCountValues.length - 1],
allowedValues: allowedCountValues,
showTickLabels: true,
formatLabel: formatCount,
flexibleControlStateSize: true,
disabled: !(
hasAccess &&
performanceIssueSettings[DetectorConfigAdmin.WEB_VITALS_ENABLED]
),
disabledReason,
visible: hasWebVitalsSeerSuggestions,
},
],
initiallyCollapsed: issueType !== IssueType.WEB_VITALS,
},
];

// If the organization can manage detectors, add the admin field to the existing settings
Expand Down
Loading