trace.tsx 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814
  1. import type React from 'react';
  2. import {Fragment, useCallback, useEffect, useMemo, useRef, useState} from 'react';
  3. import {browserHistory} from 'react-router';
  4. import {AutoSizer, List} from 'react-virtualized';
  5. import {type Theme, useTheme} from '@emotion/react';
  6. import styled from '@emotion/styled';
  7. import * as qs from 'query-string';
  8. import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
  9. import Link from 'sentry/components/links/link';
  10. import LoadingIndicator from 'sentry/components/loadingIndicator';
  11. import {pickBarColor} from 'sentry/components/performance/waterfall/utils';
  12. import Placeholder from 'sentry/components/placeholder';
  13. import {generateIssueEventTarget} from 'sentry/components/quickTrace/utils';
  14. import {IconChevron, IconFire} from 'sentry/icons';
  15. import {t} from 'sentry/locale';
  16. import {space} from 'sentry/styles/space';
  17. import type {Organization, Project} from 'sentry/types';
  18. import {getDuration} from 'sentry/utils/formatters';
  19. import useApi from 'sentry/utils/useApi';
  20. import useOnClickOutside from 'sentry/utils/useOnClickOutside';
  21. import useOrganization from 'sentry/utils/useOrganization';
  22. import useProjects from 'sentry/utils/useProjects';
  23. import {
  24. getRovingIndexActionFromEvent,
  25. type RovingTabIndexAction,
  26. type RovingTabIndexUserActions,
  27. } from 'sentry/views/performance/newTraceDetails/rovingTabIndex';
  28. import type {
  29. TraceSearchAction,
  30. TraceSearchState,
  31. } from 'sentry/views/performance/newTraceDetails/traceSearch';
  32. import {
  33. isAutogroupedNode,
  34. isMissingInstrumentationNode,
  35. isParentAutogroupedNode,
  36. isSpanNode,
  37. isTraceErrorNode,
  38. isTraceNode,
  39. isTransactionNode,
  40. } from './guards';
  41. import {ParentAutogroupNode, type TraceTree, type TraceTreeNode} from './traceTree';
  42. import type {VirtualizedViewManager} from './virtualizedViewManager';
  43. function decodeScrollQueue(maybePath: unknown): TraceTree.NodePath[] | null {
  44. if (Array.isArray(maybePath)) {
  45. return maybePath;
  46. }
  47. if (typeof maybePath === 'string') {
  48. return [maybePath as TraceTree.NodePath];
  49. }
  50. return null;
  51. }
  52. const COUNT_FORMATTER = Intl.NumberFormat(undefined, {notation: 'compact'});
  53. interface RovingTabIndexState {
  54. index: number | null;
  55. items: number | null;
  56. node: TraceTreeNode<TraceTree.NodeValue> | null;
  57. }
  58. function computeNextIndexFromAction(
  59. current_index: number,
  60. action: RovingTabIndexUserActions,
  61. items: number
  62. ): number {
  63. switch (action) {
  64. case 'next':
  65. if (current_index === items) {
  66. return 0;
  67. }
  68. return current_index + 1;
  69. case 'previous':
  70. if (current_index === 0) {
  71. return items;
  72. }
  73. return current_index - 1;
  74. case 'last':
  75. return items;
  76. case 'first':
  77. return 0;
  78. default:
  79. throw new TypeError(`Invalid or not implemented reducer action - ${action}`);
  80. }
  81. }
  82. function maybeFocusRow(
  83. ref: HTMLDivElement | null,
  84. index: number,
  85. previouslyFocusedIndexRef: React.MutableRefObject<number | null>
  86. ) {
  87. if (!ref) return;
  88. if (index === previouslyFocusedIndexRef.current) return;
  89. ref.focus();
  90. previouslyFocusedIndexRef.current = index;
  91. }
  92. interface TraceProps {
  93. manager: VirtualizedViewManager;
  94. onTraceSearch: (query: string) => void;
  95. roving_dispatch: React.Dispatch<RovingTabIndexAction>;
  96. roving_state: RovingTabIndexState;
  97. searchResultsIteratorIndex: number | undefined;
  98. searchResultsMap: Map<TraceTreeNode<TraceTree.NodeValue>, number>;
  99. search_dispatch: React.Dispatch<TraceSearchAction>;
  100. search_state: TraceSearchState;
  101. setDetailNode: (node: TraceTreeNode<TraceTree.NodeValue> | null) => void;
  102. trace: TraceTree;
  103. trace_id: string;
  104. }
  105. function Trace({
  106. trace,
  107. trace_id,
  108. roving_state,
  109. roving_dispatch,
  110. search_state,
  111. search_dispatch,
  112. setDetailNode,
  113. manager,
  114. searchResultsIteratorIndex,
  115. searchResultsMap,
  116. onTraceSearch,
  117. }: TraceProps) {
  118. const theme = useTheme();
  119. const api = useApi();
  120. const {projects} = useProjects();
  121. const organization = useOrganization();
  122. const containerRef = useRef<HTMLDivElement | null>(null);
  123. const [_rerender, setRender] = useState(0);
  124. const previouslyFocusedIndexRef = useRef<number | null>(null);
  125. const treePromiseStatusRef =
  126. useRef<Map<TraceTreeNode<TraceTree.NodeValue>, 'loading' | 'error' | 'success'>>();
  127. if (!treePromiseStatusRef.current) {
  128. treePromiseStatusRef.current = new Map();
  129. }
  130. const scrollQueue = useRef<TraceTree.NodePath[] | null>(null);
  131. const treeRef = useRef<TraceTree>(trace);
  132. treeRef.current = trace;
  133. if (
  134. trace.root.space &&
  135. (trace.root.space[0] !== manager.to_origin ||
  136. trace.root.space[1] !== manager.trace_space.width)
  137. ) {
  138. manager.initializeTraceSpace([trace.root.space[0], 0, trace.root.space[1], 1]);
  139. scrollQueue.current = decodeScrollQueue(qs.parse(location.search).node);
  140. }
  141. const loadedRef = useRef(false);
  142. useEffect(() => {
  143. if (loadedRef.current) {
  144. return;
  145. }
  146. if (trace.type === 'loading' || !manager) {
  147. return;
  148. }
  149. loadedRef.current = true;
  150. if (!scrollQueue.current) {
  151. if (search_state.query) {
  152. onTraceSearch(search_state.query);
  153. }
  154. return;
  155. }
  156. manager
  157. .scrollToPath(trace, scrollQueue.current, () => setRender(a => (a + 1) % 2), {
  158. api,
  159. organization,
  160. })
  161. .then(maybeNode => {
  162. scrollQueue.current = null;
  163. if (!maybeNode) {
  164. return;
  165. }
  166. manager.onScrollEndOutOfBoundsCheck();
  167. setDetailNode(maybeNode.node);
  168. roving_dispatch({
  169. type: 'set index',
  170. index: maybeNode.index,
  171. node: maybeNode.node,
  172. });
  173. if (search_state.query) {
  174. onTraceSearch(search_state.query);
  175. }
  176. });
  177. }, [
  178. api,
  179. organization,
  180. trace,
  181. trace_id,
  182. manager,
  183. search_state.query,
  184. onTraceSearch,
  185. setDetailNode,
  186. roving_dispatch,
  187. ]);
  188. const handleZoomIn = useCallback(
  189. (
  190. event: React.MouseEvent,
  191. node: TraceTreeNode<TraceTree.NodeValue>,
  192. value: boolean
  193. ) => {
  194. if (!isTransactionNode(node) && !isSpanNode(node)) {
  195. throw new TypeError('Node must be a transaction or span');
  196. }
  197. event.stopPropagation();
  198. setRender(a => (a + 1) % 2);
  199. treeRef.current
  200. .zoomIn(node, value, {
  201. api,
  202. organization,
  203. })
  204. .then(() => {
  205. setRender(a => (a + 1) % 2);
  206. if (search_state.query) {
  207. onTraceSearch(search_state.query);
  208. }
  209. if (search_state.resultsLookup.has(node)) {
  210. const idx = search_state.resultsLookup.get(node)!;
  211. search_dispatch({
  212. type: 'set iterator index',
  213. resultIndex: search_state.results?.[idx]?.index!,
  214. resultIteratorIndex: idx,
  215. });
  216. } else {
  217. search_dispatch({type: 'clear iterator index'});
  218. }
  219. treePromiseStatusRef.current!.set(node, 'success');
  220. })
  221. .catch(_e => {
  222. treePromiseStatusRef.current!.set(node, 'error');
  223. });
  224. },
  225. [api, organization, search_state, search_dispatch, onTraceSearch]
  226. );
  227. const handleExpandNode = useCallback(
  228. (
  229. event: React.MouseEvent<Element>,
  230. node: TraceTreeNode<TraceTree.NodeValue>,
  231. value: boolean
  232. ) => {
  233. event.stopPropagation();
  234. treeRef.current.expand(node, value);
  235. setRender(a => (a + 1) % 2);
  236. if (search_state.query) {
  237. onTraceSearch(search_state.query);
  238. }
  239. if (search_state.resultsLookup.has(node)) {
  240. const idx = search_state.resultsLookup.get(node)!;
  241. search_dispatch({
  242. type: 'set iterator index',
  243. resultIndex: search_state.results?.[idx]?.index!,
  244. resultIteratorIndex: idx,
  245. });
  246. } else {
  247. search_dispatch({type: 'clear iterator index'});
  248. }
  249. },
  250. [search_state, search_dispatch, onTraceSearch]
  251. );
  252. const onRowClick = useCallback(
  253. (
  254. _event: React.MouseEvent,
  255. index: number,
  256. node: TraceTreeNode<TraceTree.NodeValue>
  257. ) => {
  258. browserHistory.push({
  259. pathname: location.pathname,
  260. query: {
  261. ...qs.parse(location.search),
  262. node: node.path,
  263. },
  264. });
  265. setDetailNode(node);
  266. roving_dispatch({type: 'set index', index, node});
  267. if (search_state.resultsLookup.has(node)) {
  268. const idx = search_state.resultsLookup.get(node)!;
  269. search_dispatch({
  270. type: 'set iterator index',
  271. resultIndex: index,
  272. resultIteratorIndex: idx,
  273. });
  274. } else {
  275. search_dispatch({type: 'clear iterator index'});
  276. }
  277. },
  278. [roving_dispatch, setDetailNode, search_state, search_dispatch]
  279. );
  280. const onOutsideClick = useCallback(() => {
  281. const {node: _node, ...queryParamsWithoutNode} = qs.parse(location.search);
  282. browserHistory.push({
  283. pathname: location.pathname,
  284. query: queryParamsWithoutNode,
  285. });
  286. // eslint-disable-next-line react-hooks/exhaustive-deps
  287. }, []);
  288. useOnClickOutside(containerRef.current, onOutsideClick);
  289. const onRowKeyDown = useCallback(
  290. (
  291. event: React.KeyboardEvent,
  292. index: number,
  293. node: TraceTreeNode<TraceTree.NodeValue>
  294. ) => {
  295. if (!manager.list) {
  296. return;
  297. }
  298. const action = getRovingIndexActionFromEvent(event);
  299. if (action) {
  300. event.preventDefault();
  301. const nextIndex = computeNextIndexFromAction(
  302. index,
  303. action,
  304. treeRef.current.list.length - 1
  305. );
  306. manager.list.scrollToRow(nextIndex);
  307. roving_dispatch({type: 'set index', index: nextIndex, node});
  308. if (search_state.resultsLookup.has(trace.list[nextIndex])) {
  309. const idx = search_state.resultsLookup.get(trace.list[nextIndex])!;
  310. search_dispatch({
  311. type: 'set iterator index',
  312. resultIndex: nextIndex,
  313. resultIteratorIndex: idx,
  314. });
  315. } else {
  316. search_dispatch({type: 'clear iterator index'});
  317. }
  318. }
  319. },
  320. [manager.list, roving_dispatch, search_state, search_dispatch, trace.list]
  321. );
  322. // @TODO this is the implementation of infinite scroll. Once the user
  323. // reaches the end of the list, we fetch more data. The data is not yet
  324. // being appended to the tree as we need to figure out UX for this.
  325. // onRowsRendered callback should be passed to the List component
  326. // const limitRef = useRef<number | null>(null);
  327. // if (limitRef.current === null) {
  328. // let decodedLimit = getTraceQueryParams(qs.parse(location.search)).limit;
  329. // if (typeof decodedLimit === 'string') {
  330. // decodedLimit = parseInt(decodedLimit, 2);
  331. // }
  332. // limitRef.current = decodedLimit;
  333. // }
  334. // const loadMoreRequestRef =
  335. // useRef<Promise<TraceSplitResults<TraceFullDetailed> | null> | null>(null);
  336. // const onRowsRendered = useCallback((rows: RenderedRows) => {
  337. // if (loadMoreRequestRef.current) {
  338. // // in flight request
  339. // return;
  340. // }
  341. // if (rows.stopIndex !== treeRef.current.list.length - 1) {
  342. // // not at the end
  343. // return;
  344. // }
  345. // if (
  346. // !loadMoreRequestRef.current &&
  347. // limitRef.current &&
  348. // rows.stopIndex === treeRef.current.list.length - 1
  349. // ) {
  350. // limitRef.current = limitRef.current + 500;
  351. // const promise = fetchTrace(api, {
  352. // traceId: trace_id,
  353. // orgSlug: organization.slug,
  354. // query: qs.stringify(getTraceQueryParams(location, {limit: limitRef.current})),
  355. // })
  356. // .then(data => {
  357. // return data;
  358. // })
  359. // .catch(e => {
  360. // return e;
  361. // });
  362. // loadMoreRequestRef.current = promise;
  363. // }
  364. // }, []);
  365. const projectLookup = useMemo(() => {
  366. return projects.reduce<Record<Project['slug'], Project>>((acc, project) => {
  367. acc[project.slug] = project;
  368. return acc;
  369. }, {});
  370. }, [projects]);
  371. const registerListRef = useCallback(
  372. (r: List | null) => {
  373. if (r) {
  374. manager.registerList(r);
  375. }
  376. },
  377. [manager]
  378. );
  379. return (
  380. <TraceStylingWrapper
  381. ref={r => {
  382. containerRef.current = r;
  383. manager.onContainerRef(r);
  384. }}
  385. className={trace.type === 'loading' ? 'Loading' : ''}
  386. >
  387. <TraceDivider className="TraceDivider" ref={r => manager?.registerDividerRef(r)} />
  388. {trace.type === 'loading' ? <TraceLoading /> : null}
  389. <div
  390. className="TraceIndicatorContainer"
  391. ref={r => manager.registerIndicatorContainerRef(r)}
  392. >
  393. {trace.indicators.length > 0
  394. ? trace.indicators.map((indicator, i) => {
  395. return (
  396. <div
  397. key={i}
  398. ref={r => manager.registerIndicatorRef(r, i, indicator)}
  399. className="TraceIndicator"
  400. >
  401. <div className="TraceIndicatorLabel">{indicator.label}</div>
  402. <div className="TraceIndicatorLine" />
  403. </div>
  404. );
  405. })
  406. : null}
  407. </div>
  408. <AutoSizer>
  409. {({width, height}) => (
  410. <List
  411. ref={registerListRef}
  412. rowHeight={24}
  413. height={height}
  414. width={width}
  415. scrollToAlignment="center"
  416. overscanRowCount={5}
  417. rowCount={treeRef.current.list.length ?? 0}
  418. rowRenderer={p => {
  419. const node = treeRef.current.list[p.index];
  420. return trace.type === 'loading' ? (
  421. <RenderPlaceholderRow
  422. style={p.style}
  423. node={node}
  424. index={p.index}
  425. theme={theme}
  426. projects={projectLookup}
  427. manager={manager}
  428. startIndex={
  429. (p.parent as unknown as {_rowStartIndex: number})._rowStartIndex ?? 0
  430. }
  431. />
  432. ) : (
  433. <RenderRow
  434. key={p.key}
  435. theme={theme}
  436. startIndex={
  437. (p.parent as unknown as {_rowStartIndex: number})._rowStartIndex ?? 0
  438. }
  439. overscroll={5}
  440. organization={organization}
  441. previouslyFocusedIndexRef={previouslyFocusedIndexRef}
  442. tabIndex={roving_state.index ?? -1}
  443. isSearchResult={searchResultsMap.has(node)}
  444. searchResultsIteratorIndex={searchResultsIteratorIndex}
  445. index={p.index}
  446. style={p.style}
  447. trace_id={trace_id}
  448. projects={projectLookup}
  449. node={node}
  450. manager={manager}
  451. onExpand={handleExpandNode}
  452. onZoomIn={handleZoomIn}
  453. onRowClick={onRowClick}
  454. onRowKeyDown={onRowKeyDown}
  455. />
  456. );
  457. }}
  458. />
  459. )}
  460. </AutoSizer>
  461. </TraceStylingWrapper>
  462. );
  463. }
  464. export default Trace;
  465. const TraceDivider = styled('div')`
  466. position: absolute;
  467. height: 100%;
  468. background-color: transparent;
  469. top: 0;
  470. cursor: col-resize;
  471. z-index: 10;
  472. &:before {
  473. content: '';
  474. position: absolute;
  475. width: 1px;
  476. height: 100%;
  477. background-color: ${p => p.theme.border};
  478. left: 50%;
  479. }
  480. &:hover&:before {
  481. background-color: ${p => p.theme.purple300};
  482. }
  483. `;
  484. function RenderRow(props: {
  485. index: number;
  486. isSearchResult: boolean;
  487. manager: VirtualizedViewManager;
  488. node: TraceTreeNode<TraceTree.NodeValue>;
  489. onExpand: (
  490. event: React.MouseEvent<Element>,
  491. node: TraceTreeNode<TraceTree.NodeValue>,
  492. value: boolean
  493. ) => void;
  494. onRowClick: (
  495. event: React.MouseEvent<Element>,
  496. index: number,
  497. node: TraceTreeNode<TraceTree.NodeValue>
  498. ) => void;
  499. onRowKeyDown: (
  500. event: React.KeyboardEvent,
  501. index: number,
  502. node: TraceTreeNode<TraceTree.NodeValue>
  503. ) => void;
  504. onZoomIn: (
  505. event: React.MouseEvent<Element>,
  506. node: TraceTreeNode<TraceTree.NodeValue>,
  507. value: boolean
  508. ) => void;
  509. organization: Organization;
  510. overscroll: number;
  511. previouslyFocusedIndexRef: React.MutableRefObject<number | null>;
  512. projects: Record<Project['slug'], Project>;
  513. searchResultsIteratorIndex: number | undefined;
  514. startIndex: number;
  515. style: React.CSSProperties;
  516. tabIndex: number;
  517. theme: Theme;
  518. trace_id: string;
  519. }) {
  520. const virtualizedIndex = props.index - props.startIndex + props.overscroll;
  521. if (!props.node.value) {
  522. return null;
  523. }
  524. const rowSearchClassName = `${props.isSearchResult ? 'SearchResult' : ''} ${props.searchResultsIteratorIndex === props.index ? 'Highlight' : ''}`;
  525. if (isAutogroupedNode(props.node)) {
  526. return (
  527. <div
  528. key={props.index}
  529. ref={r =>
  530. props.tabIndex === props.index
  531. ? maybeFocusRow(r, props.index, props.previouslyFocusedIndexRef)
  532. : null
  533. }
  534. tabIndex={props.tabIndex === props.index ? 0 : -1}
  535. className={`Autogrouped TraceRow ${rowSearchClassName}`}
  536. onClick={e => props.onRowClick(e, props.index, props.node)}
  537. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  538. style={{
  539. top: props.style.top,
  540. height: props.style.height,
  541. }}
  542. >
  543. <div
  544. className="TraceLeftColumn"
  545. ref={r =>
  546. props.manager.registerColumnRef('list', r, virtualizedIndex, props.node)
  547. }
  548. style={{
  549. width: props.manager.columns.list.width * 100 + '%',
  550. }}
  551. >
  552. <div
  553. className={`TraceLeftColumnInner ${props.node.has_error ? 'Errored' : ''}`}
  554. style={{
  555. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  556. }}
  557. >
  558. <div className="TraceChildrenCountWrapper">
  559. <Connectors node={props.node} manager={props.manager} />
  560. <ChildrenButton
  561. icon={<IconChevron direction={props.node.expanded ? 'up' : 'down'} />}
  562. status={props.node.fetchStatus}
  563. expanded={!props.node.expanded}
  564. onClick={e => props.onExpand(e, props.node, !props.node.expanded)}
  565. errored={props.node.has_error}
  566. >
  567. {COUNT_FORMATTER.format(props.node.groupCount)}
  568. </ChildrenButton>
  569. </div>
  570. <span className="TraceOperation">{t('Autogrouped')}</span>
  571. <strong className="TraceEmDash"> — </strong>
  572. <span className="TraceDescription">{props.node.value.autogrouped_by.op}</span>
  573. </div>
  574. </div>
  575. <div
  576. className={`TraceRightColumn ${props.index % 2 === 0 ? 0 : 'Odd'}`}
  577. ref={r =>
  578. props.manager.registerColumnRef('span_list', r, virtualizedIndex, props.node)
  579. }
  580. style={{
  581. width: props.manager.columns.span_list.width * 100 + '%',
  582. }}
  583. >
  584. <AutogroupedTraceBar
  585. virtualizedIndex={virtualizedIndex}
  586. viewManager={props.manager}
  587. color={props.theme.blue300}
  588. node_space={props.node.autogroupedSegments}
  589. />
  590. </div>
  591. </div>
  592. );
  593. }
  594. if (isTransactionNode(props.node)) {
  595. const errored =
  596. props.node.value.errors.length > 0 ||
  597. props.node.value.performance_issues.length > 0;
  598. return (
  599. <div
  600. key={props.index}
  601. ref={r =>
  602. props.tabIndex === props.index
  603. ? maybeFocusRow(r, props.index, props.previouslyFocusedIndexRef)
  604. : null
  605. }
  606. tabIndex={props.tabIndex === props.index ? 0 : -1}
  607. className={`TraceRow ${rowSearchClassName}`}
  608. onClick={e => props.onRowClick(e, props.index, props.node)}
  609. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  610. style={{
  611. top: props.style.top,
  612. height: props.style.height,
  613. }}
  614. >
  615. <div
  616. className="TraceLeftColumn"
  617. ref={r =>
  618. props.manager.registerColumnRef('list', r, virtualizedIndex, props.node)
  619. }
  620. style={{
  621. width: props.manager.columns.list.width * 100 + '%',
  622. }}
  623. >
  624. <div
  625. className={`TraceLeftColumnInner ${errored ? 'Errored' : ''}`}
  626. style={{
  627. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  628. }}
  629. >
  630. <div
  631. className={`TraceChildrenCountWrapper ${
  632. props.node.isOrphaned ? 'Orphaned' : ''
  633. }
  634. `}
  635. >
  636. <Connectors node={props.node} manager={props.manager} />
  637. {props.node.children.length > 0 || props.node.canFetch ? (
  638. <ChildrenButton
  639. icon={
  640. props.node.canFetch && props.node.fetchStatus === 'idle' ? (
  641. '+'
  642. ) : props.node.canFetch && props.node.zoomedIn ? (
  643. <IconChevron direction="down" />
  644. ) : (
  645. '+'
  646. )
  647. }
  648. status={props.node.fetchStatus}
  649. expanded={props.node.expanded || props.node.zoomedIn}
  650. onClick={e =>
  651. props.node.canFetch
  652. ? props.onZoomIn(e, props.node, !props.node.zoomedIn)
  653. : props.onExpand(e, props.node, !props.node.expanded)
  654. }
  655. errored={errored}
  656. >
  657. {props.node.children.length > 0
  658. ? COUNT_FORMATTER.format(props.node.children.length)
  659. : null}
  660. </ChildrenButton>
  661. ) : null}
  662. </div>
  663. <ProjectBadge project={props.projects[props.node.value.project_slug]} />
  664. <span className="TraceOperation">{props.node.value['transaction.op']}</span>
  665. <strong className="TraceEmDash"> — </strong>
  666. <span>{props.node.value.transaction}</span>
  667. </div>
  668. </div>
  669. <div
  670. ref={r =>
  671. props.manager.registerColumnRef('span_list', r, virtualizedIndex, props.node)
  672. }
  673. className={`TraceRightColumn ${props.index % 2 === 0 ? 0 : 'Odd'}`}
  674. style={{
  675. width: props.manager.columns.span_list.width * 100 + '%',
  676. }}
  677. >
  678. <TraceBar
  679. virtualizedIndex={virtualizedIndex}
  680. manager={props.manager}
  681. color={pickBarColor(props.node.value['transaction.op'])}
  682. node_space={props.node.space}
  683. />
  684. </div>
  685. </div>
  686. );
  687. }
  688. if (isSpanNode(props.node)) {
  689. const errored = props.node.value.relatedErrors.length > 0;
  690. return (
  691. <div
  692. key={props.index}
  693. ref={r =>
  694. props.tabIndex === props.index
  695. ? maybeFocusRow(r, props.index, props.previouslyFocusedIndexRef)
  696. : null
  697. }
  698. tabIndex={props.tabIndex === props.index ? 0 : -1}
  699. className={`TraceRow ${rowSearchClassName}`}
  700. onClick={e => props.onRowClick(e, props.index, props.node)}
  701. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  702. style={{
  703. top: props.style.top,
  704. height: props.style.height,
  705. }}
  706. >
  707. <div
  708. className="TraceLeftColumn"
  709. ref={r =>
  710. props.manager.registerColumnRef('list', r, virtualizedIndex, props.node)
  711. }
  712. style={{
  713. width: props.manager.columns.list.width * 100 + '%',
  714. }}
  715. >
  716. <div
  717. className={`TraceLeftColumnInner ${errored ? 'Errored' : ''}`}
  718. style={{
  719. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  720. }}
  721. >
  722. <div
  723. className={`TraceChildrenCountWrapper ${
  724. props.node.isOrphaned ? 'Orphaned' : ''
  725. }`}
  726. >
  727. <Connectors node={props.node} manager={props.manager} />
  728. {props.node.children.length > 0 || props.node.canFetch ? (
  729. <ChildrenButton
  730. icon={
  731. props.node.canFetch ? (
  732. '+'
  733. ) : props.node.expanded ? (
  734. <IconChevron direction="up" />
  735. ) : (
  736. <IconChevron direction="down" />
  737. )
  738. }
  739. status={props.node.fetchStatus}
  740. expanded={props.node.expanded || props.node.zoomedIn}
  741. onClick={e =>
  742. props.node.canFetch
  743. ? props.onZoomIn(e, props.node, !props.node.zoomedIn)
  744. : props.onExpand(e, props.node, !props.node.expanded)
  745. }
  746. errored={errored}
  747. >
  748. {props.node.children.length > 0
  749. ? COUNT_FORMATTER.format(props.node.children.length)
  750. : null}
  751. </ChildrenButton>
  752. ) : null}
  753. </div>
  754. <span className="TraceOperation">{props.node.value.op ?? '<unknown>'}</span>
  755. <strong className="TraceEmDash"> — </strong>
  756. <span className="TraceDescription" title={props.node.value.description}>
  757. {!props.node.value.description
  758. ? 'unknown'
  759. : props.node.value.description.length > 100
  760. ? props.node.value.description.slice(0, 100).trim() + '\u2026'
  761. : props.node.value.description}
  762. </span>
  763. </div>
  764. </div>
  765. <div
  766. ref={r =>
  767. props.manager.registerColumnRef('span_list', r, virtualizedIndex, props.node)
  768. }
  769. className={`TraceRightColumn ${props.index % 2 === 0 ? 0 : 'Odd'}`}
  770. style={{
  771. width: props.manager.columns.span_list.width * 100 + '%',
  772. }}
  773. >
  774. <TraceBar
  775. virtualizedIndex={virtualizedIndex}
  776. manager={props.manager}
  777. color={pickBarColor(props.node.value.op)}
  778. node_space={props.node.space}
  779. />
  780. </div>
  781. </div>
  782. );
  783. }
  784. if (isMissingInstrumentationNode(props.node)) {
  785. return (
  786. <div
  787. key={props.index}
  788. ref={r =>
  789. props.tabIndex === props.index
  790. ? maybeFocusRow(r, props.index, props.previouslyFocusedIndexRef)
  791. : null
  792. }
  793. tabIndex={props.tabIndex === props.index ? 0 : -1}
  794. className={`TraceRow ${rowSearchClassName}`}
  795. onClick={e => props.onRowClick(e, props.index, props.node)}
  796. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  797. style={{
  798. top: props.style.top,
  799. height: props.style.height,
  800. }}
  801. >
  802. <div
  803. className="TraceLeftColumn"
  804. ref={r =>
  805. props.manager.registerColumnRef('list', r, virtualizedIndex, props.node)
  806. }
  807. style={{
  808. width: props.manager.columns.list.width * 100 + '%',
  809. }}
  810. >
  811. <div
  812. className="TraceLeftColumnInner"
  813. style={{
  814. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  815. }}
  816. >
  817. <div className="TraceChildrenCountWrapper">
  818. <Connectors node={props.node} manager={props.manager} />
  819. </div>
  820. <span className="TraceOperation">{t('Missing instrumentation')}</span>
  821. </div>
  822. </div>
  823. <div
  824. ref={r =>
  825. props.manager.registerColumnRef('span_list', r, virtualizedIndex, props.node)
  826. }
  827. className={`TraceRightColumn ${props.index % 2 === 0 ? 0 : 'Odd'}`}
  828. style={{
  829. width: props.manager.columns.span_list.width * 100 + '%',
  830. }}
  831. >
  832. {' '}
  833. <TraceBar
  834. virtualizedIndex={virtualizedIndex}
  835. manager={props.manager}
  836. color={pickBarColor('missing-instrumentation')}
  837. node_space={props.node.space}
  838. />
  839. </div>
  840. </div>
  841. );
  842. }
  843. if (isTraceNode(props.node)) {
  844. return (
  845. <div
  846. key={props.index}
  847. ref={r =>
  848. props.tabIndex === props.index
  849. ? maybeFocusRow(r, props.index, props.previouslyFocusedIndexRef)
  850. : null
  851. }
  852. tabIndex={props.tabIndex === props.index ? 0 : -1}
  853. className={`TraceRow ${rowSearchClassName}`}
  854. onClick={e => props.onRowClick(e, props.index, props.node)}
  855. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  856. style={{
  857. top: props.style.top,
  858. height: props.style.height,
  859. }}
  860. >
  861. <div
  862. className="TraceLeftColumn"
  863. ref={r =>
  864. props.manager.registerColumnRef('list', r, virtualizedIndex, props.node)
  865. }
  866. style={{
  867. width: props.manager.columns.list.width * 100 + '%',
  868. }}
  869. >
  870. <div
  871. className="TraceLeftColumnInner"
  872. style={{
  873. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  874. }}
  875. >
  876. <div className="TraceChildrenCountWrapper Root">
  877. <Connectors node={props.node} manager={props.manager} />
  878. {props.node.children.length > 0 || props.node.canFetch ? (
  879. <ChildrenButton icon={''} status={'idle'} expanded onClick={() => void 0}>
  880. {props.node.children.length > 0
  881. ? COUNT_FORMATTER.format(props.node.children.length)
  882. : null}
  883. </ChildrenButton>
  884. ) : null}
  885. </div>
  886. <span className="TraceOperation">{t('Trace')}</span>
  887. <strong className="TraceEmDash"> — </strong>
  888. <span className="TraceDescription">{props.trace_id}</span>
  889. </div>
  890. </div>
  891. <div
  892. ref={r =>
  893. props.manager.registerColumnRef('span_list', r, virtualizedIndex, props.node)
  894. }
  895. className={`TraceRightColumn ${props.index % 2 === 0 ? 0 : 'Odd'}`}
  896. style={{
  897. width: props.manager.columns.span_list.width * 100 + '%',
  898. }}
  899. >
  900. <TraceBar
  901. virtualizedIndex={virtualizedIndex}
  902. manager={props.manager}
  903. color={pickBarColor('missing-instrumentation')}
  904. node_space={props.node.space}
  905. />
  906. </div>
  907. </div>
  908. );
  909. }
  910. if (isTraceErrorNode(props.node)) {
  911. return (
  912. <div
  913. key={props.index}
  914. ref={r =>
  915. props.tabIndex === props.index
  916. ? maybeFocusRow(r, props.index, props.previouslyFocusedIndexRef)
  917. : null
  918. }
  919. tabIndex={props.tabIndex === props.index ? 0 : -1}
  920. className={`TraceRow ${rowSearchClassName}`}
  921. onClick={e => props.onRowClick(e, props.index, props.node)}
  922. onKeyDown={event => props.onRowKeyDown(event, props.index, props.node)}
  923. style={{
  924. top: props.style.top,
  925. height: props.style.height,
  926. }}
  927. >
  928. <div
  929. className="TraceLeftColumn"
  930. ref={r =>
  931. props.manager.registerColumnRef('list', r, virtualizedIndex, props.node)
  932. }
  933. style={{
  934. width: props.manager.columns.list.width * 100 + '%',
  935. }}
  936. >
  937. <div
  938. className="TraceLeftColumnInner"
  939. style={{
  940. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  941. }}
  942. >
  943. <div className="TraceChildrenCountWrapper">
  944. <Connectors node={props.node} manager={props.manager} />
  945. {props.node.children.length > 0 || props.node.canFetch ? (
  946. <ChildrenButton
  947. icon={''}
  948. status={props.node.fetchStatus}
  949. expanded={props.node.expanded || props.node.zoomedIn}
  950. onClick={e => props.onExpand(e, props.node, !props.node.expanded)}
  951. >
  952. {props.node.children.length > 0
  953. ? COUNT_FORMATTER.format(props.node.children.length)
  954. : null}
  955. </ChildrenButton>
  956. ) : null}
  957. </div>
  958. <ProjectBadge project={props.projects[props.node.value.project_slug]} />
  959. <Link
  960. className="Errored Link"
  961. onClick={e => e.stopPropagation()}
  962. to={generateIssueEventTarget(props.node.value, props.organization)}
  963. >
  964. <span className="TraceOperation">{t('Error')}</span>
  965. <strong className="TraceEmDash"> — </strong>
  966. <span className="TraceDescription">{props.node.value.title}</span>
  967. </Link>
  968. </div>
  969. </div>
  970. <div
  971. ref={r =>
  972. props.manager.registerColumnRef('span_list', r, virtualizedIndex, props.node)
  973. }
  974. className={`TraceRightColumn ${props.index % 2 === 0 ? 0 : 'Odd'}`}
  975. style={{
  976. width: props.manager.columns.span_list.width * 100 + '%',
  977. }}
  978. >
  979. {typeof props.node.value.timestamp === 'number' ? (
  980. <div
  981. className="ErrorIconBorder"
  982. style={{
  983. transform: `translateX(${props.manager.computeTransformXFromTimestamp(
  984. props.node.value.timestamp * 1000
  985. )}px)`,
  986. }}
  987. >
  988. <IconFire color="errorText" size="xs" />
  989. </div>
  990. ) : null}
  991. </div>
  992. </div>
  993. );
  994. }
  995. return null;
  996. }
  997. function RenderPlaceholderRow(props: {
  998. index: number;
  999. manager: VirtualizedViewManager;
  1000. node: TraceTreeNode<TraceTree.NodeValue>;
  1001. projects: Record<Project['slug'], Project>;
  1002. startIndex: number;
  1003. style: React.CSSProperties;
  1004. theme: Theme;
  1005. }) {
  1006. return (
  1007. <div
  1008. className="TraceRow"
  1009. style={{
  1010. top: props.style.top,
  1011. height: props.style.height,
  1012. pointerEvents: 'none',
  1013. color: props.theme.subText,
  1014. paddingLeft: space(1),
  1015. }}
  1016. >
  1017. <div
  1018. className="TraceLeftColumn"
  1019. style={{width: props.manager.columns.list.width * 100 + '%'}}
  1020. >
  1021. <div
  1022. className="TraceLeftColumnInner"
  1023. style={{
  1024. paddingLeft: props.node.depth * props.manager.row_depth_padding,
  1025. }}
  1026. >
  1027. <div
  1028. className={`TraceChildrenCountWrapper ${isTraceNode(props.node) ? 'Root' : ''}`}
  1029. >
  1030. <Connectors node={props.node} manager={props.manager} />
  1031. {props.node.children.length > 0 || props.node.canFetch ? (
  1032. <ChildrenButton
  1033. icon="+"
  1034. status={props.node.fetchStatus}
  1035. expanded={props.node.expanded || props.node.zoomedIn}
  1036. onClick={() => void 0}
  1037. >
  1038. {props.node.children.length > 0
  1039. ? COUNT_FORMATTER.format(props.node.children.length)
  1040. : null}
  1041. </ChildrenButton>
  1042. ) : null}
  1043. </div>
  1044. <Placeholder
  1045. className="Placeholder"
  1046. height="12px"
  1047. width={randomBetween(20, 80) + '%'}
  1048. style={{
  1049. transition: 'all 30s ease-out',
  1050. }}
  1051. />
  1052. </div>
  1053. </div>
  1054. <div
  1055. className={`TraceRightColumn ${props.index % 2 === 0 ? 0 : 'Odd'}`}
  1056. style={{
  1057. width: props.manager.columns.span_list.width * 100 + '%',
  1058. backgroundColor:
  1059. props.index % 2 === 0 ? props.theme.backgroundSecondary : undefined,
  1060. }}
  1061. >
  1062. <Placeholder
  1063. className="Placeholder"
  1064. height="12px"
  1065. width={randomBetween(20, 80) + '%'}
  1066. style={{
  1067. transition: 'all 30s ease-out',
  1068. transform: `translate(${randomBetween(0, 200) + 'px'}, 0)`,
  1069. }}
  1070. />
  1071. </div>
  1072. </div>
  1073. );
  1074. }
  1075. function randomBetween(min: number, max: number) {
  1076. return Math.floor(Math.random() * (max - min + 1) + min);
  1077. }
  1078. function Connectors(props: {
  1079. manager: VirtualizedViewManager;
  1080. node: TraceTreeNode<TraceTree.NodeValue>;
  1081. }) {
  1082. const showVerticalConnector =
  1083. ((props.node.expanded || props.node.zoomedIn) && props.node.children.length > 0) ||
  1084. (props.node.value && isParentAutogroupedNode(props.node));
  1085. // If the tail node of the collapsed node has no children,
  1086. // we don't want to render the vertical connector as no children
  1087. // are being rendered as the chain is entirely collapsed
  1088. const hideVerticalConnector =
  1089. showVerticalConnector &&
  1090. props.node.value &&
  1091. props.node instanceof ParentAutogroupNode &&
  1092. !props.node.tail.children.length;
  1093. return (
  1094. <Fragment>
  1095. {props.node.connectors.map((c, i) => {
  1096. return (
  1097. <div
  1098. key={i}
  1099. style={{
  1100. left: -(
  1101. Math.abs(Math.abs(c) - props.node.depth) * props.manager.row_depth_padding
  1102. ),
  1103. }}
  1104. className={`TraceVerticalConnector ${c < 0 ? 'Orphaned' : ''}`}
  1105. />
  1106. );
  1107. })}
  1108. {showVerticalConnector && !hideVerticalConnector ? (
  1109. <div className="TraceExpandedVerticalConnector" />
  1110. ) : null}
  1111. {props.node.isLastChild ? (
  1112. <div className="TraceVerticalLastChildConnector" />
  1113. ) : (
  1114. <div className="TraceVerticalConnector" />
  1115. )}
  1116. </Fragment>
  1117. );
  1118. }
  1119. function ProjectBadge(props: {project: Project}) {
  1120. return <ProjectAvatar project={props.project} />;
  1121. }
  1122. function ChildrenButton(props: {
  1123. children: React.ReactNode;
  1124. expanded: boolean;
  1125. icon: React.ReactNode;
  1126. onClick: (e: React.MouseEvent) => void;
  1127. status: TraceTreeNode<any>['fetchStatus'] | undefined;
  1128. errored?: boolean;
  1129. }) {
  1130. return (
  1131. <button
  1132. className={`TraceChildrenCount ${props.errored ? 'Errored' : ''}`}
  1133. onClick={props.onClick}
  1134. >
  1135. <div className="TraceChildrenCountContent">{props.children}</div>
  1136. <div className="TraceChildrenCountAction">
  1137. {props.icon}
  1138. {props.status === 'loading' ? (
  1139. <LoadingIndicator className="TraceActionsLoadingIndicator" size={8} />
  1140. ) : null}
  1141. </div>
  1142. </button>
  1143. );
  1144. }
  1145. interface TraceBarProps {
  1146. color: string;
  1147. manager: VirtualizedViewManager;
  1148. node_space: [number, number] | null;
  1149. virtualizedIndex: number;
  1150. duration?: number;
  1151. }
  1152. function TraceBar(props: TraceBarProps) {
  1153. if (!props.node_space) {
  1154. return null;
  1155. }
  1156. const duration = getDuration(props.node_space[1] / 1000, 2, true);
  1157. const spanTransform = props.manager.computeSpanCSSMatrixTransform(props.node_space);
  1158. const [inside, textTransform] = props.manager.computeSpanTextPlacement(
  1159. props.node_space,
  1160. duration
  1161. );
  1162. return (
  1163. <Fragment>
  1164. <div
  1165. ref={r =>
  1166. props.manager.registerSpanBarRef(r, props.node_space!, props.virtualizedIndex)
  1167. }
  1168. className="TraceBar"
  1169. style={{
  1170. transform: `matrix(${spanTransform.join(',')})`,
  1171. backgroundColor: props.color,
  1172. }}
  1173. onDoubleClick={e => {
  1174. e.stopPropagation();
  1175. props.manager.onZoomIntoSpace(props.node_space!);
  1176. }}
  1177. />
  1178. <div
  1179. ref={r =>
  1180. props.manager.registerSpanBarTextRef(
  1181. r,
  1182. duration,
  1183. props.node_space!,
  1184. props.virtualizedIndex
  1185. )
  1186. }
  1187. className="TraceBarDuration"
  1188. style={{
  1189. color: inside ? 'white' : '',
  1190. transform: `translate(${textTransform ?? 0}px, 0)`,
  1191. }}
  1192. >
  1193. {duration}
  1194. </div>
  1195. </Fragment>
  1196. );
  1197. }
  1198. interface AutogroupedTraceBarProps {
  1199. color: string;
  1200. node_space: [number, number][] | null;
  1201. viewManager: VirtualizedViewManager;
  1202. virtualizedIndex: number;
  1203. duration?: number;
  1204. }
  1205. function AutogroupedTraceBar(props: AutogroupedTraceBarProps) {
  1206. // if (props.node_space && props.node_space.length <= 1) {
  1207. // return (
  1208. // <TraceBar
  1209. // color={props.color}
  1210. // node_space={props.node_space[0]}
  1211. // manager={props.manager}
  1212. // virtualizedIndex={props.virtualizedIndex}
  1213. // duration={props.duration}
  1214. // />
  1215. // );
  1216. // }
  1217. if (!props.node_space) {
  1218. return null;
  1219. }
  1220. const entire_space: [number, number] = [
  1221. props.node_space![0][0],
  1222. props.node_space![props.node_space.length - 1][1],
  1223. ];
  1224. const duration = getDuration(entire_space[1] / 1000, 2, true);
  1225. const spanTransform = props.viewManager.computeSpanCSSMatrixTransform(entire_space);
  1226. const [inside, textTransform] = props.viewManager.computeSpanTextPlacement(
  1227. entire_space,
  1228. duration
  1229. );
  1230. return (
  1231. <Fragment>
  1232. <div
  1233. ref={r =>
  1234. props.viewManager.registerSpanBarRef(r, entire_space, props.virtualizedIndex)
  1235. }
  1236. className="TraceBar Invisible"
  1237. style={{
  1238. transform: `matrix(${spanTransform.join(',')})`,
  1239. backgroundColor: props.color,
  1240. }}
  1241. onDoubleClick={e => {
  1242. e.stopPropagation();
  1243. props.viewManager.onZoomIntoSpace(entire_space);
  1244. }}
  1245. >
  1246. {props.node_space.map((node_space, i) => {
  1247. const width = node_space[1] / entire_space[1];
  1248. const left = (node_space[0] - entire_space[0]) / entire_space[1];
  1249. return (
  1250. <div
  1251. key={i}
  1252. className="TraceBar"
  1253. style={{
  1254. left: `${left * 1000}%`,
  1255. width: `${width * 100}%`,
  1256. backgroundColor: props.color,
  1257. }}
  1258. />
  1259. );
  1260. })}
  1261. </div>
  1262. <div
  1263. ref={r =>
  1264. props.viewManager.registerSpanBarTextRef(
  1265. r,
  1266. duration,
  1267. entire_space,
  1268. props.virtualizedIndex
  1269. )
  1270. }
  1271. className="TraceBarDuration"
  1272. style={{
  1273. color: inside ? 'white' : '',
  1274. transform: `translate(${textTransform ?? 0}px, 0)`,
  1275. }}
  1276. >
  1277. {duration}
  1278. </div>
  1279. </Fragment>
  1280. );
  1281. }
  1282. /**
  1283. * This is a wrapper around the Trace component to apply styles
  1284. * to the trace tree. It exists because we _do not_ want to trigger
  1285. * emotion's css parsing logic as it is very slow and will cause
  1286. * the scrolling to flicker.
  1287. */
  1288. const TraceStylingWrapper = styled('div')`
  1289. height: 70vh;
  1290. width: 100%;
  1291. margin: auto;
  1292. overflow: hidden;
  1293. position: relative;
  1294. box-shadow: 0 0 0 1px ${p => p.theme.border};
  1295. border-radius: ${space(0.5)};
  1296. padding-top: 24px;
  1297. &:before {
  1298. content: '';
  1299. position: absolute;
  1300. left: 0;
  1301. top: 0;
  1302. width: 100%;
  1303. height: 22px;
  1304. background-color: ${p => p.theme.backgroundSecondary};
  1305. border-bottom: 1px solid ${p => p.theme.border};
  1306. }
  1307. &.Loading {
  1308. .TraceRow {
  1309. .TraceLeftColumnInner {
  1310. width: 100%;
  1311. }
  1312. }
  1313. .TraceRightColumn {
  1314. background-color: transparent !important;
  1315. }
  1316. .TraceDivider {
  1317. pointer-events: none;
  1318. }
  1319. }
  1320. .TraceIndicatorContainer {
  1321. overflow: hidden;
  1322. width: 100%;
  1323. height: 100%;
  1324. position: absolute;
  1325. right: 0;
  1326. top: 0;
  1327. }
  1328. .TraceIndicator {
  1329. z-index: 1;
  1330. width: 3px;
  1331. height: 100%;
  1332. top: 0;
  1333. position: absolute;
  1334. .TraceIndicatorLabel {
  1335. min-width: 34px;
  1336. text-align: center;
  1337. position: absolute;
  1338. font-size: ${p => p.theme.fontSizeExtraSmall};
  1339. font-weight: bold;
  1340. color: ${p => p.theme.textColor};
  1341. background-color: ${p => p.theme.background};
  1342. border-radius: ${p => p.theme.borderRadius};
  1343. border: 1px solid ${p => p.theme.border};
  1344. padding: ${space(0.25)};
  1345. display: inline-block;
  1346. line-height: 1;
  1347. margin-top: 2px;
  1348. white-space: nowrap;
  1349. }
  1350. .TraceIndicatorLine {
  1351. width: 1px;
  1352. height: 100%;
  1353. top: 20px;
  1354. position: absolute;
  1355. left: 50%;
  1356. transform: translateX(-2px);
  1357. background: repeating-linear-gradient(
  1358. to bottom,
  1359. transparent 0 4px,
  1360. ${p => p.theme.textColor} 4px 8px
  1361. )
  1362. 80%/2px 100% no-repeat;
  1363. }
  1364. }
  1365. .TraceRow {
  1366. display: flex;
  1367. align-items: center;
  1368. position: absolute;
  1369. width: 100%;
  1370. transition: none;
  1371. font-size: ${p => p.theme.fontSizeSmall};
  1372. .Errored {
  1373. color: ${p => p.theme.error};
  1374. }
  1375. .Link {
  1376. &:hover {
  1377. color: ${p => p.theme.blue300};
  1378. }
  1379. }
  1380. .ErrorIconBorder {
  1381. position: absolute;
  1382. margin: ${space(0.25)};
  1383. left: -12px;
  1384. background: ${p => p.theme.background};
  1385. width: ${space(3)};
  1386. height: ${space(3)};
  1387. border: 1px solid ${p => p.theme.error};
  1388. border-radius: 50%;
  1389. display: flex;
  1390. align-items: center;
  1391. justify-content: center;
  1392. }
  1393. .TraceRightColumn.Odd {
  1394. background-color: ${p => p.theme.backgroundSecondary};
  1395. }
  1396. &:hover {
  1397. background-color: ${p => p.theme.backgroundSecondary};
  1398. }
  1399. &.Highlight,
  1400. &:focus {
  1401. outline: none;
  1402. background-color: ${p => p.theme.backgroundTertiary};
  1403. .TraceRightColumn.Odd {
  1404. background-color: transparent !important;
  1405. }
  1406. }
  1407. &:focus {
  1408. box-shadow: inset 0 0 0 1px ${p => p.theme.blue300} !important;
  1409. .TraceLeftColumn {
  1410. box-shadow: inset 0px 0 0px 1px ${p => p.theme.blue300} !important;
  1411. }
  1412. }
  1413. &.Highlight {
  1414. box-shadow: inset 0 0 0 1px ${p => p.theme.blue200} !important;
  1415. .TraceLeftColumn {
  1416. box-shadow: inset 0px 0 0px 1px ${p => p.theme.blue200} !important;
  1417. }
  1418. }
  1419. &.SearchResult {
  1420. background-color: ${p => p.theme.yellow100};
  1421. .TraceRightColumn {
  1422. background-color: transparent;
  1423. }
  1424. }
  1425. &.Autogrouped {
  1426. color: ${p => p.theme.blue300};
  1427. .TraceDescription {
  1428. font-weight: bold;
  1429. }
  1430. .TraceChildrenCountWrapper {
  1431. button {
  1432. color: ${p => p.theme.white};
  1433. background-color: ${p => p.theme.blue300};
  1434. }
  1435. }
  1436. }
  1437. }
  1438. .TraceLeftColumn {
  1439. height: 100%;
  1440. white-space: nowrap;
  1441. display: flex;
  1442. align-items: center;
  1443. overflow: hidden;
  1444. will-change: width;
  1445. box-shadow: inset 1px 0 0px 0px transparent;
  1446. .TraceLeftColumnInner {
  1447. height: 100%;
  1448. white-space: nowrap;
  1449. display: flex;
  1450. align-items: center;
  1451. will-change: transform;
  1452. transform-origin: left center;
  1453. transform: translateX(var(--column-translate-x));
  1454. }
  1455. }
  1456. .TraceRightColumn {
  1457. height: 100%;
  1458. overflow: hidden;
  1459. position: relative;
  1460. display: flex;
  1461. align-items: center;
  1462. will-change: width;
  1463. z-index: 1;
  1464. }
  1465. .TraceBar {
  1466. position: absolute;
  1467. height: 64%;
  1468. width: 100%;
  1469. background-color: black;
  1470. transform-origin: left center;
  1471. &.Invisible {
  1472. background-color: transparent !important;
  1473. > div {
  1474. height: 100%;
  1475. }
  1476. }
  1477. }
  1478. .TraceBarDuration {
  1479. display: inline-block;
  1480. transform-origin: left center;
  1481. font-size: ${p => p.theme.fontSizeExtraSmall};
  1482. color: ${p => p.theme.gray300};
  1483. white-space: nowrap;
  1484. font-variant-numeric: tabular-nums;
  1485. position: absolute;
  1486. transition: color 0.1s ease-in-out;
  1487. }
  1488. .TraceChildrenCount {
  1489. height: 16px;
  1490. white-space: nowrap;
  1491. min-width: 30px;
  1492. display: flex;
  1493. align-items: center;
  1494. justify-content: center;
  1495. border-radius: 99px;
  1496. padding: 0px ${space(0.5)};
  1497. transition: all 0.15s ease-in-out;
  1498. background: ${p => p.theme.background};
  1499. border: 2px solid ${p => p.theme.border};
  1500. line-height: 0;
  1501. z-index: 1;
  1502. font-size: 10px;
  1503. box-shadow: ${p => p.theme.dropShadowLight};
  1504. margin-right: ${space(1)};
  1505. &.Errored {
  1506. border: 2px solid ${p => p.theme.error};
  1507. }
  1508. .TraceChildrenCountContent {
  1509. + .TraceChildrenCountAction {
  1510. margin-left: 2px;
  1511. }
  1512. }
  1513. .TraceChildrenCountAction {
  1514. position: relative;
  1515. display: flex;
  1516. align-items: center;
  1517. justify-content: center;
  1518. }
  1519. .TraceActionsLoadingIndicator {
  1520. margin: 0;
  1521. position: absolute;
  1522. top: 50%;
  1523. left: 50%;
  1524. transform: translate(-50%, -50%);
  1525. background-color: ${p => p.theme.background};
  1526. animation: show 0.1s ease-in-out forwards;
  1527. @keyframes show {
  1528. from {
  1529. opacity: 0;
  1530. transform: translate(-50%, -50%) scale(0.86);
  1531. }
  1532. to {
  1533. opacity: 1;
  1534. transform: translate(-50%, -50%) scale(1);
  1535. }
  1536. }
  1537. .loading-indicator {
  1538. border-width: 2px;
  1539. }
  1540. .loading-message {
  1541. display: none;
  1542. }
  1543. }
  1544. svg {
  1545. width: 7px;
  1546. transition: none;
  1547. }
  1548. }
  1549. .TraceChildrenCountWrapper {
  1550. display: flex;
  1551. justify-content: flex-end;
  1552. align-items: center;
  1553. min-width: 44px;
  1554. height: 100%;
  1555. position: relative;
  1556. button {
  1557. transition: none;
  1558. }
  1559. &.Orphaned {
  1560. .TraceVerticalConnector,
  1561. .TraceVerticalLastChildConnector,
  1562. .TraceExpandedVerticalConnector {
  1563. border-left: 2px dashed ${p => p.theme.border};
  1564. }
  1565. &::before {
  1566. border-bottom: 2px dashed ${p => p.theme.border};
  1567. }
  1568. }
  1569. &.Root {
  1570. &:before,
  1571. .TraceVerticalLastChildConnector {
  1572. visibility: hidden;
  1573. }
  1574. }
  1575. &::before {
  1576. content: '';
  1577. display: block;
  1578. width: 50%;
  1579. height: 2px;
  1580. border-bottom: 2px solid ${p => p.theme.border};
  1581. position: absolute;
  1582. left: 0;
  1583. top: 50%;
  1584. transform: translateY(-50%);
  1585. }
  1586. &::after {
  1587. content: '';
  1588. background-color: ${p => p.theme.border};
  1589. border-radius: 50%;
  1590. height: 6px;
  1591. width: 6px;
  1592. position: absolute;
  1593. left: 50%;
  1594. top: 50%;
  1595. transform: translateY(-50%);
  1596. }
  1597. }
  1598. .TraceVerticalConnector {
  1599. position: absolute;
  1600. left: 0;
  1601. top: 0;
  1602. bottom: 0;
  1603. height: 100%;
  1604. width: 2px;
  1605. border-left: 2px solid ${p => p.theme.border};
  1606. &.Orphaned {
  1607. border-left: 2px dashed ${p => p.theme.border};
  1608. }
  1609. }
  1610. .TraceVerticalLastChildConnector {
  1611. position: absolute;
  1612. left: 0;
  1613. top: 0;
  1614. bottom: 0;
  1615. height: 50%;
  1616. width: 2px;
  1617. border-left: 2px solid ${p => p.theme.border};
  1618. border-bottom-left-radius: 4px;
  1619. }
  1620. .TraceExpandedVerticalConnector {
  1621. position: absolute;
  1622. bottom: 0;
  1623. height: 50%;
  1624. left: 50%;
  1625. width: 2px;
  1626. border-left: 2px solid ${p => p.theme.border};
  1627. }
  1628. .TraceOperation {
  1629. margin-left: ${space(0.5)};
  1630. text-overflow: ellipsis;
  1631. white-space: nowrap;
  1632. font-weight: bold;
  1633. }
  1634. .TraceEmDash {
  1635. margin-left: ${space(0.5)};
  1636. margin-right: ${space(0.5)};
  1637. }
  1638. .TraceDescription {
  1639. white-space: nowrap;
  1640. }
  1641. `;
  1642. const LoadingContainer = styled('div')`
  1643. display: flex;
  1644. justify-content: center;
  1645. align-items: center;
  1646. flex-direction: column;
  1647. left: 50%;
  1648. top: 50%;
  1649. transform: translate(-50%, -50%);
  1650. position: absolute;
  1651. height: auto;
  1652. font-size: ${p => p.theme.fontSizeMedium};
  1653. color: ${p => p.theme.gray300};
  1654. z-index: 30;
  1655. padding: 24px;
  1656. background-color: ${p => p.theme.background};
  1657. border-radius: ${p => p.theme.borderRadius};
  1658. border: 1px solid ${p => p.theme.border};
  1659. `;
  1660. function TraceLoading() {
  1661. return (
  1662. <LoadingContainer>
  1663. <NoMarginIndicator size={24}>
  1664. <div>{t('Assembling the trace')}</div>
  1665. </NoMarginIndicator>
  1666. </LoadingContainer>
  1667. );
  1668. }
  1669. const NoMarginIndicator = styled(LoadingIndicator)`
  1670. margin: 0;
  1671. `;