diff --git a/static/app/components/events/autofix/autofixSetupModal.tsx b/static/app/components/events/autofix/autofixSetupModal.tsx index d8ba9cd5e3a3fa..c51f74d671f429 100644 --- a/static/app/components/events/autofix/autofixSetupModal.tsx +++ b/static/app/components/events/autofix/autofixSetupModal.tsx @@ -8,7 +8,6 @@ import { useAutofixSetup, } from 'sentry/components/events/autofix/useAutofixSetup'; import {GuidedSteps} from 'sentry/components/guidedSteps/guidedSteps'; -import HookOrDefault from 'sentry/components/hookOrDefault'; import ExternalLink from 'sentry/components/links/externalLink'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; @@ -18,11 +17,6 @@ import {space} from 'sentry/styles/space'; import {trackAnalytics} from 'sentry/utils/analytics'; import useOrganization from 'sentry/utils/useOrganization'; -const ConsentStep = HookOrDefault({ - hookName: 'component:autofix-setup-step-consent', - defaultComponent: null, -}); - function AutofixIntegrationStep({autofixSetup}: {autofixSetup: AutofixSetupResponse}) { if (autofixSetup.integration.ok) { return ( @@ -283,7 +277,6 @@ function AutofixSetupSteps({ }) { return ( - +
Set up Autofix

Sentry's AI-enabled Autofix uses all of the contextual data surrounding this error @@ -401,3 +395,8 @@ const GithubLink = styled('div')` align-items: center; gap: ${space(0.5)}; `; + +const Divider = styled('div')` + margin: ${space(3)} 0; + border-bottom: 2px solid ${p => p.theme.gray100}; +`; diff --git a/static/app/types/hooks.tsx b/static/app/types/hooks.tsx index 5c5157fe25aa43..19760514b485fa 100644 --- a/static/app/types/hooks.tsx +++ b/static/app/types/hooks.tsx @@ -72,6 +72,9 @@ export type RouteHooks = { * Component specific hooks for DateRange and SelectorItems * These components have plan specific overrides in getsentry */ +type AiSetupDataConsentProps = { + groupId: string; +}; type AutofixSetupConsentStepProps = {hasConsented: boolean}; type DateRangeProps = React.ComponentProps; @@ -184,6 +187,7 @@ export type MembershipSettingsProps = { * Component wrapping hooks */ export type ComponentHooks = { + 'component:ai-setup-data-consent': () => React.ComponentType | null; 'component:autofix-setup-step-consent': () => React.ComponentType | null; 'component:codecov-integration-settings-link': () => React.ComponentType; 'component:confirm-account-close': () => React.ComponentType; diff --git a/static/app/views/issueDetails/streamline/solutionsHubDrawer.spec.tsx b/static/app/views/issueDetails/streamline/solutionsHubDrawer.spec.tsx index e10948c9a082e7..91aca082b11a12 100644 --- a/static/app/views/issueDetails/streamline/solutionsHubDrawer.spec.tsx +++ b/static/app/views/issueDetails/streamline/solutionsHubDrawer.spec.tsx @@ -6,13 +6,19 @@ import {GroupFixture} from 'sentry-fixture/group'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {ProjectFixture} from 'sentry-fixture/project'; -import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary'; +import { + render, + screen, + userEvent, + waitFor, + waitForElementToBeRemoved, +} from 'sentry-test/reactTestingLibrary'; import {t} from 'sentry/locale'; import {EntryType} from 'sentry/types/event'; import {SolutionsHubDrawer} from 'sentry/views/issueDetails/streamline/solutionsHubDrawer'; -describe('AutofixDrawer', () => { +describe('SolutionsHubDrawer', () => { const organization = OrganizationFixture({genAIConsent: true, hideAiFeatures: false}); const mockEvent = EventFixture({ @@ -51,10 +57,18 @@ describe('AutofixDrawer', () => { }); }); - it('renders properly', () => { + it('renders consent state if not consented', async () => { + MockApiClient.addMockResponse({ + url: `/issues/${mockGroup.id}/autofix/setup/`, + body: { + genAIConsent: {ok: false}, + integration: {ok: false}, + githubWriteIntegration: {ok: false}, + }, + }); MockApiClient.addMockResponse({ url: `/issues/${mockGroup.id}/autofix/`, - body: {autofix: mockAutofixData}, + body: {autofix: null}, }); render( @@ -62,7 +76,35 @@ describe('AutofixDrawer', () => { {organization} ); - expect(screen.getByText(mockGroup.shortId)).toBeInTheDocument(); + expect(screen.getByTestId('ai-setup-loading-indicator')).toBeInTheDocument(); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('ai-setup-loading-indicator') + ); + + expect(screen.getByText(mockEvent.id)).toBeInTheDocument(); + + expect(screen.getByRole('heading', {name: 'Solutions Hub'})).toBeInTheDocument(); + + expect(screen.getByTestId('ai-setup-data-consent')).toBeInTheDocument(); + }); + + it('renders initial state correctly', async () => { + 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.getByText(mockEvent.id)).toBeInTheDocument(); @@ -89,6 +131,12 @@ describe('AutofixDrawer', () => { {organization} ); + expect(screen.getByTestId('ai-setup-loading-indicator')).toBeInTheDocument(); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('ai-setup-loading-indicator') + ); + const startButton = screen.getByRole('button', {name: 'Start Autofix'}); await userEvent.click(startButton); diff --git a/static/app/views/issueDetails/streamline/solutionsHubDrawer.tsx b/static/app/views/issueDetails/streamline/solutionsHubDrawer.tsx index bb73f27ebe7987..0fb1169df10883 100644 --- a/static/app/views/issueDetails/streamline/solutionsHubDrawer.tsx +++ b/static/app/views/issueDetails/streamline/solutionsHubDrawer.tsx @@ -15,7 +15,9 @@ import {useAiAutofix} from 'sentry/components/events/autofix/useAutofix'; import {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'; import Input from 'sentry/components/input'; +import LoadingIndicator from 'sentry/components/loadingIndicator'; import {IconDocs, IconSeer} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; @@ -88,6 +90,7 @@ function AutofixStartBox({onSend, groupId}: AutofixStartBoxProps) { : 'Autofix: Start Fix Clicked' } analyticsParams={{group_id: groupId}} + aria-label="Start Autofix" > {t('Start Autofix')} @@ -133,8 +136,13 @@ interface SolutionsHubDrawerProps { project: Project; } +const AiSetupDataConsent = HookOrDefault({ + hookName: 'component:ai-setup-data-consent', + defaultComponent: () =>

, +}); + export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerProps) { - const {autofixData, triggerAutofix, reset, isPolling} = useAiAutofix(group, event); + const {autofixData, triggerAutofix, reset} = useAiAutofix(group, event); const { data: summaryData, isError, @@ -154,11 +162,14 @@ export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerPr const config = getConfigForIssueType(group, project); - const isSetupComplete = setupData?.integration.ok && setupData?.genAIConsent.ok; - const hasSummary = summaryData && !isError && setupData?.genAIConsent.ok; + const hasConsent = Boolean(setupData?.genAIConsent.ok); + const isAutofixSetupComplete = setupData?.integration.ok && hasConsent; + + const hasSummary = summaryData && !isError && hasConsent; const organization = useOrganization(); const isSampleError = useIsSampleEvent(); + const displayAiAutofix = shouldDisplayAiAutofixForOrganization(organization) && config.autofix && @@ -234,35 +245,43 @@ export function SolutionsHubDrawer({group, project, event}: SolutionsHubDrawerPr )} - {hasSummary && ( - - - - )} - {displayAiAutofix && ( + {isSetupLoading ? ( +
+ +
+ ) : !hasConsent ? ( + + ) : ( - {!isSetupLoading && !isSetupComplete ? ( - - + - - ) : !autofixData && isPolling ? ( - - ) : autofixData ? ( - - ) : null} + + )} + {displayAiAutofix && ( + + {!isAutofixSetupComplete ? ( + + ) : !autofixData ? ( + + ) : ( + + )} + + )} )} @@ -392,16 +411,6 @@ const StarLarge3 = styled(StarLarge)` height: 28px; `; -const SetupContainer = styled('div')` - padding: ${space(2)}; - - /* Override some modal-specific styles */ - h3 { - font-size: ${p => p.theme.fontSizeLarge}; - margin-bottom: ${space(2)}; - } -`; - const StyledCard = styled('div')` background: ${p => p.theme.backgroundElevated}; border-radius: ${p => p.theme.borderRadius};