trace.tsx 71 KB

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