trace.tsx 42 KB

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