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,