newTraceDetailsTraceView.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. import {createRef, Fragment, useEffect} from 'react';
  2. import {RouteComponentProps} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import * as Sentry from '@sentry/react';
  5. import * as DividerHandlerManager from 'sentry/components/events/interfaces/spans/dividerHandlerManager';
  6. import MeasurementsPanel from 'sentry/components/events/interfaces/spans/measurementsPanel';
  7. import TraceViewHeader from 'sentry/components/events/interfaces/spans/newTraceDetailsHeader';
  8. import * as ScrollbarManager from 'sentry/components/events/interfaces/spans/scrollbarManager';
  9. import {
  10. boundsGenerator,
  11. getMeasurements,
  12. } from 'sentry/components/events/interfaces/spans/utils';
  13. import Panel from 'sentry/components/panels/panel';
  14. import {MessageRow} from 'sentry/components/performance/waterfall/messageRow';
  15. import {
  16. DividerSpacer,
  17. ScrollbarContainer,
  18. VirtualScrollbar,
  19. VirtualScrollbarGrip,
  20. } from 'sentry/components/performance/waterfall/miniHeader';
  21. import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
  22. import {tct} from 'sentry/locale';
  23. import {EventTransaction, Organization} from 'sentry/types';
  24. import {trackAnalytics} from 'sentry/utils/analytics';
  25. import EventView from 'sentry/utils/discover/eventView';
  26. import toPercent from 'sentry/utils/number/toPercent';
  27. import {
  28. TraceError,
  29. TraceFullDetailed,
  30. TraceMeta,
  31. } from 'sentry/utils/performance/quickTrace/types';
  32. import {
  33. TraceDetailBody,
  34. TraceViewContainer,
  35. TraceViewHeaderContainer,
  36. } from 'sentry/views/performance/traceDetails/styles';
  37. import TransactionGroup from 'sentry/views/performance/traceDetails/transactionGroup';
  38. import {TraceInfo, TreeDepth} from 'sentry/views/performance/traceDetails/types';
  39. import {
  40. getTraceInfo,
  41. hasTraceData,
  42. isRootTransaction,
  43. } from 'sentry/views/performance/traceDetails/utils';
  44. import LimitExceededMessage from './limitExceededMessage';
  45. import {TraceType} from './newTraceDetailsContent';
  46. import TraceNotFound from './traceNotFound';
  47. type AccType = {
  48. lastIndex: number;
  49. numberOfHiddenTransactionsAbove: number;
  50. renderedChildren: React.ReactNode[];
  51. };
  52. type Props = Pick<RouteComponentProps<{}, {}>, 'location'> & {
  53. meta: TraceMeta | null;
  54. organization: Organization;
  55. rootEvent: EventTransaction | undefined;
  56. traceEventView: EventView;
  57. traceSlug: string;
  58. traceType: TraceType;
  59. traces: TraceFullDetailed[];
  60. filteredEventIds?: Set<string>;
  61. handleLimitChange?: (newLimit: number) => void;
  62. orphanErrors?: TraceError[];
  63. traceInfo?: TraceInfo;
  64. };
  65. function TraceHiddenMessage({
  66. isVisible,
  67. numberOfHiddenTransactionsAbove,
  68. numberOfHiddenErrorsAbove,
  69. }: {
  70. isVisible: boolean;
  71. numberOfHiddenErrorsAbove: number;
  72. numberOfHiddenTransactionsAbove: number;
  73. }) {
  74. if (
  75. !isVisible ||
  76. (numberOfHiddenTransactionsAbove < 1 && numberOfHiddenErrorsAbove < 1)
  77. ) {
  78. return null;
  79. }
  80. const numOfTransaction = <strong>{numberOfHiddenTransactionsAbove}</strong>;
  81. const numOfErrors = <strong>{numberOfHiddenErrorsAbove}</strong>;
  82. const hiddenTransactionsMessage =
  83. numberOfHiddenTransactionsAbove < 1
  84. ? ''
  85. : numberOfHiddenTransactionsAbove === 1
  86. ? tct('[numOfTransaction] hidden transaction', {
  87. numOfTransaction,
  88. })
  89. : tct('[numOfTransaction] hidden transactions', {
  90. numOfTransaction,
  91. });
  92. const hiddenErrorsMessage =
  93. numberOfHiddenErrorsAbove < 1
  94. ? ''
  95. : numberOfHiddenErrorsAbove === 1
  96. ? tct('[numOfErrors] hidden error', {
  97. numOfErrors,
  98. })
  99. : tct('[numOfErrors] hidden errors', {
  100. numOfErrors,
  101. });
  102. return (
  103. <MessageRow>
  104. <span key="trace-info-message">
  105. {hiddenTransactionsMessage}
  106. {hiddenErrorsMessage && hiddenTransactionsMessage && ', '}
  107. {hiddenErrorsMessage}
  108. </span>
  109. </MessageRow>
  110. );
  111. }
  112. function isRowVisible(
  113. row: TraceFullDetailed | TraceError,
  114. filteredEventIds?: Set<string>
  115. ): boolean {
  116. return filteredEventIds ? filteredEventIds.has(row.event_id) : true;
  117. }
  118. function generateBounds(traceInfo: TraceInfo) {
  119. return boundsGenerator({
  120. traceStartTimestamp: traceInfo.startTimestamp,
  121. traceEndTimestamp: traceInfo.endTimestamp,
  122. viewStart: 0,
  123. viewEnd: 1,
  124. });
  125. }
  126. export default function NewTraceView({
  127. location,
  128. meta,
  129. organization,
  130. traces,
  131. traceSlug,
  132. traceEventView,
  133. filteredEventIds,
  134. orphanErrors,
  135. traceType,
  136. handleLimitChange,
  137. ...props
  138. }: Props) {
  139. const sentryTransaction = Sentry.getCurrentHub().getScope()?.getTransaction();
  140. const sentrySpan = sentryTransaction?.startChild({
  141. op: 'trace.render',
  142. description: 'trace-view-content',
  143. });
  144. const hasOrphanErrors = orphanErrors && orphanErrors.length > 0;
  145. const onlyOrphanErrors = hasOrphanErrors && (!traces || traces.length === 0);
  146. useEffect(() => {
  147. trackAnalytics('performance_views.trace_view.view', {
  148. organization,
  149. });
  150. }, [organization]);
  151. function renderTransaction(
  152. transaction: TraceFullDetailed,
  153. {
  154. continuingDepths,
  155. isOrphan,
  156. isLast,
  157. index,
  158. numberOfHiddenTransactionsAbove,
  159. traceInfo,
  160. hasGuideAnchor,
  161. }: {
  162. continuingDepths: TreeDepth[];
  163. hasGuideAnchor: boolean;
  164. index: number;
  165. isLast: boolean;
  166. isOrphan: boolean;
  167. numberOfHiddenTransactionsAbove: number;
  168. traceInfo: TraceInfo;
  169. }
  170. ) {
  171. const {children, event_id: eventId} = transaction;
  172. // Add 1 to the generation to make room for the "root trace"
  173. const generation = transaction.generation + 1;
  174. const isVisible = isRowVisible(transaction, filteredEventIds);
  175. const accumulated: AccType = children.reduce(
  176. (acc: AccType, child: TraceFullDetailed, idx: number) => {
  177. const isLastChild = idx === children.length - 1;
  178. const hasChildren = child.children.length > 0;
  179. const result = renderTransaction(child, {
  180. continuingDepths:
  181. !isLastChild && hasChildren
  182. ? [...continuingDepths, {depth: generation, isOrphanDepth: isOrphan}]
  183. : continuingDepths,
  184. isOrphan,
  185. isLast: isLastChild,
  186. index: acc.lastIndex + 1,
  187. numberOfHiddenTransactionsAbove: acc.numberOfHiddenTransactionsAbove,
  188. traceInfo,
  189. hasGuideAnchor: false,
  190. });
  191. acc.lastIndex = result.lastIndex;
  192. acc.numberOfHiddenTransactionsAbove = result.numberOfHiddenTransactionsAbove;
  193. acc.renderedChildren.push(result.transactionGroup);
  194. return acc;
  195. },
  196. {
  197. renderedChildren: [],
  198. lastIndex: index,
  199. numberOfHiddenTransactionsAbove: isVisible
  200. ? 0
  201. : numberOfHiddenTransactionsAbove + 1,
  202. }
  203. );
  204. return {
  205. transactionGroup: (
  206. <Fragment key={eventId}>
  207. <TraceHiddenMessage
  208. isVisible={isVisible}
  209. numberOfHiddenTransactionsAbove={numberOfHiddenTransactionsAbove}
  210. numberOfHiddenErrorsAbove={0}
  211. />
  212. <TransactionGroup
  213. location={location}
  214. traceViewRef={traceViewRef}
  215. organization={organization}
  216. traceInfo={traceInfo}
  217. transaction={{
  218. ...transaction,
  219. generation,
  220. }}
  221. measurements={
  222. traces && traces.length > 0
  223. ? getMeasurements(traces[0], generateBounds(traceInfo))
  224. : undefined
  225. }
  226. generateBounds={generateBounds(traceInfo)}
  227. continuingDepths={continuingDepths}
  228. isOrphan={isOrphan}
  229. isLast={isLast}
  230. index={index}
  231. isVisible={isVisible}
  232. hasGuideAnchor={hasGuideAnchor}
  233. renderedChildren={accumulated.renderedChildren}
  234. barColor={pickBarColor(transaction['transaction.op'])}
  235. />
  236. </Fragment>
  237. ),
  238. lastIndex: accumulated.lastIndex,
  239. numberOfHiddenTransactionsAbove: accumulated.numberOfHiddenTransactionsAbove,
  240. };
  241. }
  242. const traceViewRef = createRef<HTMLDivElement>();
  243. const virtualScrollbarContainerRef = createRef<HTMLDivElement>();
  244. if (!hasTraceData(traces, orphanErrors)) {
  245. return (
  246. <TraceNotFound
  247. meta={meta}
  248. traceEventView={traceEventView}
  249. traceSlug={traceSlug}
  250. location={location}
  251. organization={organization}
  252. />
  253. );
  254. }
  255. const traceInfo = props.traceInfo || getTraceInfo(traces);
  256. const accumulator: {
  257. index: number;
  258. numberOfHiddenTransactionsAbove: number;
  259. traceInfo: TraceInfo;
  260. transactionGroups: React.ReactNode[];
  261. } = {
  262. index: 1,
  263. numberOfHiddenTransactionsAbove: 0,
  264. traceInfo,
  265. transactionGroups: [],
  266. };
  267. let lastIndex: number = 0;
  268. const {transactionGroups, numberOfHiddenTransactionsAbove} = traces.reduce(
  269. (acc, trace, index) => {
  270. const isLastTransaction = index === traces.length - 1;
  271. const hasChildren = trace.children.length > 0;
  272. const isNextChildOrphaned =
  273. !isLastTransaction && traces[index + 1].parent_span_id !== null;
  274. const result = renderTransaction(trace, {
  275. ...acc,
  276. // if the root of a subtrace has a parent_span_id, then it must be an orphan
  277. isOrphan: !isRootTransaction(trace),
  278. isLast: isLastTransaction && !hasOrphanErrors,
  279. continuingDepths:
  280. (!isLastTransaction && hasChildren) || hasOrphanErrors
  281. ? [{depth: 0, isOrphanDepth: isNextChildOrphaned || Boolean(hasOrphanErrors)}]
  282. : [],
  283. hasGuideAnchor: index === 0,
  284. });
  285. acc.index = result.lastIndex + 1;
  286. lastIndex = Math.max(lastIndex, result.lastIndex);
  287. acc.numberOfHiddenTransactionsAbove = result.numberOfHiddenTransactionsAbove;
  288. acc.transactionGroups.push(result.transactionGroup);
  289. return acc;
  290. },
  291. accumulator
  292. );
  293. // Build transaction groups for orphan errors
  294. let numOfHiddenErrorsAbove = 0;
  295. let totalNumOfHiddenErrors = 0;
  296. if (hasOrphanErrors) {
  297. orphanErrors.forEach((error, index) => {
  298. const isLastError = index === orphanErrors.length - 1;
  299. const isVisible = isRowVisible(error, filteredEventIds);
  300. const currentHiddenCount = numOfHiddenErrorsAbove;
  301. if (!isVisible) {
  302. numOfHiddenErrorsAbove += 1;
  303. totalNumOfHiddenErrors += 1;
  304. } else {
  305. numOfHiddenErrorsAbove = 0;
  306. }
  307. transactionGroups.push(
  308. <Fragment key={error.event_id}>
  309. <TraceHiddenMessage
  310. isVisible={isVisible}
  311. numberOfHiddenTransactionsAbove={
  312. index === 0 ? numberOfHiddenTransactionsAbove : 0
  313. }
  314. numberOfHiddenErrorsAbove={index > 0 ? currentHiddenCount : 0}
  315. />
  316. <TransactionGroup
  317. location={location}
  318. organization={organization}
  319. traceViewRef={traceViewRef}
  320. traceInfo={traceInfo}
  321. transaction={{
  322. ...error,
  323. generation: 1,
  324. }}
  325. generateBounds={generateBounds(traceInfo)}
  326. measurements={
  327. traces && traces.length > 0
  328. ? getMeasurements(traces[0], generateBounds(traceInfo))
  329. : undefined
  330. }
  331. continuingDepths={[]}
  332. isOrphan
  333. isLast={isLastError}
  334. index={lastIndex + index + 1}
  335. isVisible={isVisible}
  336. hasGuideAnchor={index === 0 && transactionGroups.length === 0}
  337. renderedChildren={[]}
  338. />
  339. </Fragment>
  340. );
  341. });
  342. }
  343. const bounds = generateBounds(traceInfo);
  344. const measurements =
  345. traces.length > 0 && Object.keys(traces[0].measurements ?? {}).length > 0
  346. ? getMeasurements(traces[0], bounds)
  347. : undefined;
  348. const traceView = (
  349. <TraceDetailBody>
  350. <DividerHandlerManager.Provider interactiveLayerRef={traceViewRef}>
  351. <DividerHandlerManager.Consumer>
  352. {({dividerPosition}) => (
  353. <ScrollbarManager.Provider
  354. dividerPosition={dividerPosition}
  355. interactiveLayerRef={virtualScrollbarContainerRef}
  356. isEmbedded
  357. >
  358. <StyledTracePanel>
  359. <TraceViewHeader
  360. traceInfo={traceInfo}
  361. traceType={traceType}
  362. traceViewHeaderRef={traceViewRef}
  363. organization={organization}
  364. event={props.rootEvent}
  365. />
  366. <TraceViewHeaderContainer>
  367. <ScrollbarManager.Consumer>
  368. {({virtualScrollbarRef, scrollBarAreaRef, onDragStart, onScroll}) => {
  369. return (
  370. <ScrollbarContainer
  371. ref={virtualScrollbarContainerRef}
  372. style={{
  373. // the width of this component is shrunk to compensate for half of the width of the divider line
  374. width: `calc(${toPercent(dividerPosition)} - 0.5px)`,
  375. }}
  376. onScroll={onScroll}
  377. >
  378. <div
  379. style={{
  380. width: 0,
  381. height: '1px',
  382. }}
  383. ref={scrollBarAreaRef}
  384. />
  385. <VirtualScrollbar
  386. data-type="virtual-scrollbar"
  387. ref={virtualScrollbarRef}
  388. onMouseDown={onDragStart}
  389. >
  390. <VirtualScrollbarGrip />
  391. </VirtualScrollbar>
  392. </ScrollbarContainer>
  393. );
  394. }}
  395. </ScrollbarManager.Consumer>
  396. <DividerSpacer />
  397. {measurements ? (
  398. <MeasurementsPanel
  399. measurements={measurements}
  400. generateBounds={bounds}
  401. dividerPosition={dividerPosition}
  402. />
  403. ) : null}
  404. </TraceViewHeaderContainer>
  405. <TraceViewContainer ref={traceViewRef}>
  406. <TransactionGroup
  407. location={location}
  408. organization={organization}
  409. traceInfo={traceInfo}
  410. transaction={{
  411. traceSlug,
  412. generation: 0,
  413. 'transaction.duration':
  414. traceInfo.endTimestamp - traceInfo.startTimestamp,
  415. children: traces,
  416. start_timestamp: traceInfo.startTimestamp,
  417. timestamp: traceInfo.endTimestamp,
  418. }}
  419. measurements={measurements}
  420. generateBounds={bounds}
  421. continuingDepths={[]}
  422. isOrphan={false}
  423. isLast={false}
  424. index={0}
  425. isVisible
  426. hasGuideAnchor={false}
  427. renderedChildren={transactionGroups}
  428. barColor={pickBarColor('')}
  429. onlyOrphanErrors={onlyOrphanErrors}
  430. traceViewRef={traceViewRef}
  431. numOfOrphanErrors={orphanErrors?.length}
  432. />
  433. <TraceHiddenMessage
  434. isVisible
  435. numberOfHiddenTransactionsAbove={numberOfHiddenTransactionsAbove}
  436. numberOfHiddenErrorsAbove={totalNumOfHiddenErrors}
  437. />
  438. <LimitExceededMessage
  439. traceInfo={traceInfo}
  440. organization={organization}
  441. traceEventView={traceEventView}
  442. meta={meta}
  443. handleLimitChange={handleLimitChange}
  444. />
  445. </TraceViewContainer>
  446. </StyledTracePanel>
  447. </ScrollbarManager.Provider>
  448. )}
  449. </DividerHandlerManager.Consumer>
  450. </DividerHandlerManager.Provider>
  451. </TraceDetailBody>
  452. );
  453. sentrySpan?.finish();
  454. return traceView;
  455. }
  456. export const StyledTracePanel = styled(Panel)`
  457. height: 100%;
  458. overflow-x: visible;
  459. ${TraceViewContainer} {
  460. overflow-x: visible;
  461. }
  462. `;