trace.tsx 66 KB

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