import {useEffect, useMemo} from 'react'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; import StackTraceContent from 'sentry/components/events/interfaces/crashContent/stackTrace/content'; import {HierarchicalGroupingContent} from 'sentry/components/events/interfaces/crashContent/stackTrace/hierarchicalGroupingContent'; import {NativeContent} from 'sentry/components/events/interfaces/crashContent/stackTrace/nativeContent'; import findBestThread from 'sentry/components/events/interfaces/threads/threadSelector/findBestThread'; import getThreadStacktrace from 'sentry/components/events/interfaces/threads/threadSelector/getThreadStacktrace'; import {isStacktraceNewestFirst} from 'sentry/components/events/interfaces/utils'; import {GroupPreviewHovercard} from 'sentry/components/groupPreviewTooltip/groupPreviewHovercard'; import { useDelayedLoadingState, usePreviewEvent, } from 'sentry/components/groupPreviewTooltip/utils'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {PlatformKey} from 'sentry/types'; import {EntryType, Event} from 'sentry/types/event'; import {StacktraceType} from 'sentry/types/stacktrace'; import {defined} from 'sentry/utils'; import {isNativePlatform} from 'sentry/utils/platform'; import useOrganization from 'sentry/utils/useOrganization'; export function getStacktrace(event: Event): StacktraceType | null { const exceptionsWithStacktrace = event.entries .find(e => e.type === EntryType.EXCEPTION) ?.data?.values.filter(({stacktrace}) => defined(stacktrace)) ?? []; const exceptionStacktrace: StacktraceType | undefined = isStacktraceNewestFirst() ? exceptionsWithStacktrace[exceptionsWithStacktrace.length - 1]?.stacktrace : exceptionsWithStacktrace[0]?.stacktrace; if (exceptionStacktrace) { return exceptionStacktrace; } const threads = event.entries.find(e => e.type === EntryType.THREADS)?.data?.values ?? []; const bestThread = findBestThread(threads); if (!bestThread) { return null; } const bestThreadStacktrace = getThreadStacktrace(false, bestThread); if (bestThreadStacktrace) { return bestThreadStacktrace; } return null; } export function StackTracePreviewContent({ event, stacktrace, orgFeatures = [], groupingCurrentLevel, }: { event: Event; stacktrace: StacktraceType; groupingCurrentLevel?: number; orgFeatures?: string[]; }) { const includeSystemFrames = useMemo(() => { return stacktrace?.frames?.every(frame => !frame.inApp) ?? false; }, [stacktrace]); const framePlatform = stacktrace?.frames?.find(frame => !!frame.platform)?.platform; const platform = (framePlatform ?? event.platform ?? 'other') as PlatformKey; const newestFirst = isStacktraceNewestFirst(); const commonProps = { data: stacktrace, expandFirstFrame: false, includeSystemFrames, platform, newestFirst, event, isHoverPreviewed: true, }; if (isNativePlatform(platform)) { return ; } if (orgFeatures.includes('grouping-stacktrace-ui')) { return ( ); } return ; } type StackTracePreviewProps = { children: React.ReactChild; groupId: string; eventId?: string; groupingCurrentLevel?: number; projectSlug?: string; query?: string; }; interface StackTracePreviewBodyProps extends Pick< StackTracePreviewProps, 'groupId' | 'eventId' | 'groupingCurrentLevel' | 'projectSlug' | 'query' > { onRequestBegin: () => void; onRequestEnd: () => void; onUnmount: () => void; } function StackTracePreviewBody({ groupId, groupingCurrentLevel, onRequestBegin, onRequestEnd, onUnmount, query, }: StackTracePreviewBodyProps) { const organization = useOrganization(); const {data, isLoading, isError} = usePreviewEvent({groupId, query}); useEffect(() => { if (isLoading) { onRequestBegin(); } else { onRequestEnd(); } return onUnmount; }, [isLoading, onRequestBegin, onRequestEnd, onUnmount]); const stacktrace = useMemo(() => (data ? getStacktrace(data) : null), [data]); if (isLoading) { return ( ); } if (isError) { return {t('Failed to load stack trace.')}; } if (stacktrace && data) { return ( ); } return ( {t('There is no stack trace available for this issue.')} ); } function StackTracePreview({children, ...props}: StackTracePreviewProps) { const organization = useOrganization(); const {shouldShowLoadingState, onRequestBegin, onRequestEnd, reset} = useDelayedLoadingState(); const hasGroupingStacktraceUI = organization.features.includes( 'grouping-stacktrace-ui' ); return ( } > {children} ); } export {StackTracePreview}; const Wrapper = styled('span')<{ hasGroupingStacktraceUI: boolean; }>` ${p => p.hasGroupingStacktraceUI && css` display: inline-flex; overflow: hidden; height: 100%; > span:first-child { ${p.theme.overflowEllipsis} } `} `; const StackTracePreviewWrapper = styled('div')` width: 700px; .traceback { margin-bottom: 0; border: 0; } `; const NoStackTraceWrapper = styled('div')` color: ${p => p.theme.subText}; padding: ${space(1.5)}; font-size: ${p => p.theme.fontSizeMedium}; display: flex; align-items: center; justify-content: center; min-height: 56px; `;