diff --git a/static/app/components/events/autofix/autofixSetupModal.tsx b/static/app/components/events/autofix/autofixSetupModal.tsx index c51f74d671f429..76ce461ddf8f69 100644 --- a/static/app/components/events/autofix/autofixSetupModal.tsx +++ b/static/app/components/events/autofix/autofixSetupModal.tsx @@ -179,8 +179,10 @@ function AutofixGithubIntegrationStep({ size="sm" disabled={!canStartAutofix} onClick={handleClose} + analyticsEventName="Autofix Setup Enable Autofix" + analyticsEventKey="autofix.setup_enable_autofix" > - {t("Let's Go!")} + {t('Enable Autofix')} )} @@ -255,6 +257,8 @@ function AutofixGithubIntegrationStep({ size="sm" disabled={!canStartAutofix} onClick={handleClose} + analyticsEventName="Autofix Setup Skip & Enable Autofix" + analyticsEventKey="autofix.setup_skip_enable_autofix" > {t('Skip & Enable Autofix')} diff --git a/static/app/components/events/autofix/useAutofixSetup.tsx b/static/app/components/events/autofix/useAutofixSetup.tsx index 23dc3204677301..4b838459aab947 100644 --- a/static/app/components/events/autofix/useAutofixSetup.tsx +++ b/static/app/components/events/autofix/useAutofixSetup.tsx @@ -11,6 +11,9 @@ export interface AutofixSetupRepoDefinition extends AutofixRepoDefinition { } export type AutofixSetupResponse = { + autofixEnabled: { + ok: boolean; + }; genAIConsent: { ok: boolean; }; diff --git a/static/app/types/organization.tsx b/static/app/types/organization.tsx index ff84ef8761b917..22f4a76ad98d68 100644 --- a/static/app/types/organization.tsx +++ b/static/app/types/organization.tsx @@ -19,6 +19,7 @@ import type {User} from './user'; */ export interface OrganizationSummary { aiSuggestedSolution: boolean; + autofixEnabled: boolean; avatar: Avatar; codecovAccess: boolean; dateCreated: string; diff --git a/static/app/views/issueDetails/streamline/solutionsHubDrawer.spec.tsx b/static/app/views/issueDetails/streamline/solutionsHubDrawer.spec.tsx index 91aca082b11a12..9553b456b3293c 100644 --- a/static/app/views/issueDetails/streamline/solutionsHubDrawer.spec.tsx +++ b/static/app/views/issueDetails/streamline/solutionsHubDrawer.spec.tsx @@ -43,6 +43,7 @@ describe('SolutionsHubDrawer', () => { genAIConsent: {ok: true}, integration: {ok: true}, githubWriteIntegration: {ok: true}, + autofixEnabled: {ok: true}, }, }); MockApiClient.addMockResponse({ @@ -64,6 +65,7 @@ describe('SolutionsHubDrawer', () => { genAIConsent: {ok: false}, integration: {ok: false}, githubWriteIntegration: {ok: false}, + autofixEnabled: {ok: false}, }, }); MockApiClient.addMockResponse({ @@ -106,8 +108,6 @@ describe('SolutionsHubDrawer', () => { screen.queryByTestId('ai-setup-loading-indicator') ); - expect(screen.getByText(mockEvent.id)).toBeInTheDocument(); - expect(screen.getByRole('heading', {name: 'Solutions Hub'})).toBeInTheDocument(); const startButton = screen.getByRole('button', {name: 'Start Autofix'}); @@ -180,4 +180,39 @@ describe('SolutionsHubDrawer', () => { expect(screen.getByRole('button', {name: 'Start Autofix'})).toBeInTheDocument(); }); }); + + it('continues to show setup if autofix is not enabled', async () => { + MockApiClient.addMockResponse({ + url: `/issues/${mockGroup.id}/autofix/setup/`, + body: { + genAIConsent: {ok: true}, + integration: {ok: true}, + githubWriteIntegration: {ok: false, repos: []}, + autofixEnabled: {ok: false}, + }, + }); + MockApiClient.addMockResponse({ + url: `/issues/${mockGroup.id}/autofix/`, + body: {autofix: null}, + }); + + render( + , + {organization} + ); + + expect(screen.getByTestId('ai-setup-loading-indicator')).toBeInTheDocument(); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('ai-setup-loading-indicator') + ); + + expect(screen.getByRole('heading', {name: 'Solutions Hub'})).toBeInTheDocument(); + + expect(screen.queryByRole('button', {name: 'Start Autofix'})).not.toBeInTheDocument(); + + expect( + screen.getByRole('button', {name: 'Skip & Enable Autofix'}) + ).toBeInTheDocument(); + }); }); diff --git a/static/app/views/issueDetails/streamline/solutionsHubDrawer.tsx b/static/app/views/issueDetails/streamline/solutionsHubDrawer.tsx index 0fb1169df10883..226896b0a7987d 100644 --- a/static/app/views/issueDetails/streamline/solutionsHubDrawer.tsx +++ b/static/app/views/issueDetails/streamline/solutionsHubDrawer.tsx @@ -12,7 +12,10 @@ import AutofixFeedback from 'sentry/components/events/autofix/autofixFeedback'; import {AutofixSetupContent} from 'sentry/components/events/autofix/autofixSetupModal'; import {AutofixSteps} from 'sentry/components/events/autofix/autofixSteps'; import {useAiAutofix} from 'sentry/components/events/autofix/useAutofix'; -import {useAutofixSetup} from 'sentry/components/events/autofix/useAutofixSetup'; +import { + makeAutofixSetupQueryKey, + useAutofixSetup, +} from 'sentry/components/events/autofix/useAutofixSetup'; import {DrawerBody, DrawerHeader} from 'sentry/components/globalDrawer/components'; import {GroupSummaryBody, useGroupSummary} from 'sentry/components/group/groupSummary'; import HookOrDefault from 'sentry/components/hookOrDefault'; @@ -30,8 +33,10 @@ import { getConfigForIssueType, shouldShowCustomErrorResourceConfig, } from 'sentry/utils/issueTypeConfig'; +import {useMutation, useQueryClient} from 'sentry/utils/queryClient'; import {getRegionDataFromOrganization} from 'sentry/utils/regions'; import useRouteAnalyticsParams from 'sentry/utils/routeAnalytics/useRouteAnalyticsParams'; +import useApi from 'sentry/utils/useApi'; import useOrganization from 'sentry/utils/useOrganization'; import {MIN_NAV_HEIGHT} from 'sentry/views/issueDetails/streamline/eventTitle'; import Resources from 'sentry/views/issueDetails/streamline/resources'; @@ -141,6 +146,26 @@ const AiSetupDataConsent = HookOrDefault({ defaultComponent: () =>
, }); +const useEnableAutofix = (groupId: string) => { + const api = useApi({persistInFlight: true}); + const queryClient = useQueryClient(); + + const organization = useOrganization(); + return useMutation({ + mutationFn: () => { + return api.requestPromise(`/organizations/${organization.slug}/`, { + method: 'PUT', + data: { + autofixEnabled: true, + }, + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({queryKey: makeAutofixSetupQueryKey(groupId)}); + }, + }); +}; + export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerProps) { const {autofixData, triggerAutofix, reset} = useAiAutofix(group, event); const { @@ -148,13 +173,10 @@ export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerPr isError, isPending: isSummaryLoading, } = useGroupSummary(group.id, group.issueCategory); - const { - data: setupData, - isPending: isSetupLoading, - refetch: refetchSetup, - } = useAutofixSetup({ + const {data: setupData, isPending: isSetupLoading} = useAutofixSetup({ groupId: group.id, }); + const enableAutofixMutation = useEnableAutofix(group.id); useRouteAnalyticsParams({ autofix_status: autofixData?.status ?? 'none', @@ -164,6 +186,7 @@ export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerPr const hasConsent = Boolean(setupData?.genAIConsent.ok); const isAutofixSetupComplete = setupData?.integration.ok && hasConsent; + const autofixEnabled = setupData?.autofixEnabled.ok; const hasSummary = summaryData && !isError && hasConsent; @@ -245,7 +268,7 @@ export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerPr )} - {isSetupLoading ? ( + {isSetupLoading || enableAutofixMutation.isPending ? (
@@ -264,11 +287,13 @@ export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerPr )} {displayAiAutofix && ( - {!isAutofixSetupComplete ? ( + {!isAutofixSetupComplete || !autofixEnabled ? ( { + enableAutofixMutation.mutate(); + }} /> ) : !autofixData ? ( diff --git a/tests/js/fixtures/organization.ts b/tests/js/fixtures/organization.ts index bf49d9a46215be..af4e29cc343987 100644 --- a/tests/js/fixtures/organization.ts +++ b/tests/js/fixtures/organization.ts @@ -64,6 +64,7 @@ export function OrganizationFixture( params: Partial = {}): Organi githubOpenPRBot: false, githubPRBot: false, hideAiFeatures: false, + autofixEnabled: false, isDefault: false, isDynamicallySampled: true, isEarlyAdopter: false,