diff --git a/static/app/components/events/autofix/useExplorerAutofix.tsx b/static/app/components/events/autofix/useExplorerAutofix.tsx index 827724a5601e..eff87ced0101 100644 --- a/static/app/components/events/autofix/useExplorerAutofix.tsx +++ b/static/app/components/events/autofix/useExplorerAutofix.tsx @@ -29,10 +29,10 @@ import {trackAnalytics} from 'sentry/utils/analytics'; import {apiOptions} from 'sentry/utils/api/apiOptions'; import {getApiUrl} from 'sentry/utils/api/getApiUrl'; import {defined} from 'sentry/utils/defined'; -import {normalizeUrl} from 'sentry/utils/url/normalizeUrl'; import {useApi} from 'sentry/utils/useApi'; import {useOrganization} from 'sentry/utils/useOrganization'; import {useUser} from 'sentry/utils/useUser'; +import {makeSeerPathname, makeSeerQuery} from 'sentry/views/issueDetails/autofix/utils'; import {groupQueryKey} from 'sentry/views/issueDetails/useGroup'; import { isArtifact, @@ -866,12 +866,8 @@ export function useExplorerAutofix( issueId: groupId, notification: { navigateTo: { - pathname: normalizeUrl( - `/organizations/${orgSlug}/issues/${groupId}/` - ), - query: { - seerDrawer: 'true', - }, + pathname: makeSeerPathname(organization, groupId), + query: makeSeerQuery(organization), }, project: { avatar: 'https://sentry.io/favicon.ico', // TODO(ryan953): Use the project avatar url or base64 encoded bytes @@ -942,15 +938,7 @@ export function useExplorerAutofix( throw e; } }, - [ - api, - group.shortId, - groupId, - orgSlug, - organization.features, - queryClient, - serviceWorker, - ] + [api, group.shortId, groupId, orgSlug, organization, queryClient, serviceWorker] ); /** diff --git a/static/app/components/events/autofix/v3/drawer.tsx b/static/app/components/events/autofix/v3/drawer.tsx index 8bbba1df0456..483c1cc6ad1e 100644 --- a/static/app/components/events/autofix/v3/drawer.tsx +++ b/static/app/components/events/autofix/v3/drawer.tsx @@ -1,36 +1,14 @@ -import {Fragment, useCallback, useMemo} from 'react'; +import {Stack} from '@sentry/scraps/layout'; -import {Alert} from '@sentry/scraps/alert'; -import {Button, LinkButton} from '@sentry/scraps/button'; -import {Flex, Stack} from '@sentry/scraps/layout'; -import {useModal} from '@sentry/scraps/modal'; - -import {AutofixGithubAppPermissionsModal} from 'sentry/components/events/autofix/autofixGithubAppPermissionsModal'; -import {getReferrerFromBlocks} from 'sentry/components/events/autofix/autofixReferrer'; -import {getAutofixRunId} from 'sentry/components/events/autofix/autofixRunId'; -import { - getAutofixArtifactFromSection, - getOrderedAutofixSections, - useExplorerAutofix, - type AutofixExplorerStep, -} from 'sentry/components/events/autofix/useExplorerAutofix'; import {SeerDrawerBody} from 'sentry/components/events/autofix/v3/body'; import {SeerDrawerContent} from 'sentry/components/events/autofix/v3/content'; import {SeerDrawerHeader} from 'sentry/components/events/autofix/v3/header'; -import {useForceBashMode} from 'sentry/components/events/autofix/v3/useForceBashMode'; -import {artifactToMarkdown} from 'sentry/components/events/autofix/v3/utils'; +import {useSeerPanel} from 'sentry/components/events/autofix/v3/useSeerPanel'; +import {AutofixWarnings} from 'sentry/components/events/autofix/v3/warnings'; import {Placeholder} from 'sentry/components/placeholder'; -import {IconClose} from 'sentry/icons'; -import {t, tct} from 'sentry/locale'; import type {Group} from 'sentry/types/group'; import type {Project} from 'sentry/types/project'; -import {defined} from 'sentry/utils/defined'; import {useAutoScroll} from 'sentry/utils/useAutoScroll'; -import {useCopyToClipboard} from 'sentry/utils/useCopyToClipboard'; -import {useDismissAlert} from 'sentry/utils/useDismissAlert'; -import {useOrganization} from 'sentry/utils/useOrganization'; -import {useAiConfig} from 'sentry/views/issueDetails/hooks/useAiConfig'; -import {useSeerExplorerDrawer} from 'sentry/views/seerExplorer/components/drawer/useSeerExplorerDrawer'; interface SeerDrawerProps { group: Group; @@ -38,41 +16,19 @@ interface SeerDrawerProps { } export function SeerDrawer({group, project}: SeerDrawerProps) { - const organization = useOrganization(); - const aiConfig = useAiConfig(group, project); - const aiAutofix = useExplorerAutofix(group, { - // Automated CI iteration pushes commits with no user action, so poll for both. - pollPR: - organization.features.includes('autofix-pr-iteration') || - organization.features.includes('autofix-pr-iteration-manual'), - }); - const [enableBashTools, setEnableBashTools] = useForceBashMode(); - - const autofix = useMemo( - () => ({ - ...aiAutofix, - startStep: ( - step: AutofixExplorerStep, - options?: Parameters['startStep']>[1] - ) => - aiAutofix.startStep(step, { - ...options, - enableBashTools: enableBashTools || undefined, - }), - }), - [aiAutofix, enableBashTools] - ); - - const handleCopyMarkdown = useHandleCopyMarkdown({aiAutofix: autofix}); - const handleRestart = useHandleRestart({aiAutofix: autofix}); - const handleOpenSeerAgent = useHandleOpenSeerAgent({aiAutofix: autofix}); - - const referrer = useMemo( - () => getReferrerFromBlocks(aiAutofix.runState?.blocks ?? []), - [aiAutofix.runState?.blocks] - ); - - const {containerRef, onScrollHandler} = useAutoScroll({key: aiAutofix.runState}); + const { + aiConfig, + autofix, + enableBashTools, + handleCopyMarkdown, + handleOpenSeerAgent, + handleRestart, + referrer, + runState, + setEnableBashTools, + warnings, + } = useSeerPanel({group, project}); + const {containerRef, onScrollHandler} = useAutoScroll({key: runState}); return ( - + {aiConfig.isAutofixSetupLoading ? ( @@ -106,179 +62,3 @@ export function SeerDrawer({group, project}: SeerDrawerProps) { ); } - -function useHandleCopyMarkdown({ - aiAutofix, -}: { - aiAutofix: ReturnType; -}): (() => void) | undefined { - const {copy} = useCopyToClipboard(); - - return useMemo(() => { - if (!aiAutofix.runState) { - return; - } - - return () => { - const markdown = getOrderedAutofixSections(aiAutofix.runState) - .map(getAutofixArtifactFromSection) - .filter(defined) - .map(artifact => artifactToMarkdown(artifact)) - .filter(defined) - .join('\n\n'); - copy(markdown, {successMessage: t('Analysis copied to clipboard.')}); - }; - }, [aiAutofix, copy]); -} - -function useHandleRestart({ - aiAutofix, -}: { - aiAutofix: ReturnType; -}): () => void { - const {startStep} = aiAutofix; - - return useCallback(() => { - startStep('root_cause'); - }, [startStep]); -} - -function useHandleOpenSeerAgent({ - aiAutofix, -}: { - aiAutofix: ReturnType; -}): (() => void) | undefined { - const {openSeerExplorerDrawer} = useSeerExplorerDrawer(); - const runId = getAutofixRunId(aiAutofix.runState); - - return useMemo(() => { - if (!defined(runId)) { - return; - } - return () => openSeerExplorerDrawer({runId}); - }, [openSeerExplorerDrawer, runId]); -} - -type AutofixWarning = { - warning_type: string; - installation_id?: string; - installation_url?: string; - repo_name?: string; -}; - -function InstallationPermissionsButton({installationUrl}: {installationUrl?: string}) { - const {openModal} = useModal(); - return ( - - ); -} - -function ConfigurationPermissionsButton() { - const organization = useOrganization(); - const configurationUrl = `/settings/${organization.slug}/integrations/github/?tab=configurations`; - - return ( - - {t('Update Permissions')} - - ); -} - -export function AutofixWarnings({ - warnings, - groupId, -}: { - groupId: string; - warnings: AutofixWarning[]; -}) { - const organization = useOrganization(); - const {dismiss, isDismissed} = useDismissAlert({ - key: `${organization.id}:${groupId}:autofix-github-permissions-warning`, - expirationDays: 7, - }); - - if (!warnings.length || isDismissed) { - return null; - } - - const permissionWarnings = warnings.filter( - w => w.warning_type === 'github_app_permissions' - ); - - if (!permissionWarnings.length) { - return null; - } - - const installationIds = [ - ...new Set(permissionWarnings.map(w => w.installation_id).filter(defined)), - ]; - const [installationId] = installationIds; - - const comp = - installationIds.length === 1 && defined(installationId) ? ( - w.installation_id === installationId) - ?.installation_url - } - /> - ) : ( - - ); - - const repoNames = [ - ...new Set(permissionWarnings.map(w => w.repo_name).filter(defined)), - ]; - - const repoNamesNode = repoNames.map((repoName, index) => ( - - {index > 0 && ', '} - {repoName} - - )); - - return ( - - - {comp} - + ); +} + +function ConfigurationPermissionsButton() { + const organization = useOrganization(); + const configurationUrl = `/settings/${organization.slug}/integrations/github/?tab=configurations`; + + return ( + + {t('Update Permissions')} + + ); +} + +export function AutofixWarnings({ + warnings, + groupId, +}: { + groupId: string; + warnings: AutofixWarning[]; +}) { + const organization = useOrganization(); + const {dismiss, isDismissed} = useDismissAlert({ + key: `${organization.id}:${groupId}:autofix-github-permissions-warning`, + expirationDays: 7, + }); + + if (!warnings.length || isDismissed) { + return null; + } + + const permissionWarnings = warnings.filter( + w => w.warning_type === 'github_app_permissions' + ); + + if (!permissionWarnings.length) { + return null; + } + + const installationIds = [ + ...new Set(permissionWarnings.map(w => w.installation_id).filter(defined)), + ]; + const [installationId] = installationIds; + + const comp = + installationIds.length === 1 && defined(installationId) ? ( + w.installation_id === installationId) + ?.installation_url + } + /> + ) : ( + + ); + + const repoNames = [ + ...new Set(permissionWarnings.map(w => w.repo_name).filter(defined)), + ]; + + const repoNamesNode = repoNames.map((repoName, index) => ( + + {index > 0 && ', '} + {repoName} + + )); + + return ( + + + {comp} +