trace.tsx 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344
  1. import type React from 'react';
  2. import {Fragment, useCallback, useLayoutEffect, useMemo, useRef, useState} from 'react';
  3. import {type Theme, useTheme} from '@emotion/react';
  4. import styled from '@emotion/styled';
  5. import * as Sentry from '@sentry/react';
  6. import {PlatformIcon} from 'platformicons';
  7. import * as qs from 'query-string';
  8. import LoadingIndicator from 'sentry/components/loadingIndicator';
  9. import Placeholder from 'sentry/components/placeholder';
  10. import {t, tct} from 'sentry/locale';
  11. import {space} from 'sentry/styles/space';
  12. import type {Organization, PlatformKey, Project} from 'sentry/types';
  13. import type {
  14. TraceError,
  15. TracePerformanceIssue,
  16. } from 'sentry/utils/performance/quickTrace/types';
  17. import {clamp} from 'sentry/utils/profiling/colors/utils';
  18. import useApi from 'sentry/utils/useApi';
  19. import useOrganization from 'sentry/utils/useOrganization';
  20. import useProjects from 'sentry/utils/useProjects';
  21. import {formatTraceDuration} from 'sentry/views/performance/newTraceDetails/formatters';
  22. import {
  23. useVirtualizedList,
  24. type VirtualizedRow,
  25. } from 'sentry/views/performance/newTraceDetails/traceRenderers/traceVirtualizedList';
  26. import type {VirtualizedViewManager} from 'sentry/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager';
  27. import type {
  28. TraceReducerAction,
  29. TraceReducerState,
  30. } from 'sentry/views/performance/newTraceDetails/traceState';
  31. import {
  32. getRovingIndexActionFromDOMEvent,
  33. type RovingTabIndexUserActions,
  34. } from 'sentry/views/performance/newTraceDetails/traceState/traceRovingTabIndex';
  35. import {
  36. makeTraceNodeBarColor,
  37. ParentAutogroupNode,
  38. TraceTree,
  39. type TraceTreeNode,
  40. } from './traceModels/traceTree';
  41. import {
  42. isAutogroupedNode,
  43. isMissingInstrumentationNode,
  44. isNoDataNode,
  45. isParentAutogroupedNode,
  46. isSpanNode,
  47. isTraceErrorNode,
  48. isTraceNode,
  49. isTransactionNode,
  50. } from './guards';
  51. import {TraceIcons} from './icons';
  52. function decodeScrollQueue(maybePath: unknown): TraceTree.NodePath[] | null {
  53. if (Array.isArray(maybePath)) {
  54. return maybePath;
  55. }
  56. if (typeof maybePath === 'string') {
  57. return [maybePath as TraceTree.NodePath];
  58. }
  59. return null;
  60. }
  61. const COUNT_FORMATTER = Intl.NumberFormat(undefined, {notation: 'compact'});
  62. const NO_ERRORS = new Set<TraceError>();
  63. const NO_PERFORMANCE_ISSUES = new Set<TracePerformanceIssue>();
  64. const NO_PROFILES = [];
  65. function computeNextIndexFromAction(
  66. current_index: number,
  67. action: RovingTabIndexUserActions,
  68. items: number
  69. ): number {
  70. switch (action) {
  71. case 'next':
  72. if (current_index === items) {
  73. return 0;
  74. }
  75. return current_index + 1;
  76. case 'previous':
  77. if (current_index === 0) {
  78. return items;
  79. }
  80. return current_index - 1;
  81. case 'last':
  82. return items;
  83. case 'first':
  84. return 0;
  85. default:
  86. throw new TypeError(`Invalid or not implemented reducer action - ${action}`);
  87. }
  88. }
  89. const RIGHT_COLUMN_EVEN_CLASSNAME = `TraceRightColumn`;
  90. const RIGHT_COLUMN_ODD_CLASSNAME = [RIGHT_COLUMN_EVEN_CLASSNAME, 'Odd'].join(' ');
  91. const CHILDREN_COUNT_WRAPPER_CLASSNAME = `TraceChildrenCountWrapper`;
  92. const CHILDREN_COUNT_WRAPPER_ORPHANED_CLASSNAME = [
  93. CHILDREN_COUNT_WRAPPER_CLASSNAME,
  94. 'Orphaned',
  95. ].join(' ');
  96. function maybeFocusRow(
  97. ref: HTMLDivElement | null,
  98. node: TraceTreeNode<TraceTree.NodeValue>,
  99. previouslyFocusedNodeRef: React.MutableRefObject<TraceTreeNode<TraceTree.NodeValue> | null>
  100. ) {
  101. if (!ref) return;
  102. if (node === previouslyFocusedNodeRef.current) return;
  103. previouslyFocusedNodeRef.current = node;
  104. ref.focus();
  105. }
  106. interface TraceProps {
  107. forceRerender: number;
  108. manager: VirtualizedViewManager;
  109. onRowClick: (
  110. node: TraceTreeNode<TraceTree.NodeValue>,
  111. event: React.MouseEvent<HTMLElement>,
  112. index: number
  113. ) => void;
  114. onTraceLoad: (
  115. trace: TraceTree,
  116. node: TraceTreeNode<TraceTree.NodeValue> | null,
  117. index: number | null
  118. ) => void;
  119. onTraceSearch: (
  120. query: string,
  121. node: TraceTreeNode<TraceTree.NodeValue>,
  122. behavior: 'track result' | 'persist'
  123. ) => void;
  124. previouslyFocusedNodeRef: React.MutableRefObject<TraceTreeNode<TraceTree.NodeValue> | null>;
  125. rerender: () => void;
  126. scrollQueueRef: React.MutableRefObject<{
  127. eventId?: string;
  128. path?: TraceTree.NodePath[];
  129. } | null>;
  130. trace: TraceTree;
  131. trace_dispatch: React.Dispatch<TraceReducerAction>;
  132. trace_id: string;
  133. trace_state: TraceReducerState;
  134. }
  135. export function Trace({
  136. trace,
  137. trace_id,
  138. onRowClick,
  139. manager,
  140. scrollQueueRef,
  141. previouslyFocusedNodeRef,
  142. onTraceSearch,
  143. onTraceLoad,
  144. rerender,
  145. trace_state,
  146. trace_dispatch,
  147. forceRerender,
  148. }: TraceProps) {
  149. const theme = useTheme();
  150. const api = useApi();
  151. const {projects} = useProjects();
  152. const organization = useOrganization();
  153. const containerRef = useRef<HTMLDivElement | null>(null);
  154. const rerenderRef = useRef<TraceProps['rerender']>(rerender);
  155. rerenderRef.current = rerender;
  156. const treePromiseStatusRef =
  157. useRef<Map<TraceTreeNode<TraceTree.NodeValue>, 'loading' | 'error' | 'success'>>();
  158. if (!treePromiseStatusRef.current) {
  159. treePromiseStatusRef.current = new Map();
  160. }
  161. const treeRef = useRef<TraceTree>(trace);
  162. treeRef.current = trace;
  163. const traceStateRef = useRef<TraceReducerState>(trace_state);
  164. traceStateRef.current = trace_state;
  165. if (
  166. trace.root.space &&
  167. (trace.root.space[0] !== manager.to_origin ||
  168. trace.root.space[1] !== manager.trace_space.width)
  169. ) {
  170. manager.initializeTraceSpace([trace.root.space[0], 0, trace.root.space[1], 1]);
  171. const maybeQueue = decodeScrollQueue(qs.parse(location.search).node);
  172. const maybeEventId = qs.parse(location.search)?.eventId;
  173. if (maybeQueue || maybeEventId) {
  174. scrollQueueRef.current = {
  175. eventId: maybeEventId as string,
  176. path: maybeQueue as TraceTreeNode<TraceTree.NodeValue>['path'],
  177. };
  178. }
  179. }
  180. const loadedRef = useRef(false);
  181. useLayoutEffect(() => {
  182. if (loadedRef.current) {
  183. return;
  184. }
  185. if (trace.type !== 'trace' || !manager) {
  186. return;
  187. }
  188. loadedRef.current = true;
  189. if (!scrollQueueRef.current) {
  190. onTraceLoad(trace, null, null);
  191. return;
  192. }
  193. // Node path has higher specificity than eventId
  194. const promise = scrollQueueRef.current?.path
  195. ? TraceTree.ExpandToPath(trace, scrollQueueRef.current.path, rerenderRef.current, {
  196. api,
  197. organization,
  198. })
  199. : scrollQueueRef.current.eventId
  200. ? TraceTree.ExpandToEventID(
  201. scrollQueueRef?.current?.eventId,
  202. trace,
  203. rerenderRef.current,
  204. {
  205. api,
  206. organization,
  207. }
  208. )
  209. : Promise.resolve(null);
  210. promise
  211. .then(maybeNode => {
  212. onTraceLoad(trace, maybeNode?.node ?? null, maybeNode?.index ?? null);
  213. if (!maybeNode) {
  214. Sentry.captureMessage('Failed to find and scroll to node in tree');
  215. return;
  216. }
  217. })
  218. .finally(() => {
  219. // Important to set scrollQueueRef.current to null and trigger a rerender
  220. // after the promise resolves as we show a loading state during scroll,
  221. // else the screen could jump around while we fetch span data
  222. scrollQueueRef.current = null;
  223. rerenderRef.current();
  224. });
  225. }, [
  226. api,
  227. trace,
  228. trace_id,
  229. manager,
  230. onTraceLoad,
  231. trace_dispatch,
  232. scrollQueueRef,
  233. organization,
  234. ]);
  235. const onNodeZoomIn = useCallback(
  236. (
  237. event: React.MouseEvent<Element> | React.KeyboardEvent<Element>,
  238. node: TraceTreeNode<TraceTree.NodeValue>,
  239. value: boolean
  240. ) => {
  241. if (!isTransactionNode(node) && !isSpanNode(node)) {
  242. throw new TypeError('Node must be a transaction or span');
  243. }
  244. event.stopPropagation();
  245. rerenderRef.current();
  246. treeRef.current
  247. .zoomIn(node, value, {
  248. api,
  249. organization,
  250. })
  251. .then(() => {
  252. rerenderRef.current();
  253. // If a query exists, we want to reapply the search after zooming in
  254. // so that new nodes are also highlighted if they match a query
  255. if (traceStateRef.current.search.query) {
  256. onTraceSearch(traceStateRef.current.search.query, node, 'persist');
  257. }
  258. treePromiseStatusRef.current!.set(node, 'success');
  259. })
  260. .catch(_e => {
  261. treePromiseStatusRef.current!.set(node, 'error');
  262. });
  263. },
  264. [api, organization, onTraceSearch]
  265. );
  266. const onNodeExpand = useCallback(
  267. (
  268. event: React.MouseEvent<Element> | React.KeyboardEvent<Element>,
  269. node: TraceTreeNode<TraceTree.NodeValue>,
  270. value: boolean
  271. ) => {
  272. event.stopPropagation();
  273. treeRef.current.expand(node, value);
  274. rerenderRef.current();
  275. if (traceStateRef.current.search.query) {
  276. // If a query exists, we want to reapply the search after expanding
  277. // so that new nodes are also highlighted if they match a query
  278. onTraceSearch(traceStateRef.current.search.query, node, 'persist');
  279. }
  280. },
  281. [onTraceSearch]
  282. );
  283. const onRowKeyDown = useCallback(
  284. (
  285. event: React.KeyboardEvent,
  286. index: number,
  287. node: TraceTreeNode<TraceTree.NodeValue>
  288. ) => {
  289. if (!manager.list) {
  290. return;
  291. }
  292. const action = getRovingIndexActionFromDOMEvent(event);
  293. if (action) {
  294. event.preventDefault();
  295. const nextIndex = computeNextIndexFromAction(
  296. index,
  297. action,
  298. treeRef.current.list.length - 1
  299. );
  300. trace_dispatch({
  301. type: 'set roving index',
  302. index: nextIndex,
  303. node: treeRef.current.list[nextIndex],
  304. action_source: 'keyboard',
  305. });
  306. }
  307. if (event.key === 'ArrowLeft') {
  308. if (node.zoomedIn) onNodeZoomIn(event, node, false);
  309. else if (node.expanded) onNodeExpand(event, node, false);
  310. } else if (event.key === 'ArrowRight') {
  311. if (!node.expanded) onNodeExpand(event, node, true);
  312. else if (node.expanded && node.canFetch) onNodeZoomIn(event, node, true);
  313. }
  314. },
  315. [manager, onNodeExpand, onNodeZoomIn, trace_dispatch]
  316. );
  317. const projectLookup: Record<string, PlatformKey | undefined> = useMemo(() => {
  318. return projects.reduce<Record<Project['slug'], Project['platform']>>(
  319. (acc, project) => {
  320. acc[project.slug] = project.platform;
  321. return acc;
  322. },
  323. {}
  324. );
  325. }, [projects]);
  326. const renderLoadingRow = useCallback(
  327. (n: VirtualizedRow) => {
  328. return (
  329. <RenderPlaceholderRow
  330. key={n.key}
  331. index={n.index}
  332. style={n.style}
  333. node={n.item}
  334. theme={theme}
  335. manager={manager}
  336. />
  337. );
  338. },
  339. [manager, theme]
  340. );
  341. const renderVirtualizedRow = useCallback(
  342. (n: VirtualizedRow) => {
  343. return (
  344. <RenderRow
  345. key={n.key}
  346. index={n.index}
  347. organization={organization}
  348. previouslyFocusedNodeRef={previouslyFocusedNodeRef}
  349. tabIndex={trace_state.rovingTabIndex.node === n.item ? 0 : -1}
  350. isSearchResult={trace_state.search.resultsLookup.has(n.item)}
  351. searchResultsIteratorIndex={trace_state.search.resultIndex}
  352. style={n.style}
  353. trace_id={trace_id}
  354. projects={projectLookup}
  355. node={n.item}
  356. manager={manager}
  357. theme={theme}
  358. onExpand={onNodeExpand}
  359. onZoomIn={onNodeZoomIn}
  360. onRowClick={onRowClick}
  361. onRowKeyDown={onRowKeyDown}
  362. />
  363. );
  364. },
  365. // we add forceRerender as a "unnecessary" dependency to trigger the virtualized list rerender
  366. // eslint-disable-next-line react-hooks/exhaustive-deps
  367. [
  368. onNodeExpand,
  369. onNodeZoomIn,
  370. manager,
  371. scrollQueueRef,
  372. previouslyFocusedNodeRef,
  373. onRowKeyDown,
  374. onRowClick,
  375. organization,
  376. projectLookup,
  377. trace_state.rovingTabIndex.node,
  378. trace_state.search.resultIteratorIndex,
  379. trace_state.search.resultsLookup,
  380. trace_state.search.resultIndex,
  381. theme,
  382. trace_id,
  383. trace.type,
  384. forceRerender,
  385. ]
  386. );
  387. const render = useMemo(() => {
  388. return trace.type !== 'trace' || scrollQueueRef.current
  389. ? r => renderLoadingRow(r)
  390. : r => renderVirtualizedRow(r);
  391. }, [renderLoadingRow, renderVirtualizedRow, trace.type, scrollQueueRef]);
  392. const [scrollContainer, setScrollContainer] = useState<HTMLElement | null>(null);
  393. const virtualizedList = useVirtualizedList({
  394. manager,
  395. items: trace.list,
  396. container: scrollContainer,
  397. render: render,
  398. });
  399. return (
  400. <TraceStylingWrapper
  401. ref={r => {
  402. containerRef.current = r;
  403. manager.registerContainerRef(r);
  404. }}
  405. className={`${trace.indicators.length > 0 ? 'WithIndicators' : ''} ${trace.type !== 'trace' || scrollQueueRef.current ? 'Loading' : ''}`}
  406. >
  407. <div
  408. className="TraceScrollbarContainer"
  409. ref={r => manager.registerHorizontalScrollBarContainerRef(r)}
  410. >
  411. <div className="TraceScrollbarScroller" />
  412. </div>
  413. <div className="TraceDivider" ref={r => manager.registerDividerRef(r)} />
  414. <div
  415. className="TraceIndicatorContainer"
  416. ref={r => manager.registerIndicatorContainerRef(r)}
  417. >
  418. {trace.indicators.length > 0
  419. ? trace.indicators.map((indicator, i) => {
  420. return (
  421. <div
  422. key={i}
  423. ref={r => manager.registerIndicatorRef(r, i, indicator)}
  424. className={`TraceIndicator ${indicator.poor ? 'Errored' : ''}`}
  425. >
  426. <div className="TraceIndicatorLabel">{indicator.label}</div>
  427. <div className="TraceIndicatorLine" />
  428. </div>
  429. );
  430. })
  431. : null}
  432. {manager.interval_bars.map((_, i) => {
  433. const indicatorTimestamp = manager.intervals[i] ?? 0;
  434. const timestamp = manager.to_origin + indicatorTimestamp;
  435. if (trace.type !== 'trace') {
  436. return null;
  437. }
  438. return (
  439. <div
  440. key={i}
  441. ref={r => manager.registerTimelineIndicatorRef(r, i)}
  442. className="TraceIndicator Timeline"
  443. style={{
  444. transform: `translate(${manager.computeTransformXFromTimestamp(timestamp)}px, 0)`,
  445. }}
  446. >
  447. <div className="TraceIndicatorLabel">
  448. {indicatorTimestamp > 0
  449. ? formatTraceDuration(manager.trace_view.x + indicatorTimestamp)
  450. : '0s'}
  451. </div>
  452. <div className="TraceIndicatorLine" />
  453. </div>
  454. );
  455. })}
  456. </div>
  457. <div
  458. ref={r => setScrollContainer(r)}
  459. data-test-id="trace-virtualized-list-scroll-container"
  460. >
  461. <div data-test-id="trace-virtualized-list">{virtualizedList.rendered}</div>
  462. <div className="TraceRow Hidden">
  463. <div
  464. className="TraceLeftColumn"
  465. ref={r => manager.registerGhostRowRef('list', r)}
  466. />
  467. <div
  468. className="TraceRightColumn"
  469. ref={r => manager.registerGhostRowRef('span_list', r)}
  470. />
  471. </div>
  472. </div>
  473. </TraceStylingWrapper>
  474. );
  475. }
  476. function RenderRow(props: {
  477. index: number;
  478. isSearchResult: boolean;
  479. manager: VirtualizedViewManager;
  480. node: TraceTreeNode<TraceTree.NodeValue>;
  481. onExpand: (
  482. event: React.MouseEvent<Element>,
  483. node: TraceTreeNode<TraceTree.NodeValue>,
  484. value: boolean
  485. ) => void;
  486. onRowClick: (
  487. node: TraceTreeNode<TraceTree.NodeValue>,
  488. event: React.MouseEvent<HTMLElement>,
  489. index: number
  490. ) => void;
  491. onRowKeyDown: (
  492. event: React.KeyboardEvent,
  493. index: number,
  494. node: TraceTreeNode<TraceTree.NodeValue>
  495. ) => void;
  496. onZoomIn: (
  497. event: React.MouseEvent<Element>,
  498. node: TraceTreeNode<TraceTree.NodeValue>,
  499. value: boolean
  500. ) => void;
  501. organization: Organization;
  502. previouslyFocusedNodeRef: React.MutableRefObject<TraceTreeNode<TraceTree.NodeValue> | null>;
  503. projects: Record<Project['slug'], Project['platform']>;
  504. searchResultsIteratorIndex: number | null;
  505. style: React.CSSProperties;
  506. tabIndex: number;
  507. theme: Theme;
  508. trace_id: string;
  509. }) {
  510. const virtualized_index = props.index - props.manager.start_virtualized_index;
  511. const rowSearchClassName = `${props.isSearchResult ? 'SearchResult' : ''} ${props.searchResultsIteratorIndex === props.index ? 'Highlight' : ''}`;
  512. if (isAutogroupedNode(props.node)) {
  513. return (
  514. <div
  515. key={props.index}
  516. ref={r =>
  517. props.tabIndex === 0
  518. ? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
  519. : null
  520. }
  521. tabIndex={props.tabIndex}
  522. className={`Autogrouped TraceRow ${rowSearchClassName} ${props.node.has_errors ? 'Errored' : ''}`}
  523. onClick={e => props.onRowClick(props.node, e, props.index)}
  524. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  525. style={{
  526. top: props.style.top,
  527. height: props.style.height,
  528. }}
  529. >
  530. <div
  531. className="TraceLeftColumn"
  532. ref={r =>
  533. props.manager.registerColumnRef('list', r, virtualized_index, props.node)
  534. }
  535. >
  536. <div
  537. className={`TraceLeftColumnInner`}
  538. style={{
  539. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  540. }}
  541. >
  542. <div className="TraceChildrenCountWrapper">
  543. <Connectors node={props.node} manager={props.manager} />
  544. <ChildrenButton
  545. icon={
  546. <TraceIcons.Chevron direction={props.node.expanded ? 'up' : 'down'} />
  547. }
  548. status={props.node.fetchStatus}
  549. expanded={!props.node.expanded}
  550. onClick={e => props.onExpand(e, props.node, !props.node.expanded)}
  551. errored={props.node.has_errors}
  552. >
  553. {COUNT_FORMATTER.format(props.node.groupCount)}
  554. </ChildrenButton>
  555. </div>
  556. <span className="TraceOperation">{t('Autogrouped')}</span>
  557. <strong className="TraceEmDash"> — </strong>
  558. <span className="TraceDescription">{props.node.value.autogrouped_by.op}</span>
  559. </div>
  560. </div>
  561. <div
  562. className={
  563. props.index % 2 === 1
  564. ? RIGHT_COLUMN_ODD_CLASSNAME
  565. : RIGHT_COLUMN_EVEN_CLASSNAME
  566. }
  567. ref={r =>
  568. props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
  569. }
  570. onDoubleClick={e => {
  571. e.stopPropagation();
  572. props.manager.onZoomIntoSpace(props.node.space!);
  573. }}
  574. >
  575. <AutogroupedTraceBar
  576. virtualized_index={virtualized_index}
  577. manager={props.manager}
  578. color={makeTraceNodeBarColor(props.theme, props.node)}
  579. entire_space={props.node.space}
  580. node_spaces={props.node.autogroupedSegments}
  581. errors={props.node.errors}
  582. performance_issues={props.node.performance_issues}
  583. profiles={props.node.profiles}
  584. />
  585. <button
  586. ref={ref =>
  587. props.manager.registerArrowRef(ref, props.node.space!, virtualized_index)
  588. }
  589. className="TraceArrow"
  590. onClick={_e => {
  591. props.manager.onBringRowIntoView(props.node.space!);
  592. }}
  593. >
  594. <TraceIcons.Chevron direction="left" />
  595. </button>
  596. </div>
  597. </div>
  598. );
  599. }
  600. if (isTransactionNode(props.node)) {
  601. const errored =
  602. props.node.value.errors.length > 0 ||
  603. props.node.value.performance_issues.length > 0;
  604. return (
  605. <div
  606. key={props.index}
  607. ref={r =>
  608. props.tabIndex === 0
  609. ? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
  610. : null
  611. }
  612. tabIndex={props.tabIndex}
  613. className={`TraceRow ${rowSearchClassName} ${errored ? 'Errored' : ''}`}
  614. onClick={e => props.onRowClick(props.node, e, props.index)}
  615. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  616. style={{
  617. top: props.style.top,
  618. height: props.style.height,
  619. }}
  620. >
  621. <div
  622. className="TraceLeftColumn"
  623. ref={r =>
  624. props.manager.registerColumnRef('list', r, virtualized_index, props.node)
  625. }
  626. >
  627. <div
  628. className={`TraceLeftColumnInner`}
  629. style={{
  630. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  631. }}
  632. >
  633. <div
  634. className={
  635. props.node.isOrphaned
  636. ? CHILDREN_COUNT_WRAPPER_ORPHANED_CLASSNAME
  637. : CHILDREN_COUNT_WRAPPER_CLASSNAME
  638. }
  639. >
  640. <Connectors node={props.node} manager={props.manager} />
  641. {props.node.children.length > 0 || props.node.canFetch ? (
  642. <ChildrenButton
  643. icon={
  644. props.node.canFetch && props.node.fetchStatus === 'idle' ? (
  645. '+'
  646. ) : props.node.canFetch && props.node.zoomedIn ? (
  647. <TraceIcons.Chevron direction="down" />
  648. ) : (
  649. '+'
  650. )
  651. }
  652. status={props.node.fetchStatus}
  653. expanded={props.node.expanded || props.node.zoomedIn}
  654. onClick={e => {
  655. props.node.canFetch
  656. ? props.onZoomIn(e, props.node, !props.node.zoomedIn)
  657. : props.onExpand(e, props.node, !props.node.expanded);
  658. }}
  659. errored={errored}
  660. >
  661. {props.node.children.length > 0
  662. ? COUNT_FORMATTER.format(props.node.children.length)
  663. : null}
  664. </ChildrenButton>
  665. ) : null}
  666. </div>
  667. <PlatformIcon
  668. platform={props.projects[props.node.value.project_slug] ?? 'default'}
  669. />
  670. <span className="TraceOperation">{props.node.value['transaction.op']}</span>
  671. <strong className="TraceEmDash"> — </strong>
  672. <span>{props.node.value.transaction}</span>
  673. </div>
  674. </div>
  675. <div
  676. ref={r =>
  677. props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
  678. }
  679. className={
  680. props.index % 2 === 1
  681. ? RIGHT_COLUMN_ODD_CLASSNAME
  682. : RIGHT_COLUMN_EVEN_CLASSNAME
  683. }
  684. onDoubleClick={e => {
  685. e.stopPropagation();
  686. props.manager.onZoomIntoSpace(props.node.space!);
  687. }}
  688. >
  689. <TraceBar
  690. virtualized_index={virtualized_index}
  691. manager={props.manager}
  692. color={makeTraceNodeBarColor(props.theme, props.node)}
  693. node_space={props.node.space}
  694. errors={props.node.errors}
  695. performance_issues={props.node.performance_issues}
  696. profiles={props.node.profiles}
  697. />
  698. <button
  699. ref={ref =>
  700. props.manager.registerArrowRef(ref, props.node.space!, virtualized_index)
  701. }
  702. className="TraceArrow"
  703. onClick={_e => {
  704. props.manager.onBringRowIntoView(props.node.space!);
  705. }}
  706. >
  707. <TraceIcons.Chevron direction="left" />
  708. </button>
  709. </div>
  710. </div>
  711. );
  712. }
  713. if (isSpanNode(props.node)) {
  714. const errored = props.node.errors.size > 0 || props.node.performance_issues.size > 0;
  715. return (
  716. <div
  717. key={props.index}
  718. ref={r =>
  719. props.tabIndex === 0
  720. ? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
  721. : null
  722. }
  723. tabIndex={props.tabIndex}
  724. className={`TraceRow ${rowSearchClassName} ${errored ? 'Errored' : ''}`}
  725. onClick={e => props.onRowClick(props.node, e, props.index)}
  726. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  727. style={{
  728. top: props.style.top,
  729. height: props.style.height,
  730. }}
  731. >
  732. <div
  733. className="TraceLeftColumn"
  734. ref={r =>
  735. props.manager.registerColumnRef('list', r, virtualized_index, props.node)
  736. }
  737. >
  738. <div
  739. className={`TraceLeftColumnInner`}
  740. style={{
  741. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  742. }}
  743. >
  744. <div
  745. className={
  746. props.node.isOrphaned
  747. ? CHILDREN_COUNT_WRAPPER_ORPHANED_CLASSNAME
  748. : CHILDREN_COUNT_WRAPPER_CLASSNAME
  749. }
  750. >
  751. <Connectors node={props.node} manager={props.manager} />
  752. {props.node.children.length > 0 || props.node.canFetch ? (
  753. <ChildrenButton
  754. icon={
  755. props.node.canFetch ? (
  756. '+'
  757. ) : (
  758. <TraceIcons.Chevron
  759. direction={props.node.expanded ? 'up' : 'down'}
  760. />
  761. )
  762. }
  763. status={props.node.fetchStatus}
  764. expanded={props.node.expanded || props.node.zoomedIn}
  765. onClick={e =>
  766. props.node.canFetch
  767. ? props.onZoomIn(e, props.node, !props.node.zoomedIn)
  768. : props.onExpand(e, props.node, !props.node.expanded)
  769. }
  770. errored={errored}
  771. >
  772. {props.node.children.length > 0
  773. ? COUNT_FORMATTER.format(props.node.children.length)
  774. : null}
  775. </ChildrenButton>
  776. ) : null}
  777. </div>
  778. <span className="TraceOperation">{props.node.value.op ?? '<unknown>'}</span>
  779. <strong className="TraceEmDash"> — </strong>
  780. <span className="TraceDescription" title={props.node.value.description}>
  781. {!props.node.value.description
  782. ? props.node.value.span_id ?? 'unknown'
  783. : props.node.value.description.length > 100
  784. ? props.node.value.description.slice(0, 100).trim() + '\u2026'
  785. : props.node.value.description}
  786. </span>
  787. </div>
  788. </div>
  789. <div
  790. ref={r =>
  791. props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
  792. }
  793. className={
  794. props.index % 2 === 1
  795. ? RIGHT_COLUMN_ODD_CLASSNAME
  796. : RIGHT_COLUMN_EVEN_CLASSNAME
  797. }
  798. onDoubleClick={e => {
  799. e.stopPropagation();
  800. props.manager.onZoomIntoSpace(props.node.space!);
  801. }}
  802. >
  803. <TraceBar
  804. virtualized_index={virtualized_index}
  805. manager={props.manager}
  806. color={makeTraceNodeBarColor(props.theme, props.node)}
  807. node_space={props.node.space}
  808. errors={props.node.errors}
  809. performance_issues={props.node.performance_issues}
  810. profiles={NO_PROFILES}
  811. />
  812. <button
  813. ref={ref =>
  814. props.manager.registerArrowRef(ref, props.node.space!, virtualized_index)
  815. }
  816. className="TraceArrow"
  817. onClick={_e => {
  818. props.manager.onBringRowIntoView(props.node.space!);
  819. }}
  820. >
  821. <TraceIcons.Chevron direction="left" />
  822. </button>
  823. </div>
  824. </div>
  825. );
  826. }
  827. if (isMissingInstrumentationNode(props.node)) {
  828. return (
  829. <div
  830. key={props.index}
  831. ref={r =>
  832. props.tabIndex === 0
  833. ? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
  834. : null
  835. }
  836. tabIndex={props.tabIndex}
  837. className={`TraceRow ${rowSearchClassName}`}
  838. onClick={e => props.onRowClick(props.node, e, props.index)}
  839. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  840. style={{
  841. top: props.style.top,
  842. height: props.style.height,
  843. }}
  844. >
  845. <div
  846. className="TraceLeftColumn"
  847. ref={r =>
  848. props.manager.registerColumnRef('list', r, virtualized_index, props.node)
  849. }
  850. >
  851. <div
  852. className="TraceLeftColumnInner"
  853. style={{
  854. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  855. }}
  856. >
  857. <div className="TraceChildrenCountWrapper">
  858. <Connectors node={props.node} manager={props.manager} />
  859. </div>
  860. <span className="TraceOperation">{t('Missing instrumentation')}</span>
  861. </div>
  862. </div>
  863. <div
  864. ref={r =>
  865. props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
  866. }
  867. className={
  868. props.index % 2 === 1
  869. ? RIGHT_COLUMN_ODD_CLASSNAME
  870. : RIGHT_COLUMN_EVEN_CLASSNAME
  871. }
  872. onDoubleClick={e => {
  873. e.stopPropagation();
  874. props.manager.onZoomIntoSpace(props.node.space!);
  875. }}
  876. >
  877. <TraceBar
  878. virtualized_index={virtualized_index}
  879. manager={props.manager}
  880. color={makeTraceNodeBarColor(props.theme, props.node)}
  881. node_space={props.node.space}
  882. performance_issues={NO_PERFORMANCE_ISSUES}
  883. profiles={NO_PROFILES}
  884. errors={NO_ERRORS}
  885. />
  886. <button
  887. ref={ref =>
  888. props.manager.registerArrowRef(ref, props.node.space!, virtualized_index)
  889. }
  890. className="TraceArrow"
  891. onClick={_e => {
  892. props.manager.onBringRowIntoView(props.node.space!);
  893. }}
  894. >
  895. <TraceIcons.Chevron direction="left" />
  896. </button>
  897. </div>
  898. </div>
  899. );
  900. }
  901. if (isTraceNode(props.node)) {
  902. return (
  903. <div
  904. key={props.index}
  905. ref={r =>
  906. props.tabIndex === 0
  907. ? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
  908. : null
  909. }
  910. tabIndex={props.tabIndex}
  911. className={`TraceRow ${rowSearchClassName} ${props.node.has_errors ? 'Errored' : ''}`}
  912. onClick={e => props.onRowClick(props.node, e, props.index)}
  913. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  914. style={{
  915. top: props.style.top,
  916. height: props.style.height,
  917. }}
  918. >
  919. <div
  920. className="TraceLeftColumn"
  921. ref={r =>
  922. props.manager.registerColumnRef('list', r, virtualized_index, props.node)
  923. }
  924. >
  925. <div
  926. className="TraceLeftColumnInner"
  927. style={{
  928. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  929. }}
  930. >
  931. {' '}
  932. <div className="TraceChildrenCountWrapper Root">
  933. <Connectors node={props.node} manager={props.manager} />
  934. {props.node.children.length > 0 || props.node.canFetch ? (
  935. <ChildrenButton icon={''} status={'idle'} expanded onClick={() => void 0}>
  936. {props.node.children.length > 0
  937. ? COUNT_FORMATTER.format(props.node.children.length)
  938. : null}
  939. </ChildrenButton>
  940. ) : null}
  941. </div>
  942. <span className="TraceOperation">{t('Trace')}</span>
  943. <strong className="TraceEmDash"> — </strong>
  944. <span className="TraceDescription">{props.trace_id}</span>
  945. </div>
  946. </div>
  947. <div
  948. ref={r =>
  949. props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
  950. }
  951. className={
  952. props.index % 2 === 1
  953. ? RIGHT_COLUMN_ODD_CLASSNAME
  954. : RIGHT_COLUMN_EVEN_CLASSNAME
  955. }
  956. onDoubleClick={e => {
  957. e.stopPropagation();
  958. props.manager.onZoomIntoSpace(props.node.space!);
  959. }}
  960. >
  961. <TraceBar
  962. virtualized_index={virtualized_index}
  963. manager={props.manager}
  964. color={makeTraceNodeBarColor(props.theme, props.node)}
  965. node_space={props.node.space}
  966. errors={NO_ERRORS}
  967. performance_issues={NO_PERFORMANCE_ISSUES}
  968. profiles={NO_PROFILES}
  969. />
  970. <button
  971. ref={ref =>
  972. props.manager.registerArrowRef(ref, props.node.space!, virtualized_index)
  973. }
  974. className="TraceArrow"
  975. onClick={_e => {
  976. props.manager.onBringRowIntoView(props.node.space!);
  977. }}
  978. >
  979. <TraceIcons.Chevron direction="left" />
  980. </button>
  981. </div>
  982. </div>
  983. );
  984. }
  985. if (isTraceErrorNode(props.node)) {
  986. return (
  987. <div
  988. key={props.index}
  989. ref={r =>
  990. props.tabIndex === 0
  991. ? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
  992. : null
  993. }
  994. tabIndex={props.tabIndex}
  995. className={`TraceRow ${rowSearchClassName} Errored`}
  996. onClick={e => props.onRowClick(props.node, e, props.index)}
  997. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  998. style={{
  999. top: props.style.top,
  1000. height: props.style.height,
  1001. }}
  1002. >
  1003. <div
  1004. className="TraceLeftColumn"
  1005. ref={r =>
  1006. props.manager.registerColumnRef('list', r, virtualized_index, props.node)
  1007. }
  1008. >
  1009. <div
  1010. className="TraceLeftColumnInner"
  1011. style={{
  1012. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  1013. }}
  1014. >
  1015. <div className="TraceChildrenCountWrapper">
  1016. <Connectors node={props.node} manager={props.manager} />{' '}
  1017. </div>
  1018. <PlatformIcon
  1019. platform={props.projects[props.node.value.project_slug] ?? 'default'}
  1020. />
  1021. <span className="TraceOperation">{t('Error')}</span>
  1022. <strong className="TraceEmDash"> — </strong>
  1023. <span className="TraceDescription">{props.node.value.title}</span>
  1024. </div>
  1025. </div>
  1026. <div
  1027. ref={r =>
  1028. props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
  1029. }
  1030. className={
  1031. props.index % 2 === 1
  1032. ? RIGHT_COLUMN_ODD_CLASSNAME
  1033. : RIGHT_COLUMN_EVEN_CLASSNAME
  1034. }
  1035. onDoubleClick={e => {
  1036. e.stopPropagation();
  1037. props.manager.onZoomIntoSpace(props.node.space!);
  1038. }}
  1039. >
  1040. <InvisibleTraceBar
  1041. node_space={props.node.space}
  1042. manager={props.manager}
  1043. virtualizedIndex={virtualized_index}
  1044. >
  1045. {typeof props.node.value.timestamp === 'number' ? (
  1046. <div className="TraceError">
  1047. <TraceIcons.Fire />
  1048. </div>
  1049. ) : null}
  1050. </InvisibleTraceBar>
  1051. </div>
  1052. </div>
  1053. );
  1054. }
  1055. if (isNoDataNode(props.node)) {
  1056. return (
  1057. <div
  1058. key={props.index}
  1059. ref={r =>
  1060. props.tabIndex === 0
  1061. ? maybeFocusRow(r, props.node, props.previouslyFocusedNodeRef)
  1062. : null
  1063. }
  1064. tabIndex={props.tabIndex}
  1065. className={`TraceRow ${rowSearchClassName}`}
  1066. onClick={e => props.onRowClick(props.node, e, props.index)}
  1067. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  1068. style={{
  1069. top: props.style.top,
  1070. height: props.style.height,
  1071. }}
  1072. >
  1073. <div
  1074. className="TraceLeftColumn"
  1075. ref={r =>
  1076. props.manager.registerColumnRef('list', r, virtualized_index, props.node)
  1077. }
  1078. >
  1079. <div
  1080. className="TraceLeftColumnInner"
  1081. style={{
  1082. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  1083. }}
  1084. >
  1085. <div className="TraceChildrenCountWrapper">
  1086. <Connectors node={props.node} manager={props.manager} />
  1087. </div>
  1088. <span className="TraceOperation">{t('Empty')}</span>{' '}
  1089. <strong className="TraceEmDash"> — </strong>
  1090. <span className="TraceDescription">
  1091. {tct('[type] did not report any span data', {
  1092. type: props.node.parent
  1093. ? isTransactionNode(props.node.parent)
  1094. ? 'Transaction'
  1095. : isSpanNode(props.node.parent)
  1096. ? 'Span'
  1097. : ''
  1098. : '',
  1099. })}
  1100. </span>
  1101. </div>
  1102. </div>
  1103. <div
  1104. ref={r =>
  1105. props.manager.registerColumnRef('span_list', r, virtualized_index, props.node)
  1106. }
  1107. className={
  1108. props.index % 2 === 1
  1109. ? RIGHT_COLUMN_ODD_CLASSNAME
  1110. : RIGHT_COLUMN_EVEN_CLASSNAME
  1111. }
  1112. />
  1113. </div>
  1114. );
  1115. }
  1116. return null;
  1117. }
  1118. function RenderPlaceholderRow(props: {
  1119. index: number;
  1120. manager: VirtualizedViewManager;
  1121. node: TraceTreeNode<TraceTree.NodeValue>;
  1122. style: React.CSSProperties;
  1123. theme: Theme;
  1124. }) {
  1125. return (
  1126. <div
  1127. key={props.index}
  1128. className="TraceRow"
  1129. style={{
  1130. top: props.style.top,
  1131. height: props.style.height,
  1132. pointerEvents: 'none',
  1133. color: props.theme.subText,
  1134. paddingLeft: 8,
  1135. }}
  1136. >
  1137. <div
  1138. className="TraceLeftColumn"
  1139. style={{width: props.manager.columns.list.width * 100 + '%'}}
  1140. >
  1141. <div
  1142. className="TraceLeftColumnInner"
  1143. style={{
  1144. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  1145. }}
  1146. >
  1147. <div
  1148. className={`TraceChildrenCountWrapper ${isTraceNode(props.node) ? 'Root' : ''}`}
  1149. >
  1150. <Connectors node={props.node} manager={props.manager} />
  1151. {props.node.children.length > 0 || props.node.canFetch ? (
  1152. <ChildrenButton
  1153. icon="+"
  1154. status={props.node.fetchStatus}
  1155. expanded={props.node.expanded || props.node.zoomedIn}
  1156. onClick={() => void 0}
  1157. >
  1158. {props.node.children.length > 0
  1159. ? COUNT_FORMATTER.format(props.node.children.length)
  1160. : null}
  1161. </ChildrenButton>
  1162. ) : null}
  1163. </div>
  1164. <Placeholder
  1165. className="Placeholder"
  1166. height="12px"
  1167. width={randomBetween(20, 80) + '%'}
  1168. style={{
  1169. transition: 'all 30s ease-out',
  1170. }}
  1171. />
  1172. </div>
  1173. </div>
  1174. <div
  1175. className={
  1176. props.index % 2 === 1 ? RIGHT_COLUMN_ODD_CLASSNAME : RIGHT_COLUMN_EVEN_CLASSNAME
  1177. }
  1178. style={{
  1179. width: props.manager.columns.span_list.width * 100 + '%',
  1180. }}
  1181. >
  1182. <Placeholder
  1183. className="Placeholder"
  1184. height="12px"
  1185. width={randomBetween(20, 80) + '%'}
  1186. style={{
  1187. transition: 'all 30s ease-out',
  1188. transform: `translate(${randomBetween(0, 200) + 'px'}, 0)`,
  1189. }}
  1190. />
  1191. </div>
  1192. </div>
  1193. );
  1194. }
  1195. function randomBetween(min: number, max: number) {
  1196. return Math.floor(Math.random() * (max - min + 1) + min);
  1197. }
  1198. function Connectors(props: {
  1199. manager: VirtualizedViewManager;
  1200. node: TraceTreeNode<TraceTree.NodeValue>;
  1201. }) {
  1202. const showVerticalConnector =
  1203. ((props.node.expanded || props.node.zoomedIn) && props.node.children.length > 0) ||
  1204. (props.node.value && isParentAutogroupedNode(props.node));
  1205. // If the tail node of the collapsed node has no children,
  1206. // we don't want to render the vertical connector as no children
  1207. // are being rendered as the chain is entirely collapsed
  1208. const hideVerticalConnector =
  1209. showVerticalConnector &&
  1210. props.node.value &&
  1211. props.node instanceof ParentAutogroupNode &&
  1212. !props.node.tail.children.length;
  1213. return (
  1214. <Fragment>
  1215. {props.node.connectors.map((c, i) => {
  1216. return (
  1217. <div
  1218. key={i}
  1219. style={{
  1220. left: -(
  1221. Math.abs(Math.abs(c) - props.node.depth) * props.manager.row_depth_padding
  1222. ),
  1223. }}
  1224. className={`TraceVerticalConnector ${c < 0 ? 'Orphaned' : ''}`}
  1225. />
  1226. );
  1227. })}
  1228. {showVerticalConnector && !hideVerticalConnector ? (
  1229. <div className="TraceExpandedVerticalConnector" />
  1230. ) : null}
  1231. {props.node.isLastChild ? (
  1232. <div className="TraceVerticalLastChildConnector" />
  1233. ) : (
  1234. <div className="TraceVerticalConnector" />
  1235. )}
  1236. </Fragment>
  1237. );
  1238. }
  1239. function ChildrenButton(props: {
  1240. children: React.ReactNode;
  1241. expanded: boolean;
  1242. icon: React.ReactNode;
  1243. onClick: (e: React.MouseEvent) => void;
  1244. status: TraceTreeNode<any>['fetchStatus'] | undefined;
  1245. errored?: boolean;
  1246. }) {
  1247. return (
  1248. <button
  1249. className={`TraceChildrenCount ${props.errored ? 'Errored' : ''}`}
  1250. onClick={props.onClick}
  1251. >
  1252. <div className="TraceChildrenCountContent">{props.children}</div>
  1253. <div className="TraceChildrenCountAction">
  1254. {props.icon}
  1255. {props.status === 'loading' ? (
  1256. <LoadingIndicator className="TraceActionsLoadingIndicator" size={8} />
  1257. ) : null}
  1258. </div>
  1259. </button>
  1260. );
  1261. }
  1262. interface TraceBarProps {
  1263. color: string;
  1264. errors: TraceTreeNode<TraceTree.Transaction>['errors'];
  1265. manager: VirtualizedViewManager;
  1266. node_space: [number, number] | null;
  1267. performance_issues: TraceTreeNode<TraceTree.Transaction>['performance_issues'];
  1268. profiles: TraceTreeNode<TraceTree.NodeValue>['profiles'];
  1269. virtualized_index: number;
  1270. }
  1271. function TraceBar(props: TraceBarProps) {
  1272. if (!props.node_space) {
  1273. return null;
  1274. }
  1275. const duration = formatTraceDuration(props.node_space[1]);
  1276. const spanTransform = props.manager.computeSpanCSSMatrixTransform(props.node_space);
  1277. const [inside, textTransform] = props.manager.computeSpanTextPlacement(
  1278. props.node_space,
  1279. duration
  1280. );
  1281. return (
  1282. <Fragment>
  1283. <div
  1284. ref={r =>
  1285. props.manager.registerSpanBarRef(r, props.node_space!, props.virtualized_index)
  1286. }
  1287. className="TraceBar"
  1288. style={
  1289. {
  1290. transform: `matrix(${spanTransform.join(',')})`,
  1291. '--inverse-span-scale': 1 / spanTransform[0],
  1292. backgroundColor: props.color,
  1293. // unknown css variables cannot be part of the style object
  1294. } as React.CSSProperties
  1295. }
  1296. >
  1297. {props.profiles.length > 0 ? (
  1298. <Profiles
  1299. node_space={props.node_space}
  1300. profiles={props.profiles}
  1301. manager={props.manager}
  1302. />
  1303. ) : null}
  1304. {props.errors.size > 0 ? (
  1305. <Errors
  1306. node_space={props.node_space}
  1307. errors={props.errors}
  1308. manager={props.manager}
  1309. />
  1310. ) : null}
  1311. {props.performance_issues.size > 0 ? (
  1312. <PerformanceIssues
  1313. node_space={props.node_space}
  1314. performance_issues={props.performance_issues}
  1315. manager={props.manager}
  1316. />
  1317. ) : null}
  1318. </div>
  1319. <div
  1320. ref={r =>
  1321. props.manager.registerSpanBarTextRef(
  1322. r,
  1323. duration,
  1324. props.node_space!,
  1325. props.virtualized_index
  1326. )
  1327. }
  1328. className="TraceBarDuration"
  1329. style={{
  1330. color: inside ? 'white' : '',
  1331. transform: `translate(${textTransform ?? 0}px, 0)`,
  1332. }}
  1333. >
  1334. {duration}
  1335. </div>
  1336. </Fragment>
  1337. );
  1338. }
  1339. interface InvisibleTraceBarProps {
  1340. children: React.ReactNode;
  1341. manager: VirtualizedViewManager;
  1342. node_space: [number, number] | null;
  1343. virtualizedIndex: number;
  1344. }
  1345. function InvisibleTraceBar(props: InvisibleTraceBarProps) {
  1346. if (!props.node_space || !props.children) {
  1347. return null;
  1348. }
  1349. const spanTransform = `translateX(${props.manager.computeTransformXFromTimestamp(props.node_space[0])}px)`;
  1350. return (
  1351. <div
  1352. ref={r =>
  1353. props.manager.registerInvisibleBarRef(
  1354. r,
  1355. props.node_space!,
  1356. props.virtualizedIndex
  1357. )
  1358. }
  1359. className="TraceBar Invisible"
  1360. style={
  1361. {
  1362. transform: spanTransform,
  1363. // undefined css variables break style rules
  1364. '--inverse-span-scale': 1,
  1365. // unknown css variables cannot be part of the style object
  1366. } as React.CSSProperties
  1367. }
  1368. onDoubleClick={e => {
  1369. e.stopPropagation();
  1370. props.manager.onZoomIntoSpace(props.node_space!);
  1371. }}
  1372. >
  1373. {props.children}
  1374. </div>
  1375. );
  1376. }
  1377. interface PerformanceIssuesProps {
  1378. manager: VirtualizedViewManager;
  1379. node_space: [number, number] | null;
  1380. performance_issues: TraceTreeNode<TraceTree.Transaction>['performance_issues'];
  1381. }
  1382. function PerformanceIssues(props: PerformanceIssuesProps) {
  1383. const performance_issues = useMemo(() => {
  1384. return [...props.performance_issues];
  1385. }, [props.performance_issues]);
  1386. if (!props.performance_issues.size) {
  1387. return null;
  1388. }
  1389. return (
  1390. <Fragment>
  1391. {performance_issues.map((issue, _i) => {
  1392. const timestamp = issue.start * 1e3;
  1393. // Clamp the issue timestamp to the span's timestamp
  1394. const left = props.manager.computeRelativeLeftPositionFromOrigin(
  1395. clamp(
  1396. timestamp,
  1397. props.node_space![0],
  1398. props.node_space![0] + props.node_space![1]
  1399. ),
  1400. props.node_space!
  1401. );
  1402. const max_width = 100 - left;
  1403. const issue_duration = (issue.end - issue.start) * 1e3;
  1404. const width = clamp((issue_duration / props.node_space![1]) * 100, 0, max_width);
  1405. return (
  1406. <div
  1407. key={issue.event_id}
  1408. className="TracePerformanceIssue"
  1409. style={{left: left * 100 + '%', width: width + '%'}}
  1410. >
  1411. <div className="TraceError" style={{left: 0}}>
  1412. <TraceIcons.Fire />
  1413. </div>
  1414. </div>
  1415. );
  1416. })}
  1417. </Fragment>
  1418. );
  1419. }
  1420. interface ErrorsProps {
  1421. errors: TraceTreeNode<TraceTree.Transaction>['errors'];
  1422. manager: VirtualizedViewManager;
  1423. node_space: [number, number] | null;
  1424. }
  1425. function Errors(props: ErrorsProps) {
  1426. const errors = useMemo(() => {
  1427. return [...props.errors];
  1428. }, [props.errors]);
  1429. if (!props.errors.size) {
  1430. return null;
  1431. }
  1432. return (
  1433. <Fragment>
  1434. {errors.map((error, _i) => {
  1435. const timestamp = error.timestamp ? error.timestamp * 1e3 : props.node_space![0];
  1436. // Clamp the error timestamp to the span's timestamp
  1437. const left = props.manager.computeRelativeLeftPositionFromOrigin(
  1438. clamp(
  1439. timestamp,
  1440. props.node_space![0],
  1441. props.node_space![0] + props.node_space![1]
  1442. ),
  1443. props.node_space!
  1444. );
  1445. return (
  1446. <div
  1447. key={error.event_id}
  1448. className="TraceError"
  1449. style={{left: left * 100 + '%'}}
  1450. >
  1451. <TraceIcons.Fire />
  1452. </div>
  1453. );
  1454. })}
  1455. </Fragment>
  1456. );
  1457. }
  1458. interface ProfilesProps {
  1459. manager: VirtualizedViewManager;
  1460. node_space: [number, number] | null;
  1461. profiles: TraceTree.Profile[];
  1462. }
  1463. function Profiles(props: ProfilesProps) {
  1464. if (!props.profiles.length) {
  1465. return null;
  1466. }
  1467. return (
  1468. <Fragment>
  1469. {props.profiles.map((profile, _i) => {
  1470. const timestamp = profile.space[0];
  1471. // Clamp the profile timestamp to the span's timestamp
  1472. const left = props.manager.computeRelativeLeftPositionFromOrigin(
  1473. clamp(
  1474. timestamp,
  1475. props.node_space![0],
  1476. props.node_space![0] + props.node_space![1]
  1477. ),
  1478. props.node_space!
  1479. );
  1480. return (
  1481. <div
  1482. key={profile.profile_id}
  1483. className="TraceProfile"
  1484. style={{left: left * 100 + '%'}}
  1485. >
  1486. <TraceIcons.Profile />
  1487. </div>
  1488. );
  1489. })}
  1490. </Fragment>
  1491. );
  1492. }
  1493. interface AutogroupedTraceBarProps {
  1494. color: string;
  1495. entire_space: [number, number] | null;
  1496. errors: TraceTreeNode<TraceTree.Transaction>['errors'];
  1497. manager: VirtualizedViewManager;
  1498. node_spaces: [number, number][];
  1499. performance_issues: TraceTreeNode<TraceTree.Transaction>['performance_issues'];
  1500. profiles: TraceTreeNode<TraceTree.NodeValue>['profiles'];
  1501. virtualized_index: number;
  1502. }
  1503. function AutogroupedTraceBar(props: AutogroupedTraceBarProps) {
  1504. if (props.node_spaces && props.node_spaces.length <= 1) {
  1505. return (
  1506. <TraceBar
  1507. color={props.color}
  1508. node_space={props.entire_space}
  1509. manager={props.manager}
  1510. virtualized_index={props.virtualized_index}
  1511. errors={props.errors}
  1512. performance_issues={props.performance_issues}
  1513. profiles={props.profiles}
  1514. />
  1515. );
  1516. }
  1517. if (!props.node_spaces || !props.entire_space) {
  1518. return null;
  1519. }
  1520. const duration = formatTraceDuration(props.entire_space[1]);
  1521. const spanTransform = props.manager.computeSpanCSSMatrixTransform(props.entire_space);
  1522. const [inside, textTransform] = props.manager.computeSpanTextPlacement(
  1523. props.entire_space,
  1524. duration
  1525. );
  1526. return (
  1527. <Fragment>
  1528. <div
  1529. ref={r =>
  1530. props.manager.registerSpanBarRef(
  1531. r,
  1532. props.entire_space!,
  1533. props.virtualized_index
  1534. )
  1535. }
  1536. className="TraceBar Invisible"
  1537. style={{
  1538. transform: `matrix(${spanTransform.join(',')})`,
  1539. backgroundColor: props.color,
  1540. }}
  1541. >
  1542. {props.node_spaces.map((node_space, i) => {
  1543. const width = node_space[1] / props.entire_space![1];
  1544. const left = props.manager.computeRelativeLeftPositionFromOrigin(
  1545. node_space[0],
  1546. props.entire_space!
  1547. );
  1548. return (
  1549. <div
  1550. key={i}
  1551. className="TraceBar"
  1552. style={{
  1553. left: `${left * 100}%`,
  1554. width: `${width * 100}%`,
  1555. backgroundColor: props.color,
  1556. }}
  1557. />
  1558. );
  1559. })}
  1560. {props.profiles.length > 0 ? (
  1561. <Profiles
  1562. node_space={props.entire_space}
  1563. profiles={props.profiles}
  1564. manager={props.manager}
  1565. />
  1566. ) : null}
  1567. {props.errors.size > 0 ? (
  1568. <Errors
  1569. node_space={props.entire_space}
  1570. errors={props.errors}
  1571. manager={props.manager}
  1572. />
  1573. ) : null}
  1574. {props.performance_issues.size > 0 ? (
  1575. <PerformanceIssues
  1576. node_space={props.entire_space}
  1577. performance_issues={props.performance_issues}
  1578. manager={props.manager}
  1579. />
  1580. ) : null}
  1581. </div>
  1582. <div
  1583. ref={r =>
  1584. props.manager.registerSpanBarTextRef(
  1585. r,
  1586. duration,
  1587. props.entire_space!,
  1588. props.virtualized_index
  1589. )
  1590. }
  1591. className="TraceBarDuration"
  1592. style={{
  1593. color: inside ? 'white' : '',
  1594. transform: `translate(${textTransform ?? 0}px, 0)`,
  1595. }}
  1596. >
  1597. {duration}
  1598. </div>
  1599. </Fragment>
  1600. );
  1601. }
  1602. /**
  1603. * This is a wrapper around the Trace component to apply styles
  1604. * to the trace tree. It exists because we _do not_ want to trigger
  1605. * emotion's css parsing logic as it is very slow and will cause
  1606. * the scrolling to flicker.
  1607. */
  1608. const TraceStylingWrapper = styled('div')`
  1609. margin: auto;
  1610. overscroll-behavior: none;
  1611. box-shadow: 0 0 0 1px ${p => p.theme.border};
  1612. position: absolute;
  1613. left: 0;
  1614. top: 0;
  1615. width: 100%;
  1616. height: 100%;
  1617. grid-area: trace;
  1618. padding-top: 26px;
  1619. &.WithIndicators {
  1620. padding-top: 44px;
  1621. &:before {
  1622. height: 44px;
  1623. .TraceScrollbarContainer {
  1624. height: 44px;
  1625. }
  1626. }
  1627. .TraceIndicator.Timeline {
  1628. .TraceIndicatorLabel {
  1629. top: 26px;
  1630. }
  1631. .TraceIndicatorLine {
  1632. top: 30px;
  1633. }
  1634. }
  1635. }
  1636. &:before {
  1637. content: '';
  1638. position: absolute;
  1639. left: 0;
  1640. top: 0;
  1641. width: 100%;
  1642. height: 26px;
  1643. background-color: ${p => p.theme.backgroundSecondary};
  1644. border-bottom: 1px solid ${p => p.theme.border};
  1645. }
  1646. &.Loading {
  1647. .TraceRow {
  1648. .TraceLeftColumnInner {
  1649. width: 100%;
  1650. }
  1651. }
  1652. .TraceRightColumn {
  1653. background-color: transparent !important;
  1654. }
  1655. .TraceDivider {
  1656. pointer-events: none;
  1657. }
  1658. }
  1659. .TraceScrollbarContainer {
  1660. left: 0;
  1661. top: 0;
  1662. height: 26px;
  1663. position: absolute;
  1664. overflow-x: auto;
  1665. overscroll-behavior: none;
  1666. will-change: transform;
  1667. .TraceScrollbarScroller {
  1668. height: 1px;
  1669. pointer-events: none;
  1670. visibility: hidden;
  1671. }
  1672. .TraceScrollbarHandle {
  1673. width: 24px;
  1674. height: 12px;
  1675. border-radius: 6px;
  1676. }
  1677. }
  1678. .TraceDivider {
  1679. position: absolute;
  1680. height: 100%;
  1681. background-color: transparent;
  1682. top: 0;
  1683. cursor: col-resize;
  1684. z-index: 10;
  1685. transform: translateX(calc(var(--translate-x) * 1px));
  1686. &:before {
  1687. content: '';
  1688. position: absolute;
  1689. width: 1px;
  1690. height: 100%;
  1691. background-color: ${p => p.theme.border};
  1692. left: 50%;
  1693. }
  1694. &:hover {
  1695. &:before {
  1696. background-color: ${p => p.theme.purple300};
  1697. }
  1698. }
  1699. }
  1700. .TraceIndicatorContainer {
  1701. overflow: hidden;
  1702. width: 100%;
  1703. height: 100%;
  1704. position: absolute;
  1705. right: 0;
  1706. top: 0;
  1707. transform: translateX(calc(var(--translate-x) * 1px));
  1708. z-index: 10;
  1709. pointer-events: none;
  1710. }
  1711. .TraceIndicator {
  1712. z-index: 1;
  1713. width: 3px;
  1714. height: 100%;
  1715. top: 0;
  1716. position: absolute;
  1717. .TraceIndicatorLabel {
  1718. min-width: 34px;
  1719. text-align: center;
  1720. position: absolute;
  1721. font-size: 10px;
  1722. font-weight: bold;
  1723. color: ${p => p.theme.textColor};
  1724. background-color: ${p => p.theme.background};
  1725. border-radius: ${p => p.theme.borderRadius};
  1726. border: 1px solid ${p => p.theme.border};
  1727. padding: 2px;
  1728. display: inline-block;
  1729. line-height: 1;
  1730. margin-top: 2px;
  1731. white-space: nowrap;
  1732. }
  1733. .TraceIndicatorLine {
  1734. width: 1px;
  1735. height: 100%;
  1736. top: 20px;
  1737. position: absolute;
  1738. left: 50%;
  1739. transform: translateX(-2px);
  1740. background: repeating-linear-gradient(
  1741. to bottom,
  1742. transparent 0 4px,
  1743. ${p => p.theme.textColor} 4px 8px
  1744. )
  1745. 80%/2px 100% no-repeat;
  1746. }
  1747. &.Errored {
  1748. .TraceIndicatorLabel {
  1749. border: 1px solid ${p => p.theme.error};
  1750. color: ${p => p.theme.error};
  1751. }
  1752. .TraceIndicatorLine {
  1753. background: repeating-linear-gradient(
  1754. to bottom,
  1755. transparent 0 4px,
  1756. ${p => p.theme.error} 4px 8px
  1757. )
  1758. 80%/2px 100% no-repeat;
  1759. }
  1760. }
  1761. &.Timeline {
  1762. opacity: 1;
  1763. z-index: 1;
  1764. pointer-events: none;
  1765. .TraceIndicatorLabel {
  1766. font-weight: normal;
  1767. min-width: 0;
  1768. top: 8px;
  1769. width: auto;
  1770. border: none;
  1771. background-color: transparent;
  1772. color: ${p => p.theme.subText};
  1773. }
  1774. .TraceIndicatorLine {
  1775. background: ${p => p.theme.translucentGray100};
  1776. top: 8px;
  1777. }
  1778. }
  1779. }
  1780. .TraceRow {
  1781. display: flex;
  1782. align-items: center;
  1783. position: absolute;
  1784. height: 24px;
  1785. width: 100%;
  1786. transition: none;
  1787. font-size: ${p => p.theme.fontSizeSmall};
  1788. --row-background-odd: ${p => p.theme.translucentSurface100};
  1789. --row-background-hover: ${p => p.theme.translucentSurface100};
  1790. --row-background-focused: ${p => p.theme.translucentSurface200};
  1791. &.Hidden {
  1792. position: absolute;
  1793. height: 100%;
  1794. width: 100%;
  1795. top: 0;
  1796. z-index: -1;
  1797. &:hover {
  1798. background-color: transparent;
  1799. }
  1800. * {
  1801. cursor: default !important;
  1802. }
  1803. }
  1804. .TraceError {
  1805. position: absolute;
  1806. top: 50%;
  1807. transform: translate(-50%, -50%) scaleX(var(--inverse-span-scale));
  1808. background: ${p => p.theme.background};
  1809. width: 18px !important;
  1810. height: 18px !important;
  1811. background-color: ${p => p.theme.error};
  1812. border-radius: 50%;
  1813. display: flex;
  1814. align-items: center;
  1815. justify-content: center;
  1816. svg {
  1817. fill: ${p => p.theme.white};
  1818. }
  1819. }
  1820. .TraceProfile {
  1821. position: absolute;
  1822. top: 50%;
  1823. transform: translate(-50%, -50%) scaleX(var(--inverse-span-scale));
  1824. background: ${p => p.theme.background};
  1825. width: 18px !important;
  1826. height: 18px !important;
  1827. background-color: ${p => p.theme.purple300};
  1828. border-radius: 50%;
  1829. display: flex;
  1830. align-items: center;
  1831. justify-content: center;
  1832. svg {
  1833. width: 12px;
  1834. height: 12px;
  1835. margin-left: 2px;
  1836. fill: ${p => p.theme.white};
  1837. }
  1838. }
  1839. .TracePerformanceIssue {
  1840. position: absolute;
  1841. top: 0;
  1842. display: flex;
  1843. align-items: center;
  1844. justify-content: flex-start;
  1845. background-color: ${p => p.theme.error};
  1846. height: 16px;
  1847. }
  1848. .TraceRightColumn.Odd {
  1849. background-color: var(--row-background-odd);
  1850. }
  1851. &:hover {
  1852. background-color: var(--row-background-hovered);
  1853. }
  1854. &.Highlight {
  1855. box-shadow: inset 0 0 0 1px ${p => p.theme.blue200} !important;
  1856. .TraceLeftColumn {
  1857. box-shadow: inset 0px 0 0px 1px ${p => p.theme.blue200} !important;
  1858. }
  1859. }
  1860. &.Highlight,
  1861. &:focus,
  1862. &[tabindex='0'] {
  1863. outline: none;
  1864. background-color: var(--row-background-focused);
  1865. .TraceRightColumn.Odd {
  1866. background-color: transparent !important;
  1867. }
  1868. }
  1869. &:focus,
  1870. &[tabindex='0'] {
  1871. background-color: var(--row-background-focused);
  1872. box-shadow: inset 0 0 0 1px ${p => p.theme.blue300} !important;
  1873. .TraceLeftColumn {
  1874. box-shadow: inset 0px 0 0px 1px ${p => p.theme.blue300} !important;
  1875. }
  1876. .TraceRightColumn.Odd {
  1877. background-color: transparent !important;
  1878. }
  1879. }
  1880. &.Errored {
  1881. color: ${p => p.theme.error};
  1882. .TraceChildrenCount {
  1883. border: 2px solid ${p => p.theme.error};
  1884. svg {
  1885. fill: ${p => p.theme.error};
  1886. }
  1887. }
  1888. &:focus {
  1889. box-shadow: inset 0 0 0 1px ${p => p.theme.red300} !important;
  1890. .TraceLeftColumn {
  1891. box-shadow: inset 0px 0 0px 1px ${p => p.theme.red300} !important;
  1892. }
  1893. }
  1894. }
  1895. &.SearchResult {
  1896. background-color: ${p => p.theme.yellow100};
  1897. .TraceRightColumn {
  1898. background-color: transparent;
  1899. }
  1900. }
  1901. &.Autogrouped {
  1902. color: ${p => p.theme.blue300};
  1903. &.Errored {
  1904. .TraceChildrenCount {
  1905. background-color: ${p => p.theme.error} !important;
  1906. }
  1907. }
  1908. .TraceDescription {
  1909. font-weight: bold;
  1910. }
  1911. .TraceChildrenCountWrapper {
  1912. button {
  1913. color: ${p => p.theme.white};
  1914. background-color: ${p => p.theme.blue300};
  1915. }
  1916. svg {
  1917. fill: ${p => p.theme.white};
  1918. }
  1919. }
  1920. }
  1921. }
  1922. .TraceLeftColumn {
  1923. height: 100%;
  1924. white-space: nowrap;
  1925. display: flex;
  1926. align-items: center;
  1927. overflow: hidden;
  1928. will-change: width;
  1929. box-shadow: inset 1px 0 0px 0px transparent;
  1930. cursor: pointer;
  1931. width: calc(var(--list-column-width) * 100%);
  1932. .TraceLeftColumnInner {
  1933. height: 100%;
  1934. white-space: nowrap;
  1935. display: flex;
  1936. align-items: center;
  1937. will-change: transform;
  1938. transform-origin: left center;
  1939. padding-right: ${space(2)};
  1940. img {
  1941. width: 16px;
  1942. height: 16px;
  1943. }
  1944. }
  1945. }
  1946. .TraceRightColumn {
  1947. height: 100%;
  1948. overflow: hidden;
  1949. position: relative;
  1950. display: flex;
  1951. align-items: center;
  1952. will-change: width;
  1953. z-index: 1;
  1954. cursor: pointer;
  1955. width: calc(var(--span-column-width) * 100%);
  1956. &:hover {
  1957. .TraceArrow.Visible {
  1958. opacity: 1;
  1959. transition: 300ms 300ms ease-out;
  1960. pointer-events: auto;
  1961. }
  1962. }
  1963. }
  1964. .TraceBar {
  1965. position: absolute;
  1966. height: 16px;
  1967. width: 100%;
  1968. background-color: black;
  1969. transform-origin: left center;
  1970. &.Invisible {
  1971. background-color: transparent !important;
  1972. > div {
  1973. height: 100%;
  1974. }
  1975. }
  1976. svg {
  1977. width: 14px;
  1978. height: 14px;
  1979. }
  1980. }
  1981. .TraceArrow {
  1982. position: absolute;
  1983. pointer-events: none;
  1984. top: 0;
  1985. width: 14px;
  1986. height: 24px;
  1987. opacity: 0;
  1988. background-color: transparent;
  1989. border: none;
  1990. transition: 60ms ease-out;
  1991. font-size: ${p => p.theme.fontSizeMedium};
  1992. color: ${p => p.theme.subText};
  1993. padding: 0 2px;
  1994. display: flex;
  1995. align-items: center;
  1996. svg {
  1997. fill: ${p => p.theme.subText};
  1998. }
  1999. &.Left {
  2000. left: 0;
  2001. }
  2002. &.Right {
  2003. right: 0;
  2004. transform: rotate(180deg);
  2005. }
  2006. }
  2007. .TraceBarDuration {
  2008. display: inline-block;
  2009. transform-origin: left center;
  2010. font-size: ${p => p.theme.fontSizeExtraSmall};
  2011. color: ${p => p.theme.gray300};
  2012. white-space: nowrap;
  2013. font-variant-numeric: tabular-nums;
  2014. position: absolute;
  2015. }
  2016. .TraceChildrenCount {
  2017. height: 16px;
  2018. white-space: nowrap;
  2019. min-width: 30px;
  2020. display: flex;
  2021. align-items: center;
  2022. justify-content: center;
  2023. border-radius: 99px;
  2024. padding: 0px 4px;
  2025. transition: all 0.15s ease-in-out;
  2026. background: ${p => p.theme.background};
  2027. border: 2px solid ${p => p.theme.border};
  2028. line-height: 0;
  2029. z-index: 1;
  2030. font-size: 10px;
  2031. box-shadow: ${p => p.theme.dropShadowLight};
  2032. margin-right: 8px;
  2033. .TraceChildrenCountContent {
  2034. + .TraceChildrenCountAction {
  2035. margin-left: 2px;
  2036. }
  2037. }
  2038. .TraceChildrenCountAction {
  2039. position: relative;
  2040. display: flex;
  2041. align-items: center;
  2042. justify-content: center;
  2043. }
  2044. .TraceActionsLoadingIndicator {
  2045. margin: 0;
  2046. position: absolute;
  2047. top: 50%;
  2048. left: 50%;
  2049. transform: translate(-50%, -50%);
  2050. background-color: ${p => p.theme.background};
  2051. animation: show 0.1s ease-in-out forwards;
  2052. @keyframes show {
  2053. from {
  2054. opacity: 0;
  2055. transform: translate(-50%, -50%) scale(0.86);
  2056. }
  2057. to {
  2058. opacity: 1;
  2059. transform: translate(-50%, -50%) scale(1);
  2060. }
  2061. }
  2062. .loading-indicator {
  2063. border-width: 2px;
  2064. }
  2065. .loading-message {
  2066. display: none;
  2067. }
  2068. }
  2069. svg {
  2070. width: 7px;
  2071. transition: none;
  2072. fill: ${p => p.theme.textColor};
  2073. }
  2074. }
  2075. .TraceChildrenCountWrapper {
  2076. display: flex;
  2077. justify-content: flex-end;
  2078. align-items: center;
  2079. min-width: 44px;
  2080. height: 100%;
  2081. position: relative;
  2082. button {
  2083. transition: none;
  2084. }
  2085. &.Orphaned {
  2086. .TraceVerticalConnector,
  2087. .TraceVerticalLastChildConnector,
  2088. .TraceExpandedVerticalConnector {
  2089. border-left: 2px dashed ${p => p.theme.border};
  2090. }
  2091. &::before {
  2092. border-bottom: 2px dashed ${p => p.theme.border};
  2093. }
  2094. }
  2095. &.Root {
  2096. &:before,
  2097. .TraceVerticalLastChildConnector {
  2098. visibility: hidden;
  2099. }
  2100. }
  2101. &::before {
  2102. content: '';
  2103. display: block;
  2104. width: 50%;
  2105. height: 2px;
  2106. border-bottom: 2px solid ${p => p.theme.border};
  2107. position: absolute;
  2108. left: 0;
  2109. top: 50%;
  2110. transform: translateY(-50%);
  2111. }
  2112. &::after {
  2113. content: '';
  2114. background-color: ${p => p.theme.border};
  2115. border-radius: 50%;
  2116. height: 6px;
  2117. width: 6px;
  2118. position: absolute;
  2119. left: 50%;
  2120. top: 50%;
  2121. transform: translateY(-50%);
  2122. }
  2123. }
  2124. .TraceVerticalConnector {
  2125. position: absolute;
  2126. left: 0;
  2127. top: 0;
  2128. bottom: 0;
  2129. height: 100%;
  2130. width: 2px;
  2131. border-left: 2px solid ${p => p.theme.border};
  2132. &.Orphaned {
  2133. border-left: 2px dashed ${p => p.theme.border};
  2134. }
  2135. }
  2136. .TraceVerticalLastChildConnector {
  2137. position: absolute;
  2138. left: 0;
  2139. top: 0;
  2140. bottom: 0;
  2141. height: 50%;
  2142. width: 2px;
  2143. border-left: 2px solid ${p => p.theme.border};
  2144. border-bottom-left-radius: 4px;
  2145. }
  2146. .TraceExpandedVerticalConnector {
  2147. position: absolute;
  2148. bottom: 0;
  2149. height: 50%;
  2150. left: 50%;
  2151. width: 2px;
  2152. border-left: 2px solid ${p => p.theme.border};
  2153. }
  2154. .TraceOperation {
  2155. margin-left: 4px;
  2156. text-overflow: ellipsis;
  2157. white-space: nowrap;
  2158. font-weight: bold;
  2159. }
  2160. .TraceEmDash {
  2161. margin-left: 4px;
  2162. margin-right: 4px;
  2163. }
  2164. .TraceDescription {
  2165. white-space: nowrap;
  2166. }
  2167. `;