stackTracePreview.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import {useEffect, useMemo} from 'react';
  2. import {css} from '@emotion/react';
  3. import styled from '@emotion/styled';
  4. import StackTraceContent from 'sentry/components/events/interfaces/crashContent/stackTrace/content';
  5. import {HierarchicalGroupingContent} from 'sentry/components/events/interfaces/crashContent/stackTrace/hierarchicalGroupingContent';
  6. import {NativeContent} from 'sentry/components/events/interfaces/crashContent/stackTrace/nativeContent';
  7. import findBestThread from 'sentry/components/events/interfaces/threads/threadSelector/findBestThread';
  8. import getThreadStacktrace from 'sentry/components/events/interfaces/threads/threadSelector/getThreadStacktrace';
  9. import {isStacktraceNewestFirst} from 'sentry/components/events/interfaces/utils';
  10. import {GroupPreviewHovercard} from 'sentry/components/groupPreviewTooltip/groupPreviewHovercard';
  11. import {
  12. useDelayedLoadingState,
  13. usePreviewEvent,
  14. } from 'sentry/components/groupPreviewTooltip/utils';
  15. import LoadingIndicator from 'sentry/components/loadingIndicator';
  16. import {t} from 'sentry/locale';
  17. import {space} from 'sentry/styles/space';
  18. import {PlatformKey} from 'sentry/types';
  19. import {EntryType, Event} from 'sentry/types/event';
  20. import {StacktraceType} from 'sentry/types/stacktrace';
  21. import {defined} from 'sentry/utils';
  22. import {isNativePlatform} from 'sentry/utils/platform';
  23. import useOrganization from 'sentry/utils/useOrganization';
  24. export function getStacktrace(event: Event): StacktraceType | null {
  25. const exceptionsWithStacktrace =
  26. event.entries
  27. .find(e => e.type === EntryType.EXCEPTION)
  28. ?.data?.values.filter(({stacktrace}) => defined(stacktrace)) ?? [];
  29. const exceptionStacktrace: StacktraceType | undefined = isStacktraceNewestFirst()
  30. ? exceptionsWithStacktrace[exceptionsWithStacktrace.length - 1]?.stacktrace
  31. : exceptionsWithStacktrace[0]?.stacktrace;
  32. if (exceptionStacktrace) {
  33. return exceptionStacktrace;
  34. }
  35. const threads =
  36. event.entries.find(e => e.type === EntryType.THREADS)?.data?.values ?? [];
  37. const bestThread = findBestThread(threads);
  38. if (!bestThread) {
  39. return null;
  40. }
  41. const bestThreadStacktrace = getThreadStacktrace(false, bestThread);
  42. if (bestThreadStacktrace) {
  43. return bestThreadStacktrace;
  44. }
  45. return null;
  46. }
  47. export function StackTracePreviewContent({
  48. event,
  49. stacktrace,
  50. orgFeatures = [],
  51. groupingCurrentLevel,
  52. }: {
  53. event: Event;
  54. stacktrace: StacktraceType;
  55. groupingCurrentLevel?: number;
  56. orgFeatures?: string[];
  57. }) {
  58. const includeSystemFrames = useMemo(() => {
  59. return stacktrace?.frames?.every(frame => !frame.inApp) ?? false;
  60. }, [stacktrace]);
  61. const framePlatform = stacktrace?.frames?.find(frame => !!frame.platform)?.platform;
  62. const platform = (framePlatform ?? event.platform ?? 'other') as PlatformKey;
  63. const newestFirst = isStacktraceNewestFirst();
  64. const commonProps = {
  65. data: stacktrace,
  66. expandFirstFrame: false,
  67. includeSystemFrames,
  68. platform,
  69. newestFirst,
  70. event,
  71. isHoverPreviewed: true,
  72. };
  73. if (isNativePlatform(platform)) {
  74. return <NativeContent {...commonProps} groupingCurrentLevel={groupingCurrentLevel} />;
  75. }
  76. if (orgFeatures.includes('grouping-stacktrace-ui')) {
  77. return (
  78. <HierarchicalGroupingContent
  79. {...commonProps}
  80. groupingCurrentLevel={groupingCurrentLevel}
  81. />
  82. );
  83. }
  84. return <StackTraceContent {...commonProps} />;
  85. }
  86. type StackTracePreviewProps = {
  87. children: React.ReactChild;
  88. groupId: string;
  89. eventId?: string;
  90. groupingCurrentLevel?: number;
  91. projectSlug?: string;
  92. query?: string;
  93. };
  94. interface StackTracePreviewBodyProps
  95. extends Pick<
  96. StackTracePreviewProps,
  97. 'groupId' | 'eventId' | 'groupingCurrentLevel' | 'projectSlug' | 'query'
  98. > {
  99. onRequestBegin: () => void;
  100. onRequestEnd: () => void;
  101. onUnmount: () => void;
  102. }
  103. function StackTracePreviewBody({
  104. groupId,
  105. groupingCurrentLevel,
  106. onRequestBegin,
  107. onRequestEnd,
  108. onUnmount,
  109. query,
  110. }: StackTracePreviewBodyProps) {
  111. const organization = useOrganization();
  112. const {data, isLoading, isError} = usePreviewEvent({groupId, query});
  113. useEffect(() => {
  114. if (isLoading) {
  115. onRequestBegin();
  116. } else {
  117. onRequestEnd();
  118. }
  119. return onUnmount;
  120. }, [isLoading, onRequestBegin, onRequestEnd, onUnmount]);
  121. const stacktrace = useMemo(() => (data ? getStacktrace(data) : null), [data]);
  122. if (isLoading) {
  123. return (
  124. <NoStackTraceWrapper>
  125. <LoadingIndicator hideMessage size={32} />
  126. </NoStackTraceWrapper>
  127. );
  128. }
  129. if (isError) {
  130. return <NoStackTraceWrapper>{t('Failed to load stack trace.')}</NoStackTraceWrapper>;
  131. }
  132. if (stacktrace && data) {
  133. return (
  134. <StackTracePreviewWrapper>
  135. <StackTracePreviewContent
  136. event={data}
  137. stacktrace={stacktrace}
  138. groupingCurrentLevel={groupingCurrentLevel}
  139. orgFeatures={organization.features}
  140. />
  141. </StackTracePreviewWrapper>
  142. );
  143. }
  144. return (
  145. <NoStackTraceWrapper>
  146. {t('There is no stack trace available for this issue.')}
  147. </NoStackTraceWrapper>
  148. );
  149. }
  150. function StackTracePreview({children, ...props}: StackTracePreviewProps) {
  151. const organization = useOrganization();
  152. const {shouldShowLoadingState, onRequestBegin, onRequestEnd, reset} =
  153. useDelayedLoadingState();
  154. const hasGroupingStacktraceUI = organization.features.includes(
  155. 'grouping-stacktrace-ui'
  156. );
  157. return (
  158. <Wrapper
  159. data-testid="stacktrace-preview"
  160. hasGroupingStacktraceUI={hasGroupingStacktraceUI}
  161. >
  162. <GroupPreviewHovercard
  163. hide={!shouldShowLoadingState}
  164. body={
  165. <StackTracePreviewBody
  166. onRequestBegin={onRequestBegin}
  167. onRequestEnd={onRequestEnd}
  168. onUnmount={reset}
  169. {...props}
  170. />
  171. }
  172. >
  173. {children}
  174. </GroupPreviewHovercard>
  175. </Wrapper>
  176. );
  177. }
  178. export {StackTracePreview};
  179. const Wrapper = styled('span')<{
  180. hasGroupingStacktraceUI: boolean;
  181. }>`
  182. ${p =>
  183. p.hasGroupingStacktraceUI &&
  184. css`
  185. display: inline-flex;
  186. overflow: hidden;
  187. height: 100%;
  188. > span:first-child {
  189. ${p.theme.overflowEllipsis}
  190. }
  191. `}
  192. `;
  193. const StackTracePreviewWrapper = styled('div')`
  194. width: 700px;
  195. .traceback {
  196. margin-bottom: 0;
  197. border: 0;
  198. }
  199. `;
  200. const NoStackTraceWrapper = styled('div')`
  201. color: ${p => p.theme.subText};
  202. padding: ${space(1.5)};
  203. font-size: ${p => p.theme.fontSizeMedium};
  204. display: flex;
  205. align-items: center;
  206. justify-content: center;
  207. min-height: 56px;
  208. `;