hierarchicalGroupingContent.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import {cloneElement, Fragment, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import List from 'sentry/components/list';
  4. import ListItem from 'sentry/components/list/listItem';
  5. import Panel from 'sentry/components/panels/panel';
  6. import {t} from 'sentry/locale';
  7. import {Frame, Group, PlatformKey} from 'sentry/types';
  8. import {Event} from 'sentry/types/event';
  9. import {StacktraceType} from 'sentry/types/stacktrace';
  10. import {defined} from 'sentry/utils';
  11. import Line from '../../frame/line';
  12. import {getImageRange, parseAddress, stackTracePlatformIcon} from '../../utils';
  13. import StacktracePlatformIcon from './platformIcon';
  14. type Props = {
  15. data: StacktraceType;
  16. event: Event;
  17. platform: PlatformKey;
  18. className?: string;
  19. expandFirstFrame?: boolean;
  20. groupingCurrentLevel?: Group['metadata']['current_level'];
  21. hideIcon?: boolean;
  22. includeSystemFrames?: boolean;
  23. isHoverPreviewed?: boolean;
  24. maxDepth?: number;
  25. meta?: Record<any, any>;
  26. newestFirst?: boolean;
  27. };
  28. export function HierarchicalGroupingContent({
  29. data,
  30. platform,
  31. event,
  32. newestFirst,
  33. className,
  34. isHoverPreviewed,
  35. groupingCurrentLevel,
  36. maxDepth,
  37. meta,
  38. hideIcon,
  39. includeSystemFrames = true,
  40. expandFirstFrame = true,
  41. }: Props) {
  42. const [showingAbsoluteAddresses, setShowingAbsoluteAddresses] = useState(false);
  43. const [showCompleteFunctionName, setShowCompleteFunctionName] = useState(false);
  44. const {frames = [], framesOmitted, registers} = data;
  45. function findImageForAddress(
  46. address: Frame['instructionAddr'],
  47. addrMode: Frame['addrMode']
  48. ) {
  49. const images = event.entries.find(entry => entry.type === 'debugmeta')?.data?.images;
  50. return images && address
  51. ? images.find((img, idx) => {
  52. if (!addrMode || addrMode === 'abs') {
  53. const [startAddress, endAddress] = getImageRange(img);
  54. return address >= (startAddress as any) && address < (endAddress as any);
  55. }
  56. return addrMode === `rel:${idx}`;
  57. })
  58. : null;
  59. }
  60. function getClassName() {
  61. if (includeSystemFrames) {
  62. return `${className} traceback full-traceback`;
  63. }
  64. return `${className} traceback in-app-traceback`;
  65. }
  66. function isFrameUsedForGrouping(frame: Frame) {
  67. const {minGroupingLevel} = frame;
  68. if (groupingCurrentLevel === undefined || minGroupingLevel === undefined) {
  69. return false;
  70. }
  71. return minGroupingLevel <= groupingCurrentLevel;
  72. }
  73. function handleToggleAddresses(mouseEvent: React.MouseEvent<SVGElement>) {
  74. mouseEvent.stopPropagation(); // to prevent collapsing if collapsible
  75. setShowingAbsoluteAddresses(!showingAbsoluteAddresses);
  76. }
  77. function handleToggleFunctionName(mouseEvent: React.MouseEvent<SVGElement>) {
  78. mouseEvent.stopPropagation(); // to prevent collapsing if collapsible
  79. setShowCompleteFunctionName(!showCompleteFunctionName);
  80. }
  81. function getLastFrameIndex() {
  82. const inAppFrameIndexes = frames
  83. .map((frame, frameIndex) => {
  84. if (frame.inApp) {
  85. return frameIndex;
  86. }
  87. return undefined;
  88. })
  89. .filter(frame => frame !== undefined);
  90. return !inAppFrameIndexes.length
  91. ? frames.length - 1
  92. : inAppFrameIndexes[inAppFrameIndexes.length - 1];
  93. }
  94. function renderOmittedFrames(firstFrameOmitted: any, lastFrameOmitted: any) {
  95. return (
  96. <ListItem className="frame frames-omitted">
  97. {t(
  98. 'Frames %d until %d were omitted and not available.',
  99. firstFrameOmitted,
  100. lastFrameOmitted
  101. )}
  102. </ListItem>
  103. );
  104. }
  105. function renderConvertedFrames() {
  106. const firstFrameOmitted = framesOmitted?.[0] ?? null;
  107. const lastFrameOmitted = framesOmitted?.[1] ?? null;
  108. const lastFrameIndex = getLastFrameIndex();
  109. let nRepeats = 0;
  110. const maxLengthOfAllRelativeAddresses = frames.reduce(
  111. (maxLengthUntilThisPoint, frame) => {
  112. const correspondingImage = findImageForAddress(
  113. frame.instructionAddr,
  114. frame.addrMode
  115. );
  116. try {
  117. const relativeAddress = (
  118. parseAddress(frame.instructionAddr) -
  119. parseAddress(correspondingImage.image_addr)
  120. ).toString(16);
  121. return maxLengthUntilThisPoint > relativeAddress.length
  122. ? maxLengthUntilThisPoint
  123. : relativeAddress.length;
  124. } catch {
  125. return maxLengthUntilThisPoint;
  126. }
  127. },
  128. 0
  129. );
  130. let convertedFrames = frames
  131. .map((frame, frameIndex) => {
  132. const prevFrame = frames[frameIndex - 1];
  133. const nextFrame = frames[frameIndex + 1];
  134. const repeatedFrame =
  135. nextFrame &&
  136. frame.lineNo === nextFrame.lineNo &&
  137. frame.instructionAddr === nextFrame.instructionAddr &&
  138. frame.package === nextFrame.package &&
  139. frame.module === nextFrame.module &&
  140. frame.function === nextFrame.function;
  141. if (repeatedFrame) {
  142. nRepeats++;
  143. }
  144. const isUsedForGrouping = isFrameUsedForGrouping(frame);
  145. const isVisible =
  146. includeSystemFrames ||
  147. frame.inApp ||
  148. (nextFrame && nextFrame.inApp) ||
  149. // the last non-app frame
  150. (!frame.inApp && !nextFrame) ||
  151. isUsedForGrouping;
  152. if (isVisible && !repeatedFrame) {
  153. const lineProps = {
  154. event,
  155. frame,
  156. prevFrame,
  157. nextFrame,
  158. isExpanded: expandFirstFrame && lastFrameIndex === frameIndex,
  159. emptySourceNotation: lastFrameIndex === frameIndex && frameIndex === 0,
  160. platform,
  161. timesRepeated: nRepeats,
  162. showingAbsoluteAddress: showingAbsoluteAddresses,
  163. onAddressToggle: handleToggleAddresses,
  164. image: findImageForAddress(frame.instructionAddr, frame.addrMode),
  165. maxLengthOfRelativeAddress: maxLengthOfAllRelativeAddresses,
  166. registers: {},
  167. includeSystemFrames,
  168. onFunctionNameToggle: handleToggleFunctionName,
  169. showCompleteFunctionName,
  170. isHoverPreviewed,
  171. isUsedForGrouping,
  172. frameMeta: meta?.frames?.[frameIndex],
  173. registersMeta: meta?.registers,
  174. };
  175. nRepeats = 0;
  176. if (frameIndex === firstFrameOmitted) {
  177. return (
  178. <Fragment key={frameIndex}>
  179. <Line {...lineProps} />
  180. {renderOmittedFrames(firstFrameOmitted, lastFrameOmitted)}
  181. </Fragment>
  182. );
  183. }
  184. return <Line key={frameIndex} {...lineProps} />;
  185. }
  186. if (!repeatedFrame) {
  187. nRepeats = 0;
  188. }
  189. if (frameIndex !== firstFrameOmitted) {
  190. return null;
  191. }
  192. return renderOmittedFrames(firstFrameOmitted, lastFrameOmitted);
  193. })
  194. .filter(frame => !!frame) as React.ReactElement[];
  195. if (convertedFrames.length > 0 && registers) {
  196. const lastFrame = convertedFrames.length - 1;
  197. convertedFrames[lastFrame] = cloneElement(convertedFrames[lastFrame], {
  198. registers,
  199. });
  200. }
  201. if (defined(maxDepth)) {
  202. convertedFrames = convertedFrames.slice(-maxDepth);
  203. }
  204. if (!newestFirst) {
  205. return convertedFrames;
  206. }
  207. return [...convertedFrames].reverse();
  208. }
  209. const platformIcon = stackTracePlatformIcon(platform, frames);
  210. return (
  211. <Wrapper className={getClassName()} data-test-id="stack-trace-content-v2">
  212. {!hideIcon && <StacktracePlatformIcon platform={platformIcon} />}
  213. <StyledList>{renderConvertedFrames()}</StyledList>
  214. </Wrapper>
  215. );
  216. }
  217. const Wrapper = styled(Panel)`
  218. position: relative;
  219. border-top-left-radius: 0;
  220. `;
  221. const StyledList = styled(List)`
  222. gap: 0;
  223. `;