trace.tsx 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078
  1. import type React from 'react';
  2. import {Fragment, useCallback, useMemo, useRef, useState} from 'react';
  3. import {AutoSizer, List} from 'react-virtualized';
  4. import {type Theme, useTheme} from '@emotion/react';
  5. import styled from '@emotion/styled';
  6. import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
  7. import LoadingIndicator from 'sentry/components/loadingIndicator';
  8. import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
  9. import PerformanceDuration from 'sentry/components/performanceDuration';
  10. import Placeholder from 'sentry/components/placeholder';
  11. import {IconChevron} from 'sentry/icons';
  12. import {t} from 'sentry/locale';
  13. import {space} from 'sentry/styles/space';
  14. import type {Project} from 'sentry/types';
  15. import useApi from 'sentry/utils/useApi';
  16. import useOrganization from 'sentry/utils/useOrganization';
  17. import useProjects from 'sentry/utils/useProjects';
  18. import {
  19. isAutogroupedNode,
  20. isMissingInstrumentationNode,
  21. isParentAutogroupedNode,
  22. isSpanNode,
  23. isTraceErrorNode,
  24. isTraceNode,
  25. isTransactionNode,
  26. } from './guards';
  27. import {ParentAutogroupNode, type TraceTree, type TraceTreeNode} from './traceTree';
  28. import {VirtualizedViewManager} from './virtualizedViewManager';
  29. interface TraceProps {
  30. trace: TraceTree;
  31. trace_id: string;
  32. }
  33. function Trace({trace, trace_id}: TraceProps) {
  34. const theme = useTheme();
  35. const api = useApi();
  36. const {projects} = useProjects();
  37. const organization = useOrganization();
  38. const virtualizedListRef = useRef<List>(null);
  39. const viewManager = useRef<VirtualizedViewManager | null>(null);
  40. const [_rerender, setRender] = useState(0);
  41. if (!viewManager.current) {
  42. viewManager.current = new VirtualizedViewManager({
  43. list: {width: 0.5},
  44. span_list: {width: 0.5},
  45. });
  46. }
  47. if (
  48. trace.root.space &&
  49. (trace.root.space[0] !== viewManager.current.space[0] ||
  50. trace.root.space[1] !== viewManager.current.space[1])
  51. ) {
  52. viewManager.current.initializeSpace(trace.root.space);
  53. }
  54. const treeRef = useRef<TraceTree>(trace);
  55. treeRef.current = trace;
  56. const handleFetchChildren = useCallback(
  57. (node: TraceTreeNode<TraceTree.NodeValue>, value: boolean) => {
  58. treeRef.current
  59. .zoomIn(node, value, {
  60. api,
  61. organization,
  62. })
  63. .then(() => {
  64. setRender(a => (a + 1) % 2);
  65. });
  66. },
  67. [api, organization]
  68. );
  69. const handleExpandNode = useCallback(
  70. (node: TraceTreeNode<TraceTree.NodeValue>, value: boolean) => {
  71. treeRef.current.expand(node, value);
  72. setRender(a => (a + 1) % 2);
  73. },
  74. []
  75. );
  76. const projectLookup = useMemo(() => {
  77. return projects.reduce<Record<Project['slug'], Project>>((acc, project) => {
  78. acc[project.slug] = project;
  79. return acc;
  80. }, {});
  81. }, [projects]);
  82. return (
  83. <Fragment>
  84. <TraceStylingWrapper
  85. ref={r => viewManager.current?.onContainerRef(r)}
  86. className={trace.type === 'loading' ? 'Loading' : ''}
  87. style={{
  88. backgroundColor: '#FFF',
  89. height: '70vh',
  90. width: '100%',
  91. margin: 'auto',
  92. }}
  93. >
  94. <TraceDivider ref={r => viewManager.current?.registerDividerRef(r)} />
  95. <AutoSizer>
  96. {({width, height}) => (
  97. <List
  98. ref={virtualizedListRef}
  99. rowHeight={24}
  100. height={height}
  101. width={width}
  102. overscanRowCount={10}
  103. rowCount={treeRef.current.list.length ?? 0}
  104. rowRenderer={p => {
  105. return trace.type === 'loading' ? (
  106. <RenderPlaceholderRow
  107. style={p.style}
  108. node={treeRef.current.list[p.index]}
  109. index={p.index}
  110. theme={theme}
  111. projects={projectLookup}
  112. viewManager={viewManager.current!}
  113. startIndex={
  114. (p.parent as unknown as {_rowStartIndex: number})._rowStartIndex ??
  115. 0
  116. }
  117. />
  118. ) : (
  119. <RenderRow
  120. key={p.key}
  121. theme={theme}
  122. startIndex={
  123. (p.parent as unknown as {_rowStartIndex: number})._rowStartIndex ??
  124. 0
  125. }
  126. index={p.index}
  127. style={p.style}
  128. trace_id={trace_id}
  129. projects={projectLookup}
  130. node={treeRef.current.list[p.index]}
  131. viewManager={viewManager.current!}
  132. onFetchChildren={handleFetchChildren}
  133. onExpandNode={handleExpandNode}
  134. />
  135. );
  136. }}
  137. />
  138. )}
  139. </AutoSizer>
  140. </TraceStylingWrapper>
  141. </Fragment>
  142. );
  143. }
  144. export default Trace;
  145. const TraceDivider = styled('div')`
  146. position: absolute;
  147. height: 100%;
  148. background-color: transparent;
  149. top: 0;
  150. z-index: 1;
  151. cursor: col-resize;
  152. &:before {
  153. content: '';
  154. position: absolute;
  155. width: 1px;
  156. height: 100%;
  157. background-color: ${p => p.theme.border};
  158. left: 50%;
  159. }
  160. &:hover&:before {
  161. background-color: ${p => p.theme.purple300};
  162. }
  163. `;
  164. function RenderRow(props: {
  165. index: number;
  166. node: TraceTreeNode<TraceTree.NodeValue>;
  167. onExpandNode: (node: TraceTreeNode<TraceTree.NodeValue>, value: boolean) => void;
  168. onFetchChildren: (node: TraceTreeNode<TraceTree.NodeValue>, value: boolean) => void;
  169. projects: Record<Project['slug'], Project>;
  170. startIndex: number;
  171. style: React.CSSProperties;
  172. theme: Theme;
  173. trace_id: string;
  174. viewManager: VirtualizedViewManager;
  175. }) {
  176. const virtualizedIndex = props.index - props.startIndex;
  177. if (!props.node.value) {
  178. return null;
  179. }
  180. if (isAutogroupedNode(props.node)) {
  181. return (
  182. <div
  183. className="TraceRow Autogrouped"
  184. style={{
  185. top: props.style.top,
  186. height: props.style.height,
  187. }}
  188. >
  189. <div
  190. className="TraceLeftColumn"
  191. ref={r =>
  192. props.viewManager.registerColumnRef('list', r, virtualizedIndex, props.node)
  193. }
  194. style={{
  195. width: props.viewManager.columns.list.width * 100 + '%',
  196. }}
  197. >
  198. <div
  199. className="TraceLeftColumnInner"
  200. style={{
  201. paddingLeft: props.node.depth * 23,
  202. }}
  203. >
  204. <div className="TraceChildrenCountWrapper">
  205. <Connectors node={props.node} />
  206. <ChildrenCountButton
  207. expanded={!props.node.expanded}
  208. onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
  209. >
  210. {props.node.groupCount}{' '}
  211. </ChildrenCountButton>
  212. </div>
  213. <span className="TraceOperation">{t('Autogrouped')}</span>
  214. <strong className="TraceEmDash"> — </strong>
  215. <span className="TraceDescription">{props.node.value.autogrouped_by.op}</span>
  216. </div>
  217. </div>
  218. <div
  219. className="TraceRightColumn"
  220. ref={r =>
  221. props.viewManager.registerColumnRef(
  222. 'span_list',
  223. r,
  224. virtualizedIndex,
  225. props.node
  226. )
  227. }
  228. style={{
  229. width: props.viewManager.columns.span_list.width * 100 + '%',
  230. backgroundColor:
  231. props.index % 2 ? undefined : props.theme.backgroundSecondary,
  232. }}
  233. >
  234. <TraceBar
  235. virtualizedIndex={virtualizedIndex}
  236. viewManager={props.viewManager}
  237. color={pickBarColor('autogrouping')}
  238. node_space={props.node.space}
  239. />
  240. </div>
  241. </div>
  242. );
  243. }
  244. if (isTransactionNode(props.node)) {
  245. return (
  246. <div
  247. className="TraceRow"
  248. style={{
  249. top: props.style.top,
  250. height: props.style.height,
  251. }}
  252. >
  253. <div
  254. className="TraceLeftColumn"
  255. ref={r =>
  256. props.viewManager.registerColumnRef('list', r, virtualizedIndex, props.node)
  257. }
  258. style={{
  259. width: props.viewManager.columns.list.width * 100 + '%',
  260. }}
  261. >
  262. <div
  263. className="TraceLeftColumnInner"
  264. style={{
  265. paddingLeft: props.node.depth * 23,
  266. }}
  267. >
  268. <div
  269. className={`TraceChildrenCountWrapper ${
  270. props.node.isOrphaned ? 'Orphaned' : ''
  271. }`}
  272. >
  273. <Connectors node={props.node} />
  274. {props.node.children.length > 0 ? (
  275. <ChildrenCountButton
  276. expanded={props.node.expanded || props.node.zoomedIn}
  277. onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
  278. >
  279. {props.node.children.length}{' '}
  280. </ChildrenCountButton>
  281. ) : null}
  282. </div>
  283. <ProjectBadge project={props.projects[props.node.value.project_slug]} />
  284. <span className="TraceOperation">{props.node.value['transaction.op']}</span>
  285. <strong className="TraceEmDash"> — </strong>
  286. <span>{props.node.value.transaction}</span>
  287. {props.node.canFetchData ? (
  288. <button
  289. onClick={() => props.onFetchChildren(props.node, !props.node.zoomedIn)}
  290. >
  291. {props.node.zoomedIn ? 'Zoom Out' : 'Zoom In'}
  292. </button>
  293. ) : null}
  294. </div>
  295. </div>
  296. <div
  297. ref={r =>
  298. props.viewManager.registerColumnRef(
  299. 'span_list',
  300. r,
  301. virtualizedIndex,
  302. props.node
  303. )
  304. }
  305. className="TraceRightColumn"
  306. style={{
  307. width: props.viewManager.columns.span_list.width * 100 + '%',
  308. backgroundColor:
  309. props.index % 2 ? undefined : props.theme.backgroundSecondary,
  310. }}
  311. >
  312. <TraceBar
  313. virtualizedIndex={virtualizedIndex}
  314. viewManager={props.viewManager}
  315. color={pickBarColor(props.node.value['transaction.op'])}
  316. node_space={props.node.space}
  317. />
  318. </div>
  319. </div>
  320. );
  321. }
  322. if (isSpanNode(props.node)) {
  323. return (
  324. <div
  325. className="TraceRow"
  326. style={{
  327. top: props.style.top,
  328. height: props.style.height,
  329. }}
  330. >
  331. <div
  332. className="TraceLeftColumn"
  333. ref={r =>
  334. props.viewManager.registerColumnRef('list', r, virtualizedIndex, props.node)
  335. }
  336. style={{
  337. width: props.viewManager.columns.list.width * 100 + '%',
  338. }}
  339. >
  340. <div
  341. className="TraceLeftColumnInner"
  342. style={{
  343. paddingLeft: props.node.depth * 23,
  344. }}
  345. >
  346. <div
  347. className={`TraceChildrenCountWrapper ${
  348. props.node.isOrphaned ? 'Orphaned' : ''
  349. }`}
  350. >
  351. <Connectors node={props.node} />
  352. {props.node.children.length > 0 ? (
  353. <ChildrenCountButton
  354. expanded={props.node.expanded || props.node.zoomedIn}
  355. onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
  356. >
  357. {props.node.children.length}{' '}
  358. </ChildrenCountButton>
  359. ) : null}
  360. </div>
  361. <span className="TraceOperation">{props.node.value.op ?? '<unknown>'}</span>
  362. <strong className="TraceEmDash"> — </strong>
  363. <span className="TraceDescription">
  364. {props.node.value.description ?? '<unknown>'}
  365. </span>
  366. {props.node.canFetchData ? (
  367. <button
  368. onClick={() => props.onFetchChildren(props.node, !props.node.zoomedIn)}
  369. >
  370. {props.node.zoomedIn ? 'Zoom Out' : 'Zoom In'}
  371. </button>
  372. ) : null}
  373. </div>
  374. </div>
  375. <div
  376. ref={r =>
  377. props.viewManager.registerColumnRef(
  378. 'span_list',
  379. r,
  380. virtualizedIndex,
  381. props.node
  382. )
  383. }
  384. className="TraceRightColumn"
  385. style={{
  386. width: props.viewManager.columns.span_list.width * 100 + '%',
  387. backgroundColor:
  388. props.index % 2 ? undefined : props.theme.backgroundSecondary,
  389. }}
  390. >
  391. <TraceBar
  392. virtualizedIndex={virtualizedIndex}
  393. viewManager={props.viewManager}
  394. color={pickBarColor(props.node.value.op)}
  395. node_space={props.node.space}
  396. />
  397. </div>
  398. </div>
  399. );
  400. }
  401. if (isMissingInstrumentationNode(props.node)) {
  402. return (
  403. <div
  404. className="TraceRow"
  405. style={{
  406. top: props.style.top,
  407. height: props.style.height,
  408. }}
  409. >
  410. <div
  411. className="TraceLeftColumn"
  412. ref={r =>
  413. props.viewManager.registerColumnRef('list', r, virtualizedIndex, props.node)
  414. }
  415. style={{
  416. width: props.viewManager.columns.list.width * 100 + '%',
  417. }}
  418. >
  419. <div
  420. className="TraceLeftColumnInner"
  421. style={{
  422. paddingLeft: props.node.depth * 23,
  423. }}
  424. >
  425. <div className="TraceChildrenCountWrapper">
  426. <Connectors node={props.node} />
  427. </div>
  428. <span className="TraceOperation">{t('Missing instrumentation')}</span>
  429. </div>
  430. </div>
  431. <div
  432. ref={r =>
  433. props.viewManager.registerColumnRef(
  434. 'span_list',
  435. r,
  436. virtualizedIndex,
  437. props.node
  438. )
  439. }
  440. className="TraceRightColumn"
  441. style={{
  442. width: props.viewManager.columns.span_list.width * 100 + '%',
  443. backgroundColor:
  444. props.index % 2 ? undefined : props.theme.backgroundSecondary,
  445. }}
  446. >
  447. <TraceBar
  448. virtualizedIndex={virtualizedIndex}
  449. viewManager={props.viewManager}
  450. color={pickBarColor('missing-instrumentation')}
  451. node_space={props.node.space}
  452. />
  453. </div>
  454. </div>
  455. );
  456. }
  457. if (isTraceNode(props.node)) {
  458. return (
  459. <div
  460. className="TraceRow"
  461. style={{
  462. top: props.style.top,
  463. height: props.style.height,
  464. }}
  465. >
  466. <div
  467. className="TraceLeftColumn"
  468. ref={r =>
  469. props.viewManager.registerColumnRef('list', r, virtualizedIndex, props.node)
  470. }
  471. style={{
  472. width: props.viewManager.columns.list.width * 100 + '%',
  473. }}
  474. >
  475. <div
  476. className="TraceLeftColumnInner"
  477. style={{
  478. paddingLeft: props.node.depth * 23,
  479. }}
  480. >
  481. <div className="TraceChildrenCountWrapper Root">
  482. <Connectors node={props.node} />
  483. {props.node.children.length > 0 ? (
  484. <ChildrenCountButton
  485. expanded={props.node.expanded || props.node.zoomedIn}
  486. onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
  487. >
  488. {props.node.children.length}{' '}
  489. </ChildrenCountButton>
  490. ) : null}
  491. </div>
  492. <span className="TraceOperation">{t('Trace')}</span>
  493. <strong className="TraceEmDash"> — </strong>
  494. <span className="TraceDescription">{props.trace_id}</span>
  495. </div>
  496. </div>
  497. <div
  498. ref={r =>
  499. props.viewManager.registerColumnRef(
  500. 'span_list',
  501. r,
  502. virtualizedIndex,
  503. props.node
  504. )
  505. }
  506. className="TraceRightColumn"
  507. style={{
  508. width: props.viewManager.columns.span_list.width * 100 + '%',
  509. backgroundColor:
  510. props.index % 2 ? undefined : props.theme.backgroundSecondary,
  511. }}
  512. >
  513. <TraceBar
  514. virtualizedIndex={virtualizedIndex}
  515. viewManager={props.viewManager}
  516. color={pickBarColor('missing-instrumentation')}
  517. node_space={props.node.space}
  518. />
  519. </div>
  520. </div>
  521. );
  522. }
  523. if (isTraceErrorNode(props.node)) {
  524. <div
  525. className="TraceRow"
  526. style={{
  527. top: props.style.top,
  528. height: props.style.height,
  529. }}
  530. >
  531. <div
  532. className="TraceLeftColumn"
  533. ref={r =>
  534. props.viewManager.registerColumnRef('list', r, virtualizedIndex, props.node)
  535. }
  536. style={{
  537. width:
  538. (props.viewManager.columns.list.width / props.viewManager.width) * 100 + '%',
  539. }}
  540. >
  541. <div
  542. className="TraceLeftColumnInner"
  543. style={{
  544. paddingLeft: props.node.depth * 23,
  545. }}
  546. >
  547. <div className="TraceChildrenCountWrapper">
  548. <Connectors node={props.node} />
  549. {props.node.children.length > 0 ? (
  550. <ChildrenCountButton
  551. expanded={props.node.expanded || props.node.zoomedIn}
  552. onClick={() => props.onExpandNode(props.node, !props.node.expanded)}
  553. >
  554. {props.node.children.length}{' '}
  555. </ChildrenCountButton>
  556. ) : null}
  557. </div>
  558. <span className="TraceOperation">{t('Error')}</span>
  559. <strong className="TraceEmDash"> — </strong>
  560. <span className="TraceDescription">{props.node.value.title}</span>
  561. </div>
  562. </div>
  563. <div
  564. ref={r =>
  565. props.viewManager.registerColumnRef(
  566. 'span_list',
  567. r,
  568. virtualizedIndex,
  569. props.node
  570. )
  571. }
  572. className="TraceRightColumn"
  573. style={{
  574. width: props.viewManager.columns.span_list.width * 100 + '%',
  575. backgroundColor: props.index % 2 ? undefined : props.theme.backgroundSecondary,
  576. }}
  577. >
  578. {/* @TODO: figure out what to do with trace errors */}
  579. {/* <TraceBar
  580. space={props.space}
  581. start_timestamp={props.node.value.start_timestamp}
  582. timestamp={props.node.value.timestamp}
  583. /> */}
  584. </div>
  585. </div>;
  586. }
  587. return null;
  588. }
  589. function RenderPlaceholderRow(props: {
  590. index: number;
  591. node: TraceTreeNode<TraceTree.NodeValue>;
  592. projects: Record<Project['slug'], Project>;
  593. startIndex: number;
  594. style: React.CSSProperties;
  595. theme: Theme;
  596. viewManager: VirtualizedViewManager;
  597. }) {
  598. const virtualizedIndex = props.index - props.startIndex;
  599. return (
  600. <div
  601. className="TraceRow"
  602. style={{
  603. top: props.style.top,
  604. height: props.style.height,
  605. pointerEvents: 'none',
  606. color: props.theme.subText,
  607. animationDelay: `${virtualizedIndex * 0.05}s`,
  608. paddingLeft: space(1),
  609. }}
  610. >
  611. <div
  612. className="TraceLeftColumn"
  613. ref={r =>
  614. props.viewManager.registerColumnRef('list', r, virtualizedIndex, props.node)
  615. }
  616. style={{width: props.viewManager.columns.list.width * 100 + '%'}}
  617. >
  618. <div
  619. className="TraceLeftColumnInner"
  620. style={{
  621. paddingLeft: props.node.depth * 23,
  622. }}
  623. >
  624. <div className="TraceChildrenCountWrapper">
  625. <Connectors node={props.node} />
  626. {props.node.children.length > 0 ? (
  627. <ChildrenCountButton
  628. expanded={props.node.expanded || props.node.zoomedIn}
  629. onClick={() => void 0}
  630. >
  631. {props.node.children.length}{' '}
  632. </ChildrenCountButton>
  633. ) : null}
  634. </div>
  635. {isTraceNode(props.node) ? <SmallLoadingIndicator /> : null}
  636. {isTraceNode(props.node) ? (
  637. 'Loading trace...'
  638. ) : (
  639. <Placeholder className="Placeholder" height="10px" width="86%" />
  640. )}
  641. </div>
  642. </div>
  643. <div
  644. className="TraceRightColumn"
  645. ref={r =>
  646. props.viewManager.registerColumnRef(
  647. 'span_list',
  648. r,
  649. virtualizedIndex,
  650. props.node
  651. )
  652. }
  653. style={{
  654. width: props.viewManager.columns.span_list.width * 100 + '%',
  655. }}
  656. >
  657. {isTraceNode(props.node) ? null : (
  658. <Placeholder
  659. className="Placeholder"
  660. height="14px"
  661. width="90%"
  662. style={{margin: 'auto'}}
  663. />
  664. )}
  665. </div>
  666. </div>
  667. );
  668. }
  669. function Connectors(props: {node: TraceTreeNode<TraceTree.NodeValue>}) {
  670. const showVerticalConnector =
  671. ((props.node.expanded || props.node.zoomedIn) && props.node.children.length > 0) ||
  672. (props.node.value && isParentAutogroupedNode(props.node));
  673. // If the tail node of the collapsed node has no children,
  674. // we don't want to render the vertical connector as no children
  675. // are being rendered as the chain is entirely collapsed
  676. const hideVerticalConnector =
  677. showVerticalConnector &&
  678. props.node.value &&
  679. props.node instanceof ParentAutogroupNode &&
  680. !props.node.tail.children.length;
  681. return (
  682. <Fragment>
  683. {/*
  684. @TODO count of rendered connectors could be % 3 as we can
  685. have up to 3 connectors per node, 1 div, 1 before and 1 after
  686. */}
  687. {props.node.connectors.map((c, i) => {
  688. return (
  689. <div
  690. key={i}
  691. style={{left: -(Math.abs(Math.abs(c) - props.node.depth) * 23)}}
  692. className={`TraceVerticalConnector ${c < 0 ? 'Orphaned' : ''}`}
  693. />
  694. );
  695. })}
  696. {showVerticalConnector && !hideVerticalConnector ? (
  697. <div className="TraceExpandedVerticalConnector" />
  698. ) : null}
  699. {props.node.isLastChild ? (
  700. <div className="TraceVerticalLastChildConnector" />
  701. ) : (
  702. <div className="TraceVerticalConnector" />
  703. )}
  704. </Fragment>
  705. );
  706. }
  707. function SmallLoadingIndicator() {
  708. return (
  709. <StyledLoadingIndicator
  710. style={{display: 'inline-block', margin: 0}}
  711. size={8}
  712. hideMessage
  713. relative
  714. />
  715. );
  716. }
  717. const StyledLoadingIndicator = styled(LoadingIndicator)`
  718. transform: translate(-5px, 0);
  719. div:first-child {
  720. border-left: 6px solid ${p => p.theme.gray300};
  721. animation: loading 900ms infinite linear;
  722. }
  723. `;
  724. function ProjectBadge(props: {project: Project}) {
  725. return <ProjectAvatar project={props.project} />;
  726. }
  727. function ChildrenCountButton(props: {
  728. children: React.ReactNode;
  729. expanded: boolean;
  730. onClick: () => void;
  731. }) {
  732. return (
  733. <button className="TraceChildrenCount" onClick={props.onClick}>
  734. {props.children}
  735. <IconChevron
  736. size="xs"
  737. direction={props.expanded ? 'up' : 'down'}
  738. style={{marginLeft: 2}}
  739. />
  740. </button>
  741. );
  742. }
  743. interface TraceBarProps {
  744. color: string;
  745. node_space: [number, number] | null;
  746. viewManager: VirtualizedViewManager;
  747. virtualizedIndex: number;
  748. }
  749. function TraceBar(props: TraceBarProps) {
  750. if (!props.node_space) {
  751. return null;
  752. }
  753. const spanTransform = props.viewManager.computeSpanMatrixTransform(props.node_space);
  754. const inverseTransform = props.viewManager.inverseSpanScaling(props.node_space);
  755. const textPosition = props.viewManager.computeSpanTextPlacement(
  756. spanTransform[4],
  757. props.node_space
  758. );
  759. return (
  760. <div
  761. ref={r =>
  762. props.viewManager.registerSpanBarRef(r, props.node_space!, props.virtualizedIndex)
  763. }
  764. className="TraceBar"
  765. style={{
  766. transform: `matrix(${spanTransform.join(',')})`,
  767. backgroundColor: props.color,
  768. }}
  769. >
  770. <div
  771. className={`TraceBarDuration ${textPosition === 'inside left' ? 'Inside' : ''}`}
  772. style={{
  773. left: textPosition === 'left' || textPosition === 'inside left' ? '0' : '100%',
  774. transform: `matrix(${inverseTransform}, 0,0,1,0,0) translate(${
  775. textPosition === 'left' ? 'calc(-100% - 4px)' : '4px'
  776. }, 0)`,
  777. }}
  778. >
  779. <PerformanceDuration seconds={props.node_space[1]} abbreviation />
  780. </div>
  781. </div>
  782. );
  783. }
  784. /**
  785. * This is a wrapper around the Trace component to apply styles
  786. * to the trace tree. It exists because we _do not_ want to trigger
  787. * emotion's css parsing logic as it is very slow and will cause
  788. * the scrolling to flicker.
  789. */
  790. const TraceStylingWrapper = styled('div')`
  791. position: relative;
  792. border: 1px solid ${p => p.theme.border};
  793. padding: ${space(0.5)} 0;
  794. border-radius: ${space(0.5)};
  795. @keyframes show {
  796. 0% {
  797. opacity: 0;
  798. transform: translate(0, 2px);
  799. }
  800. 100% {
  801. opacity: 0.7;
  802. transform: translate(0, 0px);
  803. }
  804. }
  805. @keyframes showPlaceholder {
  806. 0% {
  807. opacity: 0;
  808. transform: translate(-8px, 0px);
  809. }
  810. 100% {
  811. opacity: 0.7;
  812. transform: translate(0, 0px);
  813. }
  814. }
  815. &.Loading {
  816. .TraceRow {
  817. opacity: 0;
  818. animation: show 0.2s ease-in-out forwards;
  819. }
  820. .Placeholder {
  821. opacity: 0;
  822. transform: translate(-8px, 0px);
  823. animation: showPlaceholder 0.2s ease-in-out forwards;
  824. }
  825. }
  826. .TraceRow {
  827. display: flex;
  828. align-items: center;
  829. position: absolute;
  830. width: 100%;
  831. transition: background-color 0.15s ease-in-out 0s;
  832. font-size: ${p => p.theme.fontSizeSmall};
  833. &:hover {
  834. background-color: ${p => p.theme.backgroundSecondary};
  835. }
  836. &.Autogrouped {
  837. color: ${p => p.theme.blue300};
  838. .TraceDescription {
  839. font-weight: bold;
  840. }
  841. .TraceChildrenCountWrapper {
  842. button {
  843. color: ${p => p.theme.white};
  844. background-color: ${p => p.theme.blue300};
  845. }
  846. }
  847. }
  848. }
  849. .TraceLeftColumn {
  850. height: 100%;
  851. white-space: nowrap;
  852. display: flex;
  853. align-items: center;
  854. overflow: hidden;
  855. will-change: width;
  856. .TraceLeftColumnInner {
  857. height: 100%;
  858. white-space: nowrap;
  859. display: flex;
  860. align-items: center;
  861. will-change: transform;
  862. transform-origin: left center;
  863. transform: translateX(var(--column-translate-x));
  864. }
  865. }
  866. .TraceRightColumn {
  867. height: 100%;
  868. position: relative;
  869. display: flex;
  870. align-items: center;
  871. will-change: width;
  872. z-index: 1;
  873. }
  874. .TraceBar {
  875. position: absolute;
  876. height: 64%;
  877. width: 100%;
  878. background-color: black;
  879. transform-origin: left center;
  880. }
  881. .TraceBarDuration {
  882. display: inline-block;
  883. transform-origin: left center;
  884. font-size: ${p => p.theme.fontSizeExtraSmall};
  885. color: ${p => p.theme.gray300};
  886. white-space: nowrap;
  887. font-variant-numeric: tabular-nums;
  888. position: absolute;
  889. &.Inside {
  890. color: ${p => p.theme.gray100};
  891. }
  892. }
  893. .TraceChildrenCount {
  894. height: 16px;
  895. white-space: nowrap;
  896. min-width: 30px;
  897. display: flex;
  898. align-items: center;
  899. justify-content: center;
  900. border-radius: 99px;
  901. padding: 0px ${space(0.5)};
  902. transition: all 0.15s ease-in-out;
  903. background: ${p => p.theme.background};
  904. border: 2px solid ${p => p.theme.border};
  905. line-height: 0;
  906. z-index: 1;
  907. font-size: 10px;
  908. box-shadow: ${p => p.theme.dropShadowLight};
  909. margin-right: ${space(1)};
  910. svg {
  911. width: 7px;
  912. transition: none;
  913. }
  914. }
  915. .TraceChildrenCountWrapper {
  916. display: flex;
  917. justify-content: flex-end;
  918. align-items: center;
  919. min-width: 46px;
  920. height: 100%;
  921. position: relative;
  922. button {
  923. transition: none;
  924. }
  925. &.Orphaned {
  926. .TraceVerticalConnector,
  927. .TraceVerticalLastChildConnector,
  928. .TraceExpandedVerticalConnector {
  929. border-left: 2px dashed ${p => p.theme.border};
  930. }
  931. &::before {
  932. border-bottom: 2px dashed ${p => p.theme.border};
  933. }
  934. }
  935. &.Root {
  936. &:before,
  937. .TraceVerticalLastChildConnector {
  938. visibility: hidden;
  939. }
  940. }
  941. &::before {
  942. content: '';
  943. display: block;
  944. width: 60%;
  945. height: 2px;
  946. border-bottom: 2px solid ${p => p.theme.border};
  947. position: absolute;
  948. left: 0;
  949. top: 50%;
  950. transform: translateY(-50%);
  951. }
  952. &::after {
  953. content: '';
  954. background-color: rgb(224, 220, 229);
  955. border-radius: 50%;
  956. height: 6px;
  957. width: 6px;
  958. position: absolute;
  959. left: 60%;
  960. top: 50%;
  961. transform: translateY(-50%);
  962. }
  963. }
  964. .TraceVerticalConnector {
  965. position: absolute;
  966. left: 0;
  967. top: 0;
  968. bottom: 0;
  969. height: 100%;
  970. width: 2px;
  971. border-left: 2px solid ${p => p.theme.border};
  972. &.Orphaned {
  973. border-left: 2px dashed ${p => p.theme.border};
  974. }
  975. }
  976. .TraceVerticalLastChildConnector {
  977. position: absolute;
  978. left: 0;
  979. top: 0;
  980. bottom: 0;
  981. height: 50%;
  982. width: 2px;
  983. border-left: 2px solid ${p => p.theme.border};
  984. border-bottom-left-radius: 4px;
  985. }
  986. .TraceExpandedVerticalConnector {
  987. position: absolute;
  988. bottom: 0;
  989. height: 50%;
  990. left: 50%;
  991. width: 2px;
  992. border-left: 2px solid ${p => p.theme.border};
  993. }
  994. .TraceOperation {
  995. margin-left: ${space(0.5)};
  996. text-overflow: ellipsis;
  997. white-space: nowrap;
  998. font-weight: bold;
  999. }
  1000. .TraceEmDash {
  1001. margin-left: ${space(0.5)};
  1002. margin-right: ${space(0.5)};
  1003. }
  1004. .TraceDescription {
  1005. white-space: nowrap;
  1006. }
  1007. `;