trace.tsx 72 KB

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