trace.tsx 27 KB

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