diff --git a/static/app/components/events/autofix/claudeCodeIntegrationCta.spec.tsx b/static/app/components/events/autofix/claudeCodeIntegrationCta.spec.tsx index 77d6ab8c1e39d2..73c6e0f7e8bbe5 100644 --- a/static/app/components/events/autofix/claudeCodeIntegrationCta.spec.tsx +++ b/static/app/components/events/autofix/claudeCodeIntegrationCta.spec.tsx @@ -9,14 +9,28 @@ import {ProjectsStore} from 'sentry/stores/projectsStore'; describe('ClaudeCodeIntegrationCta', () => { const project = ProjectFixture(); + const enabledProject = ProjectFixture({ + ...project, + seerScannerAutomation: true, + autofixAutomationTuning: 'medium', + }); const organization = OrganizationFixture({ features: ['integrations-claude-code'], }); + function mockDetailedProject(projectBody = enabledProject) { + return MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${projectBody.slug}/`, + body: projectBody, + }); + } + beforeEach(() => { MockApiClient.clearMockResponses(); localStorage.clear(); + mockDetailedProject(); + MockApiClient.addMockResponse({ url: `/projects/${organization.slug}/${project.slug}/seer/preferences/`, body: { @@ -209,6 +223,7 @@ describe('ClaudeCodeIntegrationCta', () => { seerScannerAutomation: true, autofixAutomationTuning: 'medium', }); + mockDetailedProject(projectWithAutomation); const projectUpdateMock = MockApiClient.addMockResponse({ url: `/projects/${organization.slug}/${projectWithAutomation.slug}/`, @@ -256,6 +271,7 @@ describe('ClaudeCodeIntegrationCta', () => { seerScannerAutomation: false, autofixAutomationTuning: 'off', }); + mockDetailedProject(projectWithoutAutomation); const updatedProject = { ...projectWithoutAutomation, @@ -370,6 +386,7 @@ describe('ClaudeCodeIntegrationCta', () => { seerScannerAutomation: false, autofixAutomationTuning: 'off', }); + mockDetailedProject(projectWithoutAutomation); render(, { organization, @@ -423,6 +440,7 @@ describe('ClaudeCodeIntegrationCta', () => { seerScannerAutomation: true, autofixAutomationTuning: 'medium', }); + mockDetailedProject(projectWithAutomation); render(, { organization, @@ -440,6 +458,7 @@ describe('ClaudeCodeIntegrationCta', () => { seerScannerAutomation: true, autofixAutomationTuning: 'medium', }); + mockDetailedProject(projectWithAutomation); render(, { organization, diff --git a/static/app/components/events/autofix/codingAgentIntegrationCta.tsx b/static/app/components/events/autofix/codingAgentIntegrationCta.tsx index 17db18b18b0ef7..85ec4b9d785a02 100644 --- a/static/app/components/events/autofix/codingAgentIntegrationCta.tsx +++ b/static/app/components/events/autofix/codingAgentIntegrationCta.tsx @@ -14,6 +14,7 @@ import {t, tct} from 'sentry/locale'; import {PluginIcon} from 'sentry/plugins/components/pluginIcon'; import type {Project} from 'sentry/types/project'; import {trackAnalytics} from 'sentry/utils/analytics'; +import {useDetailedProject} from 'sentry/utils/project/useDetailedProject'; import {useUpdateProject} from 'sentry/utils/project/useUpdateProject'; import {useOrganization} from 'sentry/utils/useOrganization'; import {useUser} from 'sentry/utils/useUser'; @@ -40,6 +41,16 @@ export function makeCodingAgentIntegrationCta(config: AgentConfig) { const organization = useOrganization(); const user = useUser(); + const hasFeatureFlag = + !config.featureFlag || organization.features.includes(config.featureFlag); + const {data: projectDetails = project, isPending: isLoadingProject} = + useDetailedProject( + { + orgSlug: organization.slug, + projectSlug: project.slug, + }, + {enabled: hasFeatureFlag} + ); const {data, isFetching: isLoadingPreferences} = useProjectSeerPreferences(project); const preference = data?.preference; const {mutate: updateProjectSeerPreferences, isPending: isUpdatingPreferences} = @@ -53,12 +64,10 @@ export function makeCodingAgentIntegrationCta(config: AgentConfig) { i => i.provider === config.provider ); - const hasFeatureFlag = - !config.featureFlag || organization.features.includes(config.featureFlag); const hasIntegration = Boolean(integration); const isAutomationEnabled = - project.seerScannerAutomation !== false && - project.autofixAutomationTuning !== 'off'; + projectDetails.seerScannerAutomation === true && + projectDetails.autofixAutomationTuning !== 'off'; const isConfigured = preference?.automation_handoff?.target === config.target && isAutomationEnabled; @@ -86,8 +95,8 @@ export function makeCodingAgentIntegrationCta(config: AgentConfig) { }); const isAutomationDisabled = - project.seerScannerAutomation === false || - project.autofixAutomationTuning === 'off'; + projectDetails.seerScannerAutomation !== true || + projectDetails.autofixAutomationTuning === 'off'; if (isAutomationDisabled) { await updateProjectAutomation({ @@ -111,7 +120,12 @@ export function makeCodingAgentIntegrationCta(config: AgentConfig) { return null; } - if (isLoadingPreferences || isLoadingIntegrations || isUpdatingPreferences) { + if ( + isLoadingProject || + isLoadingPreferences || + isLoadingIntegrations || + isUpdatingPreferences + ) { return ( { const project = ProjectFixture(); + const enabledProject = ProjectFixture({ + ...project, + seerScannerAutomation: true, + autofixAutomationTuning: 'medium', + }); const organization = OrganizationFixture(); beforeEach(() => { MockApiClient.clearMockResponses(); localStorage.clear(); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${enabledProject.slug}/`, + body: enabledProject, + }); + // Default mock for seer preferences MockApiClient.addMockResponse({ url: `/projects/${organization.slug}/${project.slug}/seer/preferences/`, @@ -187,6 +197,10 @@ describe('CursorIntegrationCta', () => { seerScannerAutomation: false, autofixAutomationTuning: 'off', }); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${projectWithoutAutomation.slug}/`, + body: projectWithoutAutomation, + }); const updatedProject = { ...projectWithoutAutomation, @@ -271,6 +285,10 @@ describe('CursorIntegrationCta', () => { seerScannerAutomation: true, autofixAutomationTuning: 'medium', }); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${projectWithAutomation.slug}/`, + body: projectWithAutomation, + }); const projectUpdateMock = MockApiClient.addMockResponse({ url: `/projects/${organization.slug}/${projectWithAutomation.slug}/`, @@ -353,6 +371,10 @@ describe('CursorIntegrationCta', () => { seerScannerAutomation: false, autofixAutomationTuning: 'off', }); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${projectWithoutAutomation.slug}/`, + body: projectWithoutAutomation, + }); render(, { organization, @@ -409,6 +431,10 @@ describe('CursorIntegrationCta', () => { seerScannerAutomation: true, autofixAutomationTuning: 'medium', }); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${projectWithAutomation.slug}/`, + body: projectWithAutomation, + }); render(, { organization, @@ -426,6 +452,10 @@ describe('CursorIntegrationCta', () => { seerScannerAutomation: true, autofixAutomationTuning: 'medium', }); + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${projectWithAutomation.slug}/`, + body: projectWithAutomation, + }); render(, { organization, diff --git a/static/app/views/issueDetails/streamline/sidebar/seerNotices.tsx b/static/app/views/issueDetails/streamline/sidebar/seerNotices.tsx index 1f6ca51359b939..20289dd5e49455 100644 --- a/static/app/views/issueDetails/streamline/sidebar/seerNotices.tsx +++ b/static/app/views/issueDetails/streamline/sidebar/seerNotices.tsx @@ -101,7 +101,7 @@ export function SeerNotices({groupId, hasGithubIntegration, project}: SeerNotice ); const {starredViews: views} = useStarredIssueViews(); - const detailedProject = useDetailedProject({ + const {data: projectDetails} = useDetailedProject({ orgSlug: organization.slug, projectSlug: project.slug, }); @@ -125,11 +125,11 @@ export function SeerNotices({groupId, hasGithubIntegration, project}: SeerNotice const needsRepoSelection = repos.length === 0 && !preference?.repositories?.length && !codeMappingRepos?.length; const needsAutomation = - detailedProject?.data && - (detailedProject?.data?.autofixAutomationTuning === 'off' || - detailedProject?.data?.autofixAutomationTuning === undefined || - detailedProject?.data?.seerScannerAutomation === false || - detailedProject?.data?.seerScannerAutomation === undefined); + projectDetails !== undefined && + (projectDetails.autofixAutomationTuning === 'off' || + projectDetails.autofixAutomationTuning === undefined || + projectDetails.seerScannerAutomation === false || + projectDetails.seerScannerAutomation === undefined); const needsFixabilityView = !views.some(view => view.query.includes(FieldKey.ISSUE_SEER_ACTIONABILITY)) && isStarredViewAllowed; @@ -166,8 +166,8 @@ export function SeerNotices({groupId, hasGithubIntegration, project}: SeerNotice } const isAutomationDisabled = - project.seerScannerAutomation === false || - project.autofixAutomationTuning === 'off'; + projectDetails?.seerScannerAutomation !== true || + projectDetails.autofixAutomationTuning === 'off'; if (isAutomationDisabled) { await updateProjectAutomation({ diff --git a/static/app/views/settings/projectSeer/index.spec.tsx b/static/app/views/settings/projectSeer/index.spec.tsx index 0ec61e7ae03e2c..ba34262fdbd3a9 100644 --- a/static/app/views/settings/projectSeer/index.spec.tsx +++ b/static/app/views/settings/projectSeer/index.spec.tsx @@ -38,6 +38,10 @@ describe('ProjectSeer', () => { beforeEach(() => { project = ProjectFixture(); organization = OrganizationFixture(); + MockApiClient.addMockResponse({ + url: `/projects/org-slug/${project.slug}/`, + body: project, + }); // Mock the seer setup check endpoint MockApiClient.addMockResponse({ @@ -716,6 +720,10 @@ describe('ProjectSeer', () => { autofixAutomationTuning: 'medium', seerScannerAutomation: true, }; + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/`, + body: initialProject, + }); MockApiClient.addMockResponse({ url: `/organizations/${orgWithCursorFeature.slug}/seer/setup-check/`, @@ -795,6 +803,10 @@ describe('ProjectSeer', () => { autofixAutomationTuning: 'medium', seerScannerAutomation: true, }; + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/`, + body: initialProject, + }); MockApiClient.addMockResponse({ url: `/organizations/${orgWithCursorFeature.slug}/seer/setup-check/`, @@ -903,6 +915,10 @@ describe('ProjectSeer', () => { autofixAutomationTuning: 'medium', seerScannerAutomation: true, }; + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/`, + body: initialProject, + }); MockApiClient.addMockResponse({ url: `/organizations/${orgWithCursorFeature.slug}/seer/setup-check/`, @@ -993,6 +1009,10 @@ describe('ProjectSeer', () => { autofixAutomationTuning: 'medium', seerScannerAutomation: true, }; + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/`, + body: initialProject, + }); MockApiClient.addMockResponse({ url: `/organizations/${orgWithCursorFeature.slug}/seer/setup-check/`, @@ -1116,6 +1136,10 @@ describe('ProjectSeer', () => { autofixAutomationTuning: 'medium', seerScannerAutomation: true, }; + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/`, + body: initialProject, + }); MockApiClient.addMockResponse({ url: `/organizations/${orgWithBothFeatures.slug}/seer/setup-check/`, @@ -1198,6 +1222,10 @@ describe('ProjectSeer', () => { autofixAutomationTuning: 'medium', seerScannerAutomation: true, }; + MockApiClient.addMockResponse({ + url: `/projects/${organization.slug}/${project.slug}/`, + body: initialProject, + }); MockApiClient.addMockResponse({ url: `/organizations/${orgWithCursorFeature.slug}/seer/setup-check/`,