trace.tsx 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692
  1. import type React from 'react';
  2. import {
  3. Fragment,
  4. useCallback,
  5. useEffect,
  6. useLayoutEffect,
  7. useMemo,
  8. useRef,
  9. useState,
  10. } from 'react';
  11. import {type Theme, useTheme} from '@emotion/react';
  12. import styled from '@emotion/styled';
  13. import {Tooltip} from 'sentry/components/tooltip';
  14. import ConfigStore from 'sentry/stores/configStore';
  15. import {useLegacyStore} from 'sentry/stores/useLegacyStore';
  16. import {space} from 'sentry/styles/space';
  17. import type {Organization} from 'sentry/types/organization';
  18. import type {PlatformKey, Project} from 'sentry/types/project';
  19. import {trackAnalytics} from 'sentry/utils/analytics';
  20. import {formatTraceDuration} from 'sentry/utils/duration/formatTraceDuration';
  21. import {WEB_VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants';
  22. import {replayPlayerTimestampEmitter} from 'sentry/utils/replays/replayPlayerTimestampEmitter';
  23. import useApi from 'sentry/utils/useApi';
  24. import useOrganization from 'sentry/utils/useOrganization';
  25. import useProjects from 'sentry/utils/useProjects';
  26. import {
  27. getFormattedDuration,
  28. WEB_VITALS_METERS_CONFIG,
  29. } from 'sentry/views/insights/browser/webVitals/components/webVitalMeters';
  30. import type {WebVitals} from 'sentry/views/insights/browser/webVitals/types';
  31. import {
  32. scoreToStatus,
  33. STATUS_TEXT,
  34. } from 'sentry/views/insights/browser/webVitals/utils/scoreToStatus';
  35. import {TraceTree} from './traceModels/traceTree';
  36. import type {TraceTreeNode} from './traceModels/traceTreeNode';
  37. import type {TraceEvents, TraceScheduler} from './traceRenderers/traceScheduler';
  38. import {
  39. useVirtualizedList,
  40. type VirtualizedRow,
  41. } from './traceRenderers/traceVirtualizedList';
  42. import type {VirtualizedViewManager} from './traceRenderers/virtualizedViewManager';
  43. import {TraceAutogroupedRow} from './traceRow/traceAutogroupedRow';
  44. import {TraceCollapsedRow} from './traceRow/traceCollapsedRow';
  45. import {TraceErrorRow} from './traceRow/traceErrorRow';
  46. import {TraceLoadingRow} from './traceRow/traceLoadingRow';
  47. import {TraceMissingInstrumentationRow} from './traceRow/traceMissingInstrumentationRow';
  48. import {TraceRootRow} from './traceRow/traceRootNode';
  49. import {
  50. TRACE_CHILDREN_COUNT_WRAPPER_CLASSNAME,
  51. TRACE_CHILDREN_COUNT_WRAPPER_ORPHANED_CLASSNAME,
  52. TRACE_RIGHT_COLUMN_EVEN_CLASSNAME,
  53. TRACE_RIGHT_COLUMN_ODD_CLASSNAME,
  54. type TraceRowProps,
  55. } from './traceRow/traceRow';
  56. import {TraceSpanRow} from './traceRow/traceSpanRow';
  57. import {TraceTransactionRow} from './traceRow/traceTransactionRow';
  58. import {
  59. getRovingIndexActionFromDOMEvent,
  60. type RovingTabIndexUserActions,
  61. } from './traceState/traceRovingTabIndex';
  62. import {useTraceState, useTraceStateDispatch} from './traceState/traceStateProvider';
  63. import {
  64. isAutogroupedNode,
  65. isCollapsedNode,
  66. isMissingInstrumentationNode,
  67. isSpanNode,
  68. isTraceErrorNode,
  69. isTraceNode,
  70. isTransactionNode,
  71. } from './traceGuards';
  72. import type {TraceReducerState} from './traceState';
  73. function computeNextIndexFromAction(
  74. current_index: number,
  75. action: RovingTabIndexUserActions,
  76. items: number
  77. ): number {
  78. switch (action) {
  79. case 'next':
  80. if (current_index === items) {
  81. return 0;
  82. }
  83. return current_index + 1;
  84. case 'previous':
  85. if (current_index === 0) {
  86. return items;
  87. }
  88. return current_index - 1;
  89. case 'last':
  90. return items;
  91. case 'first':
  92. return 0;
  93. default:
  94. throw new TypeError(`Invalid or not implemented reducer action - ${action}`);
  95. }
  96. }
  97. interface TraceProps {
  98. forceRerender: number;
  99. isLoading: boolean;
  100. manager: VirtualizedViewManager;
  101. onRowClick: (
  102. node: TraceTreeNode<TraceTree.NodeValue>,
  103. event: React.MouseEvent<HTMLElement>,
  104. index: number
  105. ) => void;
  106. onTraceSearch: (
  107. query: string,
  108. node: TraceTreeNode<TraceTree.NodeValue>,
  109. behavior: 'track result' | 'persist'
  110. ) => void;
  111. previouslyFocusedNodeRef: React.MutableRefObject<TraceTreeNode<TraceTree.NodeValue> | null>;
  112. rerender: () => void;
  113. scheduler: TraceScheduler;
  114. trace: TraceTree;
  115. trace_id: string | undefined;
  116. }
  117. export function Trace({
  118. trace,
  119. onRowClick,
  120. manager,
  121. previouslyFocusedNodeRef,
  122. onTraceSearch,
  123. rerender,
  124. scheduler,
  125. forceRerender,
  126. trace_id,
  127. isLoading,
  128. }: TraceProps) {
  129. const theme = useTheme();
  130. const api = useApi();
  131. const {projects} = useProjects();
  132. const organization = useOrganization();
  133. const traceState = useTraceState();
  134. const traceDispatch = useTraceStateDispatch();
  135. const {theme: colorMode} = useLegacyStore(ConfigStore);
  136. const rerenderRef = useRef<TraceProps['rerender']>(rerender);
  137. rerenderRef.current = rerender;
  138. const treePromiseStatusRef =
  139. useRef<Map<TraceTreeNode<TraceTree.NodeValue>, 'loading' | 'error' | 'success'>>();
  140. if (!treePromiseStatusRef.current) {
  141. treePromiseStatusRef.current = new Map();
  142. }
  143. const treeRef = useRef<TraceTree>(trace);
  144. treeRef.current = trace;
  145. const traceStateRef = useRef<TraceReducerState>(traceState);
  146. traceStateRef.current = traceState;
  147. const traceStatePreferencesRef = useRef<
  148. Pick<TraceReducerState['preferences'], 'autogroup' | 'missing_instrumentation'>
  149. >(traceState.preferences);
  150. traceStatePreferencesRef.current = traceState.preferences;
  151. useLayoutEffect(() => {
  152. const onTraceViewChange: TraceEvents['set trace view'] = () => {
  153. manager.recomputeTimelineIntervals();
  154. manager.recomputeSpanToPXMatrix();
  155. manager.syncResetZoomButton();
  156. manager.draw();
  157. };
  158. const onPhysicalSpaceChange: TraceEvents['set container physical space'] = () => {
  159. manager.recomputeTimelineIntervals();
  160. manager.recomputeSpanToPXMatrix();
  161. manager.draw();
  162. };
  163. const onTraceSpaceChange: TraceEvents['initialize trace space'] = () => {
  164. manager.recomputeTimelineIntervals();
  165. manager.recomputeSpanToPXMatrix();
  166. manager.draw();
  167. };
  168. const onDividerResize: TraceEvents['divider resize'] = view => {
  169. manager.recomputeTimelineIntervals();
  170. manager.recomputeSpanToPXMatrix();
  171. manager.draw(view);
  172. };
  173. scheduler.on('set trace view', onTraceViewChange);
  174. scheduler.on('set trace space', onTraceSpaceChange);
  175. scheduler.on('set container physical space', onPhysicalSpaceChange);
  176. scheduler.on('initialize trace space', onTraceSpaceChange);
  177. scheduler.on('divider resize', onDividerResize);
  178. return () => {
  179. scheduler.off('set trace view', onTraceViewChange);
  180. scheduler.off('set trace space', onTraceSpaceChange);
  181. scheduler.off('set container physical space', onPhysicalSpaceChange);
  182. scheduler.off('initialize trace space', onTraceSpaceChange);
  183. scheduler.off('divider resize', onDividerResize);
  184. };
  185. }, [manager, scheduler]);
  186. const onNodeZoomIn = useCallback(
  187. (
  188. event: React.MouseEvent<Element> | React.KeyboardEvent<Element>,
  189. node: TraceTreeNode<TraceTree.NodeValue>,
  190. value: boolean
  191. ) => {
  192. if (!isTransactionNode(node) && !isSpanNode(node)) {
  193. throw new TypeError('Node must be a transaction or span');
  194. }
  195. event.stopPropagation();
  196. rerenderRef.current();
  197. treeRef.current
  198. .zoom(node, value, {
  199. api,
  200. organization,
  201. preferences: traceStatePreferencesRef.current,
  202. })
  203. .then(() => {
  204. rerenderRef.current();
  205. // If a query exists, we want to reapply the search after zooming in
  206. // so that new nodes are also highlighted if they match a query
  207. if (traceStateRef.current.search.query) {
  208. onTraceSearch(traceStateRef.current.search.query, node, 'persist');
  209. }
  210. treePromiseStatusRef.current!.set(node, 'success');
  211. })
  212. .catch(_e => {
  213. treePromiseStatusRef.current!.set(node, 'error');
  214. });
  215. },
  216. [api, organization, onTraceSearch]
  217. );
  218. const onNodeExpand = useCallback(
  219. (
  220. event: React.MouseEvent<Element> | React.KeyboardEvent<Element>,
  221. node: TraceTreeNode<TraceTree.NodeValue>,
  222. value: boolean
  223. ) => {
  224. event.stopPropagation();
  225. treeRef.current.expand(node, value);
  226. rerenderRef.current();
  227. if (traceStateRef.current.search.query) {
  228. // If a query exists, we want to reapply the search after expanding
  229. // so that new nodes are also highlighted if they match a query
  230. onTraceSearch(traceStateRef.current.search.query, node, 'persist');
  231. }
  232. },
  233. [onTraceSearch]
  234. );
  235. const onRowKeyDown = useCallback(
  236. (
  237. event: React.KeyboardEvent,
  238. index: number,
  239. node: TraceTreeNode<TraceTree.NodeValue>
  240. ) => {
  241. if (!manager.list) {
  242. return;
  243. }
  244. const action = getRovingIndexActionFromDOMEvent(event);
  245. if (action) {
  246. event.preventDefault();
  247. const nextIndex = computeNextIndexFromAction(
  248. index,
  249. action,
  250. treeRef.current.list.length - 1
  251. );
  252. traceDispatch({
  253. type: 'set roving index',
  254. index: nextIndex,
  255. node: treeRef.current.list[nextIndex]!,
  256. action_source: 'keyboard',
  257. });
  258. }
  259. if (event.key === 'ArrowLeft') {
  260. if (node.zoomedIn) {
  261. onNodeZoomIn(event, node, false);
  262. } else if (node.expanded) {
  263. onNodeExpand(event, node, false);
  264. }
  265. } else if (event.key === 'ArrowRight') {
  266. if (node.canFetch) {
  267. onNodeZoomIn(event, node, true);
  268. } else {
  269. onNodeExpand(event, node, true);
  270. }
  271. }
  272. },
  273. [manager, onNodeExpand, onNodeZoomIn, traceDispatch]
  274. );
  275. const projectLookup: Record<string, PlatformKey | undefined> = useMemo(() => {
  276. return projects.reduce<Record<Project['slug'], Project['platform']>>(
  277. (acc, project) => {
  278. acc[project.slug] = project.platform;
  279. return acc;
  280. },
  281. {}
  282. );
  283. }, [projects]);
  284. const renderLoadingRow = useCallback(
  285. (n: VirtualizedRow) => {
  286. return (
  287. <TraceLoadingRow
  288. key={n.key}
  289. index={n.index}
  290. style={n.style}
  291. node={n.item}
  292. theme={theme}
  293. manager={manager}
  294. />
  295. );
  296. },
  297. [manager, theme]
  298. );
  299. const renderVirtualizedRow = useCallback(
  300. (n: VirtualizedRow) => {
  301. return (
  302. <RenderTraceRow
  303. key={n.key}
  304. index={n.index}
  305. organization={organization}
  306. previouslyFocusedNodeRef={previouslyFocusedNodeRef}
  307. tabIndex={traceState.rovingTabIndex.node === n.item ? 0 : -1}
  308. isSearchResult={traceState.search.resultsLookup.has(n.item)}
  309. searchResultsIteratorIndex={traceState.search.resultIndex}
  310. style={n.style}
  311. projects={projectLookup}
  312. node={n.item}
  313. manager={manager}
  314. theme={theme}
  315. onExpand={onNodeExpand}
  316. onZoomIn={onNodeZoomIn}
  317. onRowClick={onRowClick}
  318. onRowKeyDown={onRowKeyDown}
  319. tree={trace}
  320. trace_id={trace_id}
  321. />
  322. );
  323. },
  324. // we add forceRerender as a "unnecessary" dependency to trigger the virtualized list rerender
  325. // eslint-disable-next-line react-hooks/exhaustive-deps
  326. [
  327. onNodeExpand,
  328. onNodeZoomIn,
  329. manager,
  330. previouslyFocusedNodeRef,
  331. onRowKeyDown,
  332. onRowClick,
  333. organization,
  334. projectLookup,
  335. traceState.rovingTabIndex.node,
  336. traceState.search.resultIteratorIndex,
  337. traceState.search.resultsLookup,
  338. traceState.search.resultIndex,
  339. theme,
  340. trace.type,
  341. forceRerender,
  342. ]
  343. );
  344. const render = useMemo(() => {
  345. return trace.type !== 'trace' || isLoading
  346. ? (r: any) => renderLoadingRow(r)
  347. : (r: any) => renderVirtualizedRow(r);
  348. }, [isLoading, renderLoadingRow, renderVirtualizedRow, trace.type]);
  349. const traceNode = trace.root.children[0];
  350. const traceStartTimestamp = traceNode?.space?.[0];
  351. const [scrollContainer, setScrollContainer] = useState<HTMLElement | null>(null);
  352. const virtualizedList = useVirtualizedList({
  353. manager,
  354. items: trace.list,
  355. container: scrollContainer,
  356. render,
  357. scheduler,
  358. });
  359. return (
  360. <TraceStylingWrapper
  361. ref={manager.registerContainerRef}
  362. className={`
  363. ${trace.root.space[1] === 0 ? 'Empty' : ''}
  364. ${trace.indicators.length > 0 ? 'WithIndicators' : ''}
  365. ${trace.type !== 'trace' || isLoading ? 'Loading' : ''}
  366. ${ConfigStore.get('theme')}`}
  367. >
  368. <div
  369. className="TraceScrollbarContainer"
  370. ref={manager.registerHorizontalScrollBarContainerRef}
  371. >
  372. <div className="TraceScrollbarScroller" />
  373. </div>
  374. <div className="TraceDivider" ref={manager.registerDividerRef} />
  375. <div
  376. className="TraceIndicatorsContainer"
  377. ref={manager.registerIndicatorContainerRef}
  378. >
  379. <div className="TraceIndicatorContainerMiddleLine" />
  380. {trace.indicators.length > 0
  381. ? trace.indicators.map((indicator, i) => {
  382. const status =
  383. indicator.score === undefined
  384. ? 'none'
  385. : STATUS_TEXT[scoreToStatus(indicator.score)];
  386. const webvital = indicator.label.toLowerCase() as WebVitals;
  387. const defaultFormatter = (value: number) =>
  388. getFormattedDuration(value / 1000);
  389. const formatter =
  390. WEB_VITALS_METERS_CONFIG[webvital]?.formatter ?? defaultFormatter;
  391. return (
  392. <Fragment key={i}>
  393. <div
  394. key={i}
  395. ref={r => manager.registerIndicatorLabelRef(r, i, indicator)}
  396. className={`TraceIndicatorLabelContainer ${status} ${colorMode}`}
  397. >
  398. <Tooltip
  399. title={
  400. <div>
  401. {WEB_VITAL_DETAILS[`measurements.${webvital}`]?.name}
  402. <br />
  403. {formatter(indicator.measurement.value)} - {status}
  404. </div>
  405. }
  406. >
  407. <div className="TraceIndicatorLabel">{indicator.label}</div>
  408. </Tooltip>
  409. </div>
  410. <div
  411. ref={r => manager.registerIndicatorRef(r, i, indicator)}
  412. className={`TraceIndicator ${indicator.poor ? 'Errored' : ''}`}
  413. >
  414. <div className={`TraceIndicatorLine ${status}`} />
  415. </div>
  416. </Fragment>
  417. );
  418. })
  419. : null}
  420. {manager.interval_bars.map((_, i) => {
  421. const indicatorTimestamp = manager.intervals[i] ?? 0;
  422. if (trace.type !== 'trace' || isLoading) {
  423. return null;
  424. }
  425. return (
  426. <div
  427. key={i}
  428. ref={r => manager.registerTimelineIndicatorRef(r, i)}
  429. className="TraceIndicator Timeline"
  430. >
  431. <div className="TraceIndicatorLabelContainer">
  432. {indicatorTimestamp > 0
  433. ? formatTraceDuration(manager.view.trace_view.x + indicatorTimestamp)
  434. : '0s'}
  435. </div>
  436. <div className="TraceIndicatorLine" />
  437. </div>
  438. );
  439. })}
  440. {traceNode && traceStartTimestamp ? (
  441. <VerticalTimestampIndicators
  442. viewmanager={manager}
  443. traceStartTimestamp={traceStartTimestamp}
  444. />
  445. ) : null}
  446. </div>
  447. <div
  448. ref={setScrollContainer}
  449. data-test-id="trace-virtualized-list-scroll-container"
  450. >
  451. <div data-test-id="trace-virtualized-list">{virtualizedList.rendered}</div>
  452. <div className="TraceRow Hidden">
  453. <div
  454. className="TraceLeftColumn"
  455. ref={r => manager.registerGhostRowRef('list', r)}
  456. />
  457. <div
  458. className="TraceRightColumn"
  459. ref={r => manager.registerGhostRowRef('span_list', r)}
  460. />
  461. </div>
  462. </div>
  463. </TraceStylingWrapper>
  464. );
  465. }
  466. function RenderTraceRow(props: {
  467. index: number;
  468. isSearchResult: boolean;
  469. manager: VirtualizedViewManager;
  470. node: TraceTreeNode<TraceTree.NodeValue>;
  471. onExpand: (
  472. event: React.MouseEvent<Element>,
  473. node: TraceTreeNode<TraceTree.NodeValue>,
  474. value: boolean
  475. ) => void;
  476. onRowClick: (
  477. node: TraceTreeNode<TraceTree.NodeValue>,
  478. event: React.MouseEvent<HTMLElement>,
  479. index: number
  480. ) => void;
  481. onRowKeyDown: (
  482. event: React.KeyboardEvent,
  483. index: number,
  484. node: TraceTreeNode<TraceTree.NodeValue>
  485. ) => void;
  486. onZoomIn: (
  487. event: React.MouseEvent<Element>,
  488. node: TraceTreeNode<TraceTree.NodeValue>,
  489. value: boolean
  490. ) => void;
  491. organization: Organization;
  492. previouslyFocusedNodeRef: React.MutableRefObject<TraceTreeNode<TraceTree.NodeValue> | null>;
  493. projects: Record<Project['slug'], Project['platform']>;
  494. searchResultsIteratorIndex: number | null;
  495. style: React.CSSProperties;
  496. tabIndex: number;
  497. theme: Theme;
  498. trace_id: string | undefined;
  499. tree: TraceTree;
  500. }) {
  501. const node = props.node;
  502. const virtualized_index = props.index - props.manager.start_virtualized_index;
  503. const rowSearchClassName = `${props.isSearchResult ? 'SearchResult' : ''} ${props.searchResultsIteratorIndex === props.index ? 'Highlight' : ''}`;
  504. const registerListColumnRef = useCallback(
  505. (ref: HTMLDivElement | null) => {
  506. props.manager.registerColumnRef('list', ref, virtualized_index, node);
  507. },
  508. [props.manager, node, virtualized_index]
  509. );
  510. const registerSpanColumnRef = useCallback(
  511. (ref: HTMLDivElement | null) => {
  512. props.manager.registerColumnRef('span_list', ref, virtualized_index, node);
  513. },
  514. [props.manager, node, virtualized_index]
  515. );
  516. const registerSpanArrowRef = useCallback(
  517. (ref: any) => {
  518. props.manager.registerArrowRef(ref, node.space!, virtualized_index);
  519. },
  520. [props.manager, node, virtualized_index]
  521. );
  522. const onRowClickProp = props.onRowClick;
  523. const onRowClick = useCallback(
  524. (event: React.MouseEvent<HTMLElement>) => {
  525. onRowClickProp(node, event, props.index);
  526. },
  527. [props.index, node, onRowClickProp]
  528. );
  529. const onRowKeyDownProp = props.onRowKeyDown;
  530. const onRowKeyDown = useCallback(
  531. (event: React.KeyboardEvent) => onRowKeyDownProp(event, props.index, node),
  532. [props.index, node, onRowKeyDownProp]
  533. );
  534. const onRowDoubleClick = useCallback(
  535. (e: React.MouseEvent) => {
  536. trackAnalytics('trace.trace_layout.zoom_to_fill', {
  537. organization: props.organization,
  538. });
  539. e.stopPropagation();
  540. props.manager.onZoomIntoSpace(node.space!);
  541. },
  542. [node, props.manager, props.organization]
  543. );
  544. const onSpanRowArrowClick = useCallback(
  545. (_e: React.MouseEvent) => {
  546. props.manager.onBringRowIntoView(node.space!);
  547. },
  548. [node.space, props.manager]
  549. );
  550. const onExpandProp = props.onExpand;
  551. const onExpand = useCallback(
  552. (e: React.MouseEvent) => {
  553. onExpandProp(e, node, !node.expanded);
  554. },
  555. [node, onExpandProp]
  556. );
  557. const onZoomInProp = props.onZoomIn;
  558. const onZoomIn = useCallback(
  559. (e: React.MouseEvent) => {
  560. onZoomInProp(e, node, !node.zoomedIn);
  561. },
  562. [node, onZoomInProp]
  563. );
  564. const onExpandDoubleClick = useCallback((e: React.MouseEvent) => {
  565. e.stopPropagation();
  566. }, []);
  567. const spanColumnClassName =
  568. props.index % 2 === 1
  569. ? TRACE_RIGHT_COLUMN_ODD_CLASSNAME
  570. : TRACE_RIGHT_COLUMN_EVEN_CLASSNAME;
  571. const listColumnClassName = isTraceNode(node)
  572. ? TRACE_CHILDREN_COUNT_WRAPPER_ORPHANED_CLASSNAME
  573. : TRACE_CHILDREN_COUNT_WRAPPER_CLASSNAME;
  574. const listColumnStyle: React.CSSProperties = {
  575. paddingLeft: TraceTree.Depth(node) * props.manager.row_depth_padding,
  576. };
  577. const rowProps: TraceRowProps<TraceTreeNode<TraceTree.NodeValue>> = {
  578. onExpand,
  579. onZoomIn,
  580. onRowClick,
  581. onRowKeyDown,
  582. previouslyFocusedNodeRef: props.previouslyFocusedNodeRef,
  583. onSpanArrowClick: onSpanRowArrowClick,
  584. manager: props.manager,
  585. index: props.index,
  586. theme: props.theme,
  587. style: props.style,
  588. projects: props.projects,
  589. tabIndex: props.tabIndex,
  590. onRowDoubleClick,
  591. trace_id: props.trace_id,
  592. node: props.node,
  593. virtualized_index,
  594. listColumnStyle,
  595. listColumnClassName,
  596. spanColumnClassName,
  597. onExpandDoubleClick,
  598. rowSearchClassName,
  599. registerListColumnRef,
  600. registerSpanColumnRef,
  601. registerSpanArrowRef,
  602. };
  603. if (isTransactionNode(node)) {
  604. return <TraceTransactionRow {...rowProps} node={node} />;
  605. }
  606. if (isSpanNode(node)) {
  607. return <TraceSpanRow {...rowProps} node={node} />;
  608. }
  609. if (isMissingInstrumentationNode(node)) {
  610. return <TraceMissingInstrumentationRow {...rowProps} node={node} />;
  611. }
  612. if (isAutogroupedNode(node)) {
  613. return <TraceAutogroupedRow {...rowProps} node={node} />;
  614. }
  615. if (isTraceErrorNode(node)) {
  616. return <TraceErrorRow {...rowProps} node={node} />;
  617. }
  618. if (isTraceNode(node)) {
  619. return <TraceRootRow {...rowProps} node={node} />;
  620. }
  621. if (isCollapsedNode(node)) {
  622. return <TraceCollapsedRow {...rowProps} node={node} />;
  623. }
  624. return null;
  625. }
  626. function VerticalTimestampIndicators({
  627. viewmanager,
  628. traceStartTimestamp,
  629. }: {
  630. traceStartTimestamp: number;
  631. viewmanager: VirtualizedViewManager;
  632. }) {
  633. useEffect(() => {
  634. function replayTimestampListener({
  635. currentTime,
  636. currentHoverTime,
  637. }: {
  638. currentHoverTime: number | undefined;
  639. currentTime: number;
  640. }) {
  641. if (viewmanager.vertical_indicators['replay_timestamp.current']) {
  642. viewmanager.vertical_indicators['replay_timestamp.current'].timestamp =
  643. traceStartTimestamp + currentTime;
  644. }
  645. if (viewmanager.vertical_indicators['replay_timestamp.hover']) {
  646. viewmanager.vertical_indicators['replay_timestamp.hover'].timestamp =
  647. currentHoverTime ? traceStartTimestamp + currentHoverTime : undefined;
  648. }
  649. // When timestamp is changing, it needs to be redrawn
  650. // if it is out of bounds, we need to scroll to it
  651. viewmanager.drawVerticalIndicators();
  652. viewmanager.maybeSyncViewWithVerticalIndicator('replay_timestamp.current');
  653. }
  654. replayPlayerTimestampEmitter.on('replay timestamp change', replayTimestampListener);
  655. return () => {
  656. replayPlayerTimestampEmitter.off(
  657. 'replay timestamp change',
  658. replayTimestampListener
  659. );
  660. };
  661. }, [traceStartTimestamp, viewmanager]);
  662. const registerReplayCurrentTimestampRef = useCallback(
  663. (ref: HTMLDivElement | null) => {
  664. viewmanager.registerVerticalIndicator('replay_timestamp.current', {
  665. ref,
  666. timestamp: undefined,
  667. });
  668. },
  669. [viewmanager]
  670. );
  671. const registerReplayHoverTimestampRef = useCallback(
  672. (ref: HTMLDivElement | null) => {
  673. viewmanager.registerVerticalIndicator('replay_timestamp.hover', {
  674. ref,
  675. timestamp: undefined,
  676. });
  677. },
  678. [viewmanager]
  679. );
  680. return (
  681. <Fragment>
  682. <div ref={registerReplayCurrentTimestampRef} className="TraceIndicator Timeline">
  683. <div className="Indicator CurrentReplayTimestamp" />
  684. </div>
  685. <div ref={registerReplayHoverTimestampRef} className="TraceIndicator Timeline">
  686. <div className="Indicator HoverReplayTimestamp" />
  687. </div>
  688. </Fragment>
  689. );
  690. }
  691. /**
  692. * This is a wrapper around the Trace component to apply styles
  693. * to the trace tree. It exists because we _do not_ want to trigger
  694. * emotion's css parsing logic as it is very slow and will cause
  695. * the scrolling to flicker.
  696. */
  697. const TraceStylingWrapper = styled('div')`
  698. margin: auto;
  699. overscroll-behavior: none;
  700. box-shadow: 0 0 0 1px ${p => p.theme.border};
  701. position: absolute;
  702. left: 0;
  703. top: 0;
  704. width: 100%;
  705. height: 100%;
  706. grid-area: trace;
  707. padding-top: 38px;
  708. &.WithIndicators {
  709. &:before {
  710. background-color: ${p => p.theme.background};
  711. height: 38px;
  712. .TraceScrollbarContainer {
  713. height: 44px;
  714. }
  715. }
  716. .TraceIndicator.Timeline {
  717. .TraceIndicatorLabelContainer {
  718. top: 22px;
  719. }
  720. .TraceIndicatorLine {
  721. top: 30px;
  722. }
  723. .Indicator {
  724. top: 44px;
  725. }
  726. }
  727. }
  728. &:before {
  729. content: '';
  730. position: absolute;
  731. left: 0;
  732. top: 0;
  733. width: 100%;
  734. height: 38px;
  735. background-color: ${p => p.theme.background};
  736. border-bottom: 1px solid ${p => p.theme.border};
  737. z-index: 10;
  738. }
  739. &.Loading {
  740. .TraceRow {
  741. .TraceLeftColumnInner {
  742. width: 100%;
  743. }
  744. }
  745. .TraceRightColumn {
  746. background-color: transparent !important;
  747. }
  748. .TraceDivider {
  749. pointer-events: none;
  750. }
  751. }
  752. &.Empty {
  753. .TraceIcon {
  754. left: 50%;
  755. }
  756. }
  757. .TraceScrollbarContainer {
  758. left: 0;
  759. top: 0;
  760. height: 38px;
  761. position: absolute;
  762. overflow-x: auto;
  763. overscroll-behavior: none;
  764. will-change: transform;
  765. .TraceScrollbarScroller {
  766. height: 1px;
  767. pointer-events: none;
  768. visibility: hidden;
  769. }
  770. .TraceScrollbarHandle {
  771. width: 24px;
  772. height: 12px;
  773. border-radius: 6px;
  774. }
  775. }
  776. .TraceDivider {
  777. position: absolute;
  778. height: 100%;
  779. background-color: transparent;
  780. top: 0;
  781. cursor: ew-resize;
  782. z-index: 10;
  783. &:before {
  784. content: '';
  785. position: absolute;
  786. width: 1px;
  787. height: 100%;
  788. background-color: ${p => p.theme.border};
  789. left: 50%;
  790. }
  791. &:hover {
  792. &:before {
  793. background-color: ${p => p.theme.purple300};
  794. }
  795. }
  796. }
  797. .TraceIndicatorsContainer {
  798. overflow: hidden;
  799. width: 100%;
  800. height: 100%;
  801. position: absolute;
  802. right: 0;
  803. top: 0;
  804. z-index: 10;
  805. pointer-events: none;
  806. }
  807. .TraceIndicatorContainerMiddleLine {
  808. position: absolute;
  809. top: 18px;
  810. background-color: ${p => p.theme.border};
  811. width: 100%;
  812. height: 1px;
  813. }
  814. .TraceIndicatorLabelContainer {
  815. min-width: 34px;
  816. text-align: center;
  817. position: absolute;
  818. font-size: 10px;
  819. font-weight: ${p => p.theme.fontWeightBold};
  820. color: ${p => p.theme.textColor};
  821. background-color: ${p => p.theme.background};
  822. border-radius: 100px;
  823. border: 1px solid ${p => p.theme.border};
  824. display: inline-block;
  825. line-height: 1;
  826. margin-top: 2px;
  827. white-space: nowrap;
  828. pointer-events: auto;
  829. cursor: pointer;
  830. &:hover {
  831. z-index: 10;
  832. & + div {
  833. z-index: 10;
  834. }
  835. }
  836. &.Poor {
  837. color: ${p => p.theme.red300};
  838. border: 1px solid ${p => p.theme.red300};
  839. &.light {
  840. background-color: rgb(251 232 233);
  841. }
  842. &.dark {
  843. background-color: rgb(63 17 20);
  844. }
  845. }
  846. &.Meh {
  847. color: ${p => p.theme.yellow400};
  848. border: 1px solid ${p => p.theme.yellow300};
  849. &.light {
  850. background-color: rgb(249 244 224);
  851. }
  852. &.dark {
  853. background-color: rgb(45 41 17);
  854. }
  855. }
  856. &.Good {
  857. color: ${p => p.theme.green300};
  858. border: 1px solid ${p => p.theme.green300};
  859. &.light {
  860. background-color: rgb(232 241 239);
  861. }
  862. &.dark {
  863. background-color: rgb(9 37 30);
  864. }
  865. }
  866. }
  867. .TraceIndicatorLabel {
  868. padding: 2px;
  869. border-radius: 100px;
  870. }
  871. .TraceIndicator {
  872. width: 3px;
  873. height: 100%;
  874. top: 0;
  875. position: absolute;
  876. .TraceIndicatorLine {
  877. width: 1px;
  878. height: 100%;
  879. top: 20px;
  880. position: absolute;
  881. left: 50%;
  882. transform: translate(-2px, -7px);
  883. background: repeating-linear-gradient(
  884. to bottom,
  885. transparent 0 4px,
  886. ${p => p.theme.textColor} 4px 8px
  887. )
  888. 80%/2px 100% no-repeat;
  889. &.Poor {
  890. background: repeating-linear-gradient(
  891. to bottom,
  892. transparent 0 4px,
  893. ${p => p.theme.red300} 4px 8px
  894. )
  895. 80%/2px 100% no-repeat;
  896. }
  897. &.Meh {
  898. background: repeating-linear-gradient(
  899. to bottom,
  900. transparent 0 4px,
  901. ${p => p.theme.yellow300} 4px 8px
  902. )
  903. 80%/2px 100% no-repeat;
  904. }
  905. &.Good {
  906. background: repeating-linear-gradient(
  907. to bottom,
  908. transparent 0 4px,
  909. ${p => p.theme.green300} 4px 8px
  910. )
  911. 80%/2px 100% no-repeat;
  912. }
  913. }
  914. .Indicator {
  915. width: 1px;
  916. height: 100%;
  917. position: absolute;
  918. left: 50%;
  919. transform: translateX(-2px);
  920. top: 26px;
  921. &.CurrentReplayTimestamp {
  922. background: ${p => p.theme.purple300};
  923. }
  924. &.HoverReplayTimestamp {
  925. background: ${p => p.theme.purple200};
  926. }
  927. }
  928. &.Errored {
  929. .TraceIndicatorLabelContainer {
  930. border: 1px solid ${p => p.theme.error};
  931. color: ${p => p.theme.error};
  932. }
  933. .TraceIndicatorLine {
  934. background: repeating-linear-gradient(
  935. to bottom,
  936. transparent 0 4px,
  937. ${p => p.theme.error} 4px 8px
  938. )
  939. 80%/2px 100% no-repeat;
  940. }
  941. }
  942. &.Timeline {
  943. opacity: 1;
  944. z-index: 1;
  945. pointer-events: none;
  946. .TraceIndicatorLabelContainer {
  947. font-weight: ${p => p.theme.fontWeightNormal};
  948. min-width: 0;
  949. top: 22px;
  950. width: auto;
  951. border: none;
  952. background-color: transparent;
  953. color: ${p => p.theme.subText};
  954. }
  955. .TraceIndicatorLine {
  956. background: ${p => p.theme.translucentGray100};
  957. top: 8px;
  958. }
  959. }
  960. }
  961. &.light {
  962. .TracePattern {
  963. &.info {
  964. --pattern-odd: #d1dff9;
  965. --pattern-even: ${p => p.theme.blue300};
  966. }
  967. &.warning {
  968. --pattern-odd: #a5752c;
  969. --pattern-even: ${p => p.theme.yellow300};
  970. }
  971. &.performance_issue {
  972. --pattern-odd: #063690;
  973. --pattern-even: ${p => p.theme.blue300};
  974. }
  975. &.profile {
  976. --pattern-odd: rgba(58, 17, 95, 0.55);
  977. --pattern-even: transparent;
  978. }
  979. &.missing_instrumentation {
  980. --pattern-odd: #dedae3;
  981. --pattern-even: #f4f2f7;
  982. }
  983. &.error,
  984. &.fatal {
  985. --pattern-odd: #872d32;
  986. --pattern-even: ${p => p.theme.red300};
  987. }
  988. /* false positive for grid layout */
  989. /* stylelint-disable */
  990. &.default {
  991. }
  992. &.unknown {
  993. }
  994. /* stylelint-enable */
  995. }
  996. }
  997. &.dark {
  998. .TracePattern {
  999. &.info {
  1000. --pattern-odd: #d1dff9;
  1001. --pattern-even: ${p => p.theme.blue300};
  1002. }
  1003. &.warning {
  1004. --pattern-odd: #a5752c;
  1005. --pattern-even: ${p => p.theme.yellow300};
  1006. }
  1007. &.performance_issue {
  1008. --pattern-odd: #063690;
  1009. --pattern-even: ${p => p.theme.blue300};
  1010. }
  1011. &.profile {
  1012. --pattern-odd: rgba(58, 17, 95, 0.55);
  1013. --pattern-even: transparent;
  1014. }
  1015. &.missing_instrumentation {
  1016. --pattern-odd: #4b4550;
  1017. --pattern-even: #1c1521;
  1018. }
  1019. &.error,
  1020. &.fatal {
  1021. --pattern-odd: #510d10;
  1022. --pattern-even: ${p => p.theme.red300};
  1023. }
  1024. /* stylelint-disable */
  1025. &.default {
  1026. }
  1027. &.unknown {
  1028. }
  1029. /* stylelint-enable */
  1030. }
  1031. }
  1032. .TraceRow {
  1033. display: flex;
  1034. align-items: center;
  1035. position: absolute;
  1036. height: 24px;
  1037. width: 100%;
  1038. transition: none;
  1039. font-size: ${p => p.theme.fontSizeSmall};
  1040. transform: translateZ(0);
  1041. --row-background-odd: ${p => p.theme.translucentSurface100};
  1042. --row-background-hover: ${p => p.theme.translucentSurface100};
  1043. --row-background-focused: ${p => p.theme.translucentSurface200};
  1044. --row-outline: ${p => p.theme.blue300};
  1045. --row-children-button-border-color: ${p => p.theme.border};
  1046. /* allow empty blocks so we can keep an exhaustive list of classnames for future reference */
  1047. /* stylelint-disable */
  1048. &.info {
  1049. }
  1050. &.warning {
  1051. }
  1052. &.debug {
  1053. }
  1054. &.error,
  1055. &.fatal,
  1056. &.performance_issue {
  1057. color: ${p => p.theme.errorText};
  1058. --autogrouped: ${p => p.theme.error};
  1059. --row-children-button-border-color: ${p => p.theme.error};
  1060. --row-outline: ${p => p.theme.error};
  1061. }
  1062. &.default {
  1063. }
  1064. &.unknown {
  1065. }
  1066. &.Hidden {
  1067. position: absolute;
  1068. height: 100%;
  1069. width: 100%;
  1070. top: 0;
  1071. z-index: -1;
  1072. &:hover {
  1073. background-color: transparent;
  1074. }
  1075. * {
  1076. cursor: default !important;
  1077. }
  1078. }
  1079. .TraceIcon {
  1080. position: absolute;
  1081. top: 50%;
  1082. transform: translate(-50%, -50%) scaleX(var(--inverse-span-scale)) translateZ(0);
  1083. background-color: ${p => p.theme.background};
  1084. width: 18px !important;
  1085. height: 18px !important;
  1086. border-radius: 50%;
  1087. display: flex;
  1088. align-items: center;
  1089. justify-content: center;
  1090. z-index: 1;
  1091. &.info {
  1092. background-color: var(--info);
  1093. }
  1094. &.warning {
  1095. background-color: var(--warning);
  1096. }
  1097. &.debug {
  1098. background-color: var(--debug);
  1099. }
  1100. &.error,
  1101. &.fatal {
  1102. background-color: var(--error);
  1103. }
  1104. &.performance_issue {
  1105. background-color: var(--performance-issue);
  1106. }
  1107. &.default {
  1108. background-color: var(--default);
  1109. }
  1110. &.unknown {
  1111. background-color: var(--unknown);
  1112. }
  1113. &.profile {
  1114. background-color: var(--profile);
  1115. }
  1116. svg {
  1117. width: 12px;
  1118. height: 12px;
  1119. fill: ${p => p.theme.white};
  1120. }
  1121. &.profile svg {
  1122. margin-left: 2px;
  1123. }
  1124. &.info,
  1125. &.warning,
  1126. &.performance_issue,
  1127. &.default,
  1128. &.unknown {
  1129. svg {
  1130. transform: translateY(-1px);
  1131. }
  1132. }
  1133. }
  1134. .TracePatternContainer {
  1135. position: absolute;
  1136. width: 100%;
  1137. height: 100%;
  1138. overflow: hidden;
  1139. }
  1140. .TracePattern {
  1141. left: 0;
  1142. width: 1000000px;
  1143. height: 100%;
  1144. position: absolute;
  1145. transform-origin: left center;
  1146. transform: scaleX(var(--inverse-span-scale)) translateZ(0);
  1147. background-image: linear-gradient(
  1148. 135deg,
  1149. var(--pattern-even) 1%,
  1150. var(--pattern-even) 11%,
  1151. var(--pattern-odd) 11%,
  1152. var(--pattern-odd) 21%,
  1153. var(--pattern-even) 21%,
  1154. var(--pattern-even) 31%,
  1155. var(--pattern-odd) 31%,
  1156. var(--pattern-odd) 41%,
  1157. var(--pattern-even) 41%,
  1158. var(--pattern-even) 51%,
  1159. var(--pattern-odd) 51%,
  1160. var(--pattern-odd) 61%,
  1161. var(--pattern-even) 61%,
  1162. var(--pattern-even) 71%,
  1163. var(--pattern-odd) 71%,
  1164. var(--pattern-odd) 81%,
  1165. var(--pattern-even) 81%,
  1166. var(--pattern-even) 91%,
  1167. var(--pattern-odd) 91%,
  1168. var(--pattern-odd) 101%
  1169. );
  1170. background-size: 25.5px 17px;
  1171. }
  1172. .TracePerformanceIssue {
  1173. position: absolute;
  1174. top: 0;
  1175. display: flex;
  1176. align-items: center;
  1177. justify-content: flex-start;
  1178. background-color: var(--performance-issue);
  1179. height: 16px;
  1180. }
  1181. .TraceRightColumn.Odd {
  1182. background-color: var(--row-background-odd);
  1183. }
  1184. &:hover {
  1185. background-color: var(--row-background-hovered);
  1186. }
  1187. &.Highlight {
  1188. box-shadow: inset 0 0 0 1px ${p => p.theme.blue200} !important;
  1189. .TraceLeftColumn {
  1190. box-shadow: inset 0px 0 0px 1px ${p => p.theme.blue200} !important;
  1191. }
  1192. }
  1193. &.Highlight,
  1194. &:focus,
  1195. &[tabindex='0'] {
  1196. outline: none;
  1197. background-color: var(--row-background-focused);
  1198. .TraceRightColumn.Odd {
  1199. background-color: transparent !important;
  1200. }
  1201. }
  1202. &:focus,
  1203. &[tabindex='0'] {
  1204. background-color: var(--row-background-focused);
  1205. box-shadow: inset 0 0 0 1px var(--row-outline) !important;
  1206. .TraceLeftColumn {
  1207. box-shadow: inset 0px 0 0px 1px var(--row-outline) !important;
  1208. }
  1209. .TraceRightColumn.Odd {
  1210. background-color: transparent !important;
  1211. }
  1212. }
  1213. &.SearchResult {
  1214. background-color: ${p => p.theme.yellow100};
  1215. .TraceRightColumn {
  1216. background-color: transparent;
  1217. }
  1218. }
  1219. &.Autogrouped {
  1220. color: ${p => p.theme.blue300};
  1221. .TraceDescription {
  1222. font-weight: ${p => p.theme.fontWeightBold};
  1223. }
  1224. .TraceChildrenCountWrapper {
  1225. button {
  1226. color: ${p => p.theme.white};
  1227. background-color: ${p => p.theme.blue300};
  1228. }
  1229. svg {
  1230. fill: ${p => p.theme.white};
  1231. }
  1232. }
  1233. &.error {
  1234. color: ${p => p.theme.red300};
  1235. .TraceChildrenCountWrapper {
  1236. button {
  1237. color: ${p => p.theme.white};
  1238. background-color: ${p => p.theme.red300};
  1239. }
  1240. }
  1241. }
  1242. }
  1243. &.Collapsed {
  1244. background-color: ${p => p.theme.backgroundSecondary};
  1245. border-bottom: 1px solid ${p => p.theme.border};
  1246. border-top: 1px solid ${p => p.theme.border};
  1247. .TraceLeftColumn {
  1248. padding-left: 14px;
  1249. width: 100%;
  1250. color: ${p => p.theme.subText};
  1251. .TraceLeftColumnInner {
  1252. padding-left: 0 !important;
  1253. }
  1254. }
  1255. }
  1256. }
  1257. .TraceLeftColumn {
  1258. height: 100%;
  1259. white-space: nowrap;
  1260. display: flex;
  1261. align-items: center;
  1262. overflow: hidden;
  1263. will-change: width;
  1264. box-shadow: inset 1px 0 0px 0px transparent;
  1265. cursor: pointer;
  1266. width: calc(var(--list-column-width) * 100%);
  1267. .TraceLeftColumnInner {
  1268. height: 100%;
  1269. white-space: nowrap;
  1270. display: flex;
  1271. align-items: center;
  1272. will-change: transform;
  1273. transform-origin: left center;
  1274. padding-right: ${space(2)};
  1275. img {
  1276. width: 16px;
  1277. height: 16px;
  1278. }
  1279. }
  1280. }
  1281. .TraceRightColumn {
  1282. height: 100%;
  1283. overflow: hidden;
  1284. position: relative;
  1285. display: flex;
  1286. align-items: center;
  1287. will-change: width;
  1288. z-index: 1;
  1289. cursor: pointer;
  1290. width: calc(var(--span-column-width) * 100%);
  1291. &:hover {
  1292. .TraceArrow.Visible {
  1293. opacity: 1;
  1294. transition: 300ms 300ms ease-out;
  1295. pointer-events: auto;
  1296. }
  1297. }
  1298. }
  1299. .TraceBar {
  1300. position: absolute;
  1301. height: 16px;
  1302. width: 100%;
  1303. background-color: black;
  1304. transform-origin: left center;
  1305. &.Invisible {
  1306. background-color: transparent !important;
  1307. > div {
  1308. height: 100%;
  1309. }
  1310. }
  1311. svg {
  1312. width: 14px;
  1313. height: 14px;
  1314. }
  1315. }
  1316. .TraceArrow {
  1317. position: absolute;
  1318. pointer-events: none;
  1319. top: 0;
  1320. width: 14px;
  1321. height: 24px;
  1322. opacity: 0;
  1323. background-color: transparent;
  1324. border: none;
  1325. transition: 60ms ease-out;
  1326. font-size: ${p => p.theme.fontSizeMedium};
  1327. color: ${p => p.theme.subText};
  1328. padding: 0 2px;
  1329. display: flex;
  1330. align-items: center;
  1331. svg {
  1332. fill: ${p => p.theme.subText};
  1333. }
  1334. &.Left {
  1335. left: 0;
  1336. }
  1337. &.Right {
  1338. right: 0;
  1339. transform: rotate(180deg);
  1340. }
  1341. }
  1342. .TraceBarDuration {
  1343. display: inline-block;
  1344. transform-origin: left center;
  1345. font-size: ${p => p.theme.fontSizeExtraSmall};
  1346. color: ${p => p.theme.gray300};
  1347. white-space: nowrap;
  1348. font-variant-numeric: tabular-nums;
  1349. position: absolute;
  1350. }
  1351. .TraceChildrenCount {
  1352. height: 16px;
  1353. white-space: nowrap;
  1354. min-width: 30px;
  1355. display: flex;
  1356. align-items: center;
  1357. justify-content: center;
  1358. border-radius: 99px;
  1359. padding: 0px 4px;
  1360. transition: all 0.15s ease-in-out;
  1361. background: ${p => p.theme.background};
  1362. border: 1.5px solid var(--row-children-button-border-color);
  1363. line-height: 0;
  1364. z-index: 1;
  1365. font-size: 10px;
  1366. box-shadow: ${p => p.theme.dropShadowLight};
  1367. margin-right: 8px;
  1368. .TraceChildrenCountContent {
  1369. + .TraceChildrenCountAction {
  1370. margin-left: 2px;
  1371. }
  1372. }
  1373. .TraceChildrenCountAction {
  1374. position: relative;
  1375. display: flex;
  1376. align-items: center;
  1377. justify-content: center;
  1378. }
  1379. .TraceActionsLoadingIndicator {
  1380. margin: 0;
  1381. position: absolute;
  1382. top: 50%;
  1383. left: 50%;
  1384. transform: translate(-50%, -50%);
  1385. background-color: ${p => p.theme.background};
  1386. animation: show 0.1s ease-in-out forwards;
  1387. @keyframes show {
  1388. from {
  1389. opacity: 0;
  1390. transform: translate(-50%, -50%) scale(0.86);
  1391. }
  1392. to {
  1393. opacity: 1;
  1394. transform: translate(-50%, -50%) scale(1);
  1395. }
  1396. }
  1397. .loading-indicator {
  1398. border-width: 2px;
  1399. }
  1400. .loading-message {
  1401. display: none;
  1402. }
  1403. }
  1404. svg {
  1405. width: 7px;
  1406. transition: none;
  1407. }
  1408. }
  1409. .TraceChildrenCountWrapper {
  1410. display: flex;
  1411. justify-content: flex-end;
  1412. align-items: center;
  1413. min-width: 44px;
  1414. height: 100%;
  1415. position: relative;
  1416. button {
  1417. transition: none;
  1418. }
  1419. svg {
  1420. fill: currentColor;
  1421. }
  1422. &.Orphaned {
  1423. .TraceVerticalConnector,
  1424. .TraceVerticalLastChildConnector,
  1425. .TraceExpandedVerticalConnector {
  1426. border-left: 2px dashed ${p => p.theme.border};
  1427. }
  1428. &::before {
  1429. border-bottom: 2px dashed ${p => p.theme.border};
  1430. }
  1431. }
  1432. &.Root {
  1433. &:before,
  1434. .TraceVerticalLastChildConnector {
  1435. visibility: hidden;
  1436. }
  1437. }
  1438. &::before {
  1439. content: '';
  1440. display: block;
  1441. width: 50%;
  1442. height: 2px;
  1443. border-bottom: 2px solid ${p => p.theme.border};
  1444. position: absolute;
  1445. left: 0;
  1446. top: 50%;
  1447. transform: translateY(-50%);
  1448. }
  1449. &::after {
  1450. content: '';
  1451. background-color: ${p => p.theme.border};
  1452. border-radius: 50%;
  1453. height: 6px;
  1454. width: 6px;
  1455. position: absolute;
  1456. left: 50%;
  1457. top: 50%;
  1458. transform: translateY(-50%);
  1459. }
  1460. }
  1461. .TraceVerticalConnector {
  1462. position: absolute;
  1463. left: 0;
  1464. top: 0;
  1465. bottom: 0;
  1466. height: 100%;
  1467. width: 2px;
  1468. border-left: 2px solid ${p => p.theme.border};
  1469. &.Orphaned {
  1470. border-left: 2px dashed ${p => p.theme.border};
  1471. }
  1472. }
  1473. .TraceVerticalLastChildConnector {
  1474. position: absolute;
  1475. left: 0;
  1476. top: 0;
  1477. bottom: 0;
  1478. height: 50%;
  1479. width: 2px;
  1480. border-left: 2px solid ${p => p.theme.border};
  1481. border-bottom-left-radius: 4px;
  1482. }
  1483. .TraceExpandedVerticalConnector {
  1484. position: absolute;
  1485. bottom: 0;
  1486. height: 50%;
  1487. left: 50%;
  1488. width: 2px;
  1489. border-left: 2px solid ${p => p.theme.border};
  1490. }
  1491. .TraceOperation {
  1492. margin-left: 4px;
  1493. text-overflow: ellipsis;
  1494. white-space: nowrap;
  1495. font-weight: ${p => p.theme.fontWeightBold};
  1496. }
  1497. .TraceEmDash {
  1498. margin-left: 4px;
  1499. margin-right: 4px;
  1500. }
  1501. .TraceDescription {
  1502. white-space: nowrap;
  1503. }
  1504. `;