traceTree.spec.tsx 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {EntryType} from 'sentry/types/event';
  3. import type {SiblingAutogroupNode} from 'sentry/views/performance/newTraceDetails/traceModels/siblingAutogroupNode';
  4. import {DEFAULT_TRACE_VIEW_PREFERENCES} from 'sentry/views/performance/newTraceDetails/traceState/tracePreferences';
  5. import type {ReplayRecord} from 'sentry/views/replays/types';
  6. import {
  7. isMissingInstrumentationNode,
  8. isParentAutogroupedNode,
  9. isSiblingAutogroupedNode,
  10. isSpanNode,
  11. isTransactionNode,
  12. } from './../traceGuards';
  13. import type {ParentAutogroupNode} from './parentAutogroupNode';
  14. import {TraceTree} from './traceTree';
  15. import {
  16. assertTransactionNode,
  17. makeEventTransaction,
  18. makeSpan,
  19. makeTrace,
  20. makeTraceError,
  21. makeTracePerformanceIssue,
  22. makeTransaction,
  23. } from './traceTreeTestUtils';
  24. function mockSpansResponse(
  25. spans: TraceTree.Span[],
  26. project_slug: string,
  27. event_id: string
  28. ): jest.Mock<any, any> {
  29. return MockApiClient.addMockResponse({
  30. url: `/organizations/org-slug/events/${project_slug}:${event_id}/?averageColumn=span.self_time&averageColumn=span.duration`,
  31. method: 'GET',
  32. body: makeEventTransaction({
  33. entries: [{type: EntryType.SPANS, data: spans}],
  34. }),
  35. });
  36. }
  37. const start = new Date('2024-02-29T00:00:00Z').getTime() / 1e3;
  38. const end = new Date('2024-02-29T00:00:00Z').getTime() / 1e3 + 5;
  39. const traceMetadata = {replay: null, meta: null};
  40. const trace = makeTrace({
  41. transactions: [
  42. makeTransaction({
  43. start_timestamp: start,
  44. timestamp: start + 2,
  45. children: [makeTransaction({start_timestamp: start + 1, timestamp: start + 4})],
  46. }),
  47. ],
  48. orphan_errors: [],
  49. });
  50. const traceWithEventId = makeTrace({
  51. transactions: [
  52. makeTransaction({
  53. event_id: 'event-id',
  54. start_timestamp: start,
  55. timestamp: start + 2,
  56. project_slug: 'project',
  57. children: [
  58. makeTransaction({
  59. start_timestamp: start + 1,
  60. timestamp: start + 4,
  61. event_id: 'child-event-id',
  62. project_slug: 'project',
  63. }),
  64. ],
  65. }),
  66. ],
  67. });
  68. const traceWithVitals = makeTrace({
  69. transactions: [
  70. makeTransaction({
  71. start_timestamp: start,
  72. timestamp: start + 2,
  73. measurements: {ttfb: {value: 0, unit: 'millisecond'}},
  74. }),
  75. ],
  76. });
  77. const traceWithOrphanError = makeTrace({
  78. transactions: [
  79. makeTransaction({
  80. start_timestamp: start,
  81. timestamp: start + 2,
  82. children: [makeTransaction({start_timestamp: start + 1, timestamp: start + 2})],
  83. }),
  84. ],
  85. orphan_errors: [makeTraceError({level: 'error', timestamp: end})],
  86. });
  87. const outOfOrderTrace = makeTrace({
  88. transactions: [
  89. makeTransaction({
  90. start_timestamp: 1,
  91. transaction: 'last',
  92. children: [],
  93. }),
  94. makeTransaction({start_timestamp: 0, transaction: 'first'}),
  95. ],
  96. });
  97. const siblingAutogroupSpans = [
  98. makeSpan({
  99. op: 'db',
  100. description: 'redis',
  101. start_timestamp: start,
  102. timestamp: start + 1,
  103. }),
  104. makeSpan({
  105. op: 'db',
  106. description: 'redis',
  107. start_timestamp: start,
  108. timestamp: start + 1,
  109. }),
  110. makeSpan({
  111. op: 'db',
  112. description: 'redis',
  113. start_timestamp: start,
  114. timestamp: start + 1,
  115. }),
  116. makeSpan({
  117. op: 'db',
  118. description: 'redis',
  119. start_timestamp: start,
  120. timestamp: start + 1,
  121. }),
  122. makeSpan({
  123. op: 'db',
  124. description: 'redis',
  125. start_timestamp: start,
  126. timestamp: start + 1,
  127. }),
  128. ];
  129. const parentAutogroupSpans = [
  130. makeSpan({op: 'db', description: 'redis', span_id: '0000'}),
  131. makeSpan({op: 'db', description: 'redis', span_id: '0001', parent_span_id: '0000'}),
  132. ];
  133. const parentAutogroupSpansWithTailChildren = [
  134. makeSpan({op: 'db', description: 'redis', span_id: '0000'}),
  135. makeSpan({
  136. op: 'db',
  137. description: 'redis',
  138. span_id: '0001',
  139. parent_span_id: '0000',
  140. }),
  141. makeSpan({
  142. op: 'http',
  143. description: 'request',
  144. span_id: '0002',
  145. parent_span_id: '0001',
  146. }),
  147. ];
  148. function findTransactionByEventId(tree: TraceTree, eventId: string) {
  149. return TraceTree.Find(
  150. tree.root,
  151. node => isTransactionNode(node) && node.value.event_id === eventId
  152. );
  153. }
  154. describe('TraceTree', () => {
  155. describe('aggreagate node properties', () => {
  156. it('adds errors to node', () => {
  157. const tree = TraceTree.FromTrace(
  158. makeTrace({
  159. transactions: [
  160. makeTransaction({
  161. errors: [makeTraceError()],
  162. }),
  163. ],
  164. }),
  165. traceMetadata
  166. );
  167. expect(tree.root.children[0]!.errors.size).toBe(1);
  168. });
  169. it('stores trace error as error on node', () => {
  170. const tree = TraceTree.FromTrace(
  171. makeTrace({
  172. orphan_errors: [makeTraceError()],
  173. }),
  174. traceMetadata
  175. );
  176. expect(tree.root.children[0]!.children[0]!.errors.size).toBe(1);
  177. });
  178. it('adds performance issues to node', () => {
  179. const tree = TraceTree.FromTrace(
  180. makeTrace({
  181. transactions: [
  182. makeTransaction({
  183. performance_issues: [makeTracePerformanceIssue()],
  184. }),
  185. ],
  186. }),
  187. traceMetadata
  188. );
  189. expect(tree.root.children[0]!.children[0]!.performance_issues.size).toBe(1);
  190. });
  191. it('adds transaction profile to node', () => {
  192. const tree = TraceTree.FromTrace(
  193. makeTrace({
  194. transactions: [
  195. makeTransaction({
  196. profile_id: 'profile-id',
  197. }),
  198. ],
  199. }),
  200. traceMetadata
  201. );
  202. expect(tree.root.children[0]!.children[0]!.profiles).toHaveLength(1);
  203. });
  204. it('adds continuous profile to node', () => {
  205. const tree = TraceTree.FromTrace(
  206. makeTrace({
  207. transactions: [
  208. makeTransaction({
  209. profiler_id: 'profile-id',
  210. children: [],
  211. }),
  212. ],
  213. }),
  214. traceMetadata
  215. );
  216. expect(tree.root.children[0]!.children[0]!.profiles).toHaveLength(1);
  217. });
  218. });
  219. describe('adjusts trace start and end', () => {
  220. it('based off min(events.start_timestamp)', () => {
  221. const tree = TraceTree.FromTrace(trace, traceMetadata);
  222. expect(tree.root.space[0]).toBe(trace.transactions[0]!.start_timestamp * 1e3);
  223. });
  224. it('based off max(events.timestamp)', () => {
  225. const tree = TraceTree.FromTrace(trace, traceMetadata);
  226. expect(tree.root.space[1]).toBe(4000);
  227. });
  228. // This happnes for errors only traces
  229. it('end,0 when we cannot construct a timeline', () => {
  230. const tree = TraceTree.FromTrace(
  231. makeTrace({orphan_errors: [makeTraceError({level: 'error', timestamp: end})]}),
  232. traceMetadata
  233. );
  234. expect(tree.root.space[0]).toBe(end * 1e3);
  235. expect(tree.root.space[1]).toBe(0);
  236. });
  237. it('considers all children when inferring start and end', () => {
  238. const tree = TraceTree.FromTrace(
  239. makeTrace({
  240. transactions: [
  241. makeTransaction({
  242. start_timestamp: start,
  243. timestamp: start + 1,
  244. children: [],
  245. }),
  246. makeTransaction({
  247. start_timestamp: start - 1,
  248. timestamp: start + 2,
  249. children: [],
  250. }),
  251. ],
  252. orphan_errors: [],
  253. }),
  254. traceMetadata
  255. );
  256. expect(tree.root.space[1]).toBe(3000);
  257. expect(tree.root.space[0]).toBe(start * 1e3 - 1e3);
  258. });
  259. it('considers orphan errors when inferring end', () => {
  260. const tree = TraceTree.FromTrace(
  261. makeTrace({
  262. transactions: [
  263. makeTransaction({
  264. start_timestamp: start,
  265. timestamp: start + 1,
  266. children: [],
  267. }),
  268. ],
  269. orphan_errors: [
  270. makeTraceError({
  271. level: 'error',
  272. timestamp: start + 5,
  273. }),
  274. ],
  275. }),
  276. traceMetadata
  277. );
  278. expect(tree.root.space[1]).toBe(5000);
  279. expect(tree.root.space[0]).toBe(start * 1e3);
  280. });
  281. it('replay record extends trace start', () => {
  282. const replayStart = new Date('2024-02-29T00:00:00Z').getTime();
  283. const replayEnd = new Date(replayStart + 5000).getTime();
  284. const tree = TraceTree.FromTrace(
  285. makeTrace({
  286. transactions: [
  287. makeTransaction({
  288. start_timestamp: replayStart / 1e3 + 0.1,
  289. timestamp: replayStart / 1e3 + 0.1,
  290. }),
  291. ],
  292. }),
  293. {
  294. meta: null,
  295. replay: {
  296. started_at: new Date(replayStart),
  297. finished_at: new Date(replayEnd),
  298. } as ReplayRecord,
  299. }
  300. );
  301. expect(tree.root.space[0]).toBe(replayStart);
  302. expect(tree.root.space[1]).toBe(5000);
  303. });
  304. it('measurements extend trace start and end', () => {
  305. const tree = TraceTree.FromTrace(
  306. makeTrace({
  307. transactions: [
  308. makeTransaction({
  309. start_timestamp: start,
  310. timestamp: start + 1,
  311. children: [],
  312. measurements: {
  313. ttfb: {
  314. unit: 'millisecond',
  315. value: -5000,
  316. },
  317. lcp: {
  318. unit: 'millisecond',
  319. value: 5000,
  320. },
  321. },
  322. }),
  323. ],
  324. orphan_errors: [],
  325. }),
  326. traceMetadata
  327. );
  328. expect(tree.root.space).toEqual([start * 1e3 - 5000, 10_000]);
  329. });
  330. });
  331. describe('indicators', () => {
  332. it('measurements are converted to indicators', () => {
  333. const tree = TraceTree.FromTrace(traceWithVitals, traceMetadata);
  334. expect(tree.indicators).toHaveLength(1);
  335. expect(tree.indicators[0]!.start).toBe(start * 1e3);
  336. });
  337. it('sorts indicators by start', () => {
  338. const tree = TraceTree.FromTrace(
  339. makeTrace({
  340. transactions: [
  341. makeTransaction({
  342. start_timestamp: 0,
  343. timestamp: 1,
  344. measurements: {
  345. ttfb: {value: 2000, unit: 'millisecond'},
  346. lcp: {value: 1000, unit: 'millisecond'},
  347. },
  348. }),
  349. ],
  350. }),
  351. traceMetadata
  352. );
  353. expect(tree.indicators).toHaveLength(2);
  354. expect(tree.indicators[0]!.start < tree.indicators[1]!.start).toBe(true);
  355. });
  356. });
  357. describe('FromTrace', () => {
  358. it('assembles tree from trace', () => {
  359. const tree = TraceTree.FromTrace(trace, traceMetadata);
  360. expect(tree.build().serialize()).toMatchSnapshot();
  361. });
  362. it('sorts by start_timestamp', () => {
  363. const tree = TraceTree.FromTrace(outOfOrderTrace, traceMetadata);
  364. expect(tree.build().serialize()).toMatchSnapshot();
  365. });
  366. it('inserts orphan error', () => {
  367. const tree = TraceTree.FromTrace(traceWithOrphanError, {
  368. meta: null,
  369. replay: null,
  370. });
  371. expect(tree.build().serialize()).toMatchSnapshot();
  372. });
  373. it('if parent span does not exist in span tree, the transaction stays under its previous parent', () => {
  374. const tree = TraceTree.FromTrace(
  375. makeTrace({
  376. transactions: [
  377. makeTransaction({
  378. transaction: 'root',
  379. children: [
  380. makeTransaction({transaction: 'child', parent_span_id: 'does not exist'}),
  381. ],
  382. }),
  383. ],
  384. }),
  385. traceMetadata
  386. );
  387. TraceTree.FromSpans(tree.root.children[0]!, [makeSpan()], makeEventTransaction());
  388. expect(tree.build().serialize()).toMatchSnapshot();
  389. });
  390. it('initializes canFetch based on spanChildrenCount', () => {
  391. const tree = TraceTree.FromTrace(
  392. makeTrace({
  393. transactions: [
  394. makeTransaction({
  395. event_id: 'transaction',
  396. children: [],
  397. }),
  398. makeTransaction({event_id: 'no-span-count-transaction'}),
  399. makeTransaction({event_id: 'no-spans-transaction', children: []}),
  400. ],
  401. }),
  402. {
  403. meta: {
  404. transactiontoSpanChildrenCount: {
  405. transaction: 10,
  406. 'no-spans-transaction': 1,
  407. // we have no data for child transaction
  408. },
  409. errors: 0,
  410. performance_issues: 0,
  411. projects: 0,
  412. transactions: 0,
  413. },
  414. replay: null,
  415. }
  416. );
  417. expect(findTransactionByEventId(tree, 'transaction')?.canFetch).toBe(true);
  418. expect(findTransactionByEventId(tree, 'no-span-count-transaction')?.canFetch).toBe(
  419. true
  420. );
  421. expect(findTransactionByEventId(tree, 'no-spans-transaction')?.canFetch).toBe(
  422. false
  423. );
  424. });
  425. it('initializes canFetch to true if no spanChildrenCount', () => {
  426. const tree = TraceTree.FromTrace(
  427. makeTrace({
  428. transactions: [
  429. makeTransaction({
  430. event_id: 'transaction',
  431. children: [],
  432. }),
  433. ],
  434. }),
  435. {meta: null, replay: null}
  436. );
  437. expect(findTransactionByEventId(tree, 'transaction')?.canFetch).toBe(true);
  438. });
  439. });
  440. describe('events', () => {
  441. it('does not dispatch timeline change when spans fall inside the trace bounds', async () => {
  442. const t = makeTrace({
  443. transactions: [
  444. makeTransaction({
  445. start_timestamp: start,
  446. timestamp: start + 2,
  447. event_id: 'event-id',
  448. project_slug: 'project',
  449. children: [],
  450. }),
  451. ],
  452. orphan_errors: [],
  453. });
  454. const tree = TraceTree.FromTrace(t, traceMetadata);
  455. const listener = jest.fn();
  456. tree.on('trace timeline change', listener);
  457. const txn = TraceTree.Find(tree.root, n => isTransactionNode(n))!;
  458. mockSpansResponse(
  459. [makeSpan({start_timestamp: start + 0.5, timestamp: start + 1})],
  460. 'project',
  461. 'event-id'
  462. );
  463. await tree.zoom(txn, true, {
  464. api: new MockApiClient(),
  465. organization: OrganizationFixture(),
  466. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  467. });
  468. expect(listener).not.toHaveBeenCalled();
  469. });
  470. it('dispatches timeline change when span timestamp > trace timestamp', async () => {
  471. const t = makeTrace({
  472. transactions: [
  473. makeTransaction({
  474. start_timestamp: start,
  475. timestamp: start + 1,
  476. event_id: 'event-id',
  477. project_slug: 'project',
  478. children: [],
  479. }),
  480. ],
  481. orphan_errors: [],
  482. });
  483. const tree = TraceTree.FromTrace(t, traceMetadata);
  484. const listener = jest.fn();
  485. tree.on('trace timeline change', listener);
  486. const txn = TraceTree.Find(tree.root, n => isTransactionNode(n))!;
  487. const transactionSpaceBounds = JSON.stringify(txn.space);
  488. mockSpansResponse(
  489. [makeSpan({start_timestamp: start, timestamp: start + 1.2})],
  490. 'project',
  491. 'event-id'
  492. );
  493. await tree.zoom(txn, true, {
  494. api: new MockApiClient(),
  495. organization: OrganizationFixture(),
  496. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  497. });
  498. expect(JSON.stringify(txn.space)).toEqual(transactionSpaceBounds);
  499. expect(listener).toHaveBeenCalledWith([start * 1e3, 1200]);
  500. });
  501. });
  502. describe('ForEachChild', () => {
  503. it('iterates dfs', () => {
  504. const tree = TraceTree.FromTrace(
  505. makeTrace({
  506. transactions: [
  507. makeTransaction({
  508. transaction: 'root',
  509. children: [
  510. makeTransaction({transaction: 'child'}),
  511. makeTransaction({transaction: 'other_child'}),
  512. ],
  513. }),
  514. ],
  515. }),
  516. {meta: null, replay: null}
  517. );
  518. const visitedNodes: string[] = [];
  519. TraceTree.ForEachChild(tree.root, node => {
  520. if (isTransactionNode(node)) {
  521. visitedNodes.push(node.value.transaction);
  522. }
  523. });
  524. expect(visitedNodes).toEqual(['root', 'child', 'other_child']);
  525. });
  526. });
  527. describe('expand', () => {
  528. it('expanding a parent autogroup node shows head to tail chain', () => {
  529. const tree = TraceTree.FromTrace(trace, traceMetadata);
  530. TraceTree.FromSpans(
  531. tree.root.children[0]!.children[0]!,
  532. parentAutogroupSpansWithTailChildren,
  533. makeEventTransaction()
  534. );
  535. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  536. const parentAutogroupNode = TraceTree.Find(tree.root, n =>
  537. isParentAutogroupedNode(n)
  538. )!;
  539. tree.expand(parentAutogroupNode, true);
  540. expect(tree.build().serialize()).toMatchSnapshot();
  541. });
  542. it('collapsing a parent autogroup node shows tail chain', () => {
  543. const tree = TraceTree.FromTrace(trace, traceMetadata);
  544. TraceTree.FromSpans(
  545. tree.root.children[0]!.children[0]!,
  546. parentAutogroupSpansWithTailChildren,
  547. makeEventTransaction()
  548. );
  549. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  550. const parentAutogroupNode = TraceTree.Find(tree.root, n =>
  551. isParentAutogroupedNode(n)
  552. )!;
  553. tree.expand(parentAutogroupNode, true);
  554. tree.expand(parentAutogroupNode, false);
  555. expect(tree.build().serialize()).toMatchSnapshot();
  556. });
  557. it('collapsing intermediary children is preserved', () => {
  558. const tree = TraceTree.FromTrace(trace, traceMetadata);
  559. TraceTree.FromSpans(
  560. tree.root.children[0]!.children[0]!,
  561. parentAutogroupSpansWithTailChildren,
  562. makeEventTransaction()
  563. );
  564. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  565. const parentAutogroupNode = TraceTree.Find(tree.root, n =>
  566. isParentAutogroupedNode(n)
  567. )! as ParentAutogroupNode;
  568. // Expand the chain and collapse an intermediary child
  569. tree.expand(parentAutogroupNode, true);
  570. tree.expand(parentAutogroupNode.head, false);
  571. const snapshot = tree.build().serialize();
  572. // Collapse the autogroup node and expand it again
  573. tree.expand(parentAutogroupNode, false);
  574. tree.expand(parentAutogroupNode, true);
  575. // Assert that the snapshot is preserved and we only render the parent autogroup chain
  576. // up to the collapsed span
  577. expect(tree.build().serialize()).toEqual(snapshot);
  578. expect(tree.build().serialize()).toMatchSnapshot();
  579. });
  580. it('expanding a sibling autogroup node shows sibling span', () => {
  581. const tree = TraceTree.FromTrace(trace, traceMetadata);
  582. TraceTree.FromSpans(
  583. tree.root.children[0]!.children[0]!,
  584. siblingAutogroupSpans,
  585. makeEventTransaction()
  586. );
  587. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  588. TraceTree.ForEachChild(tree.root, n => {
  589. if (isSiblingAutogroupedNode(n)) {
  590. tree.expand(n, true);
  591. }
  592. });
  593. expect(tree.build().serialize()).toMatchSnapshot();
  594. });
  595. it('collapsing a sibling autogroup node hides children', () => {
  596. const tree = TraceTree.FromTrace(trace, traceMetadata);
  597. TraceTree.FromSpans(
  598. tree.root.children[0]!.children[0]!,
  599. siblingAutogroupSpans,
  600. makeEventTransaction()
  601. );
  602. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  603. TraceTree.ForEachChild(tree.root, n => {
  604. if (isSiblingAutogroupedNode(n)) {
  605. tree.expand(n, true);
  606. }
  607. });
  608. TraceTree.ForEachChild(tree.root, n => {
  609. if (isSiblingAutogroupedNode(n)) {
  610. tree.expand(n, false);
  611. }
  612. });
  613. expect(tree.build().serialize()).toMatchSnapshot();
  614. });
  615. });
  616. describe('zoom', () => {
  617. it('does nothing if node cannot fetch', () => {
  618. const tree = TraceTree.FromTrace(traceWithEventId, traceMetadata);
  619. const request = mockSpansResponse([], 'project', 'event-id');
  620. tree.root.children[0]!.children[0]!.canFetch = false;
  621. tree.zoom(tree.root.children[0]!.children[0]!, true, {
  622. api: new MockApiClient(),
  623. organization: OrganizationFixture(),
  624. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  625. });
  626. expect(request).not.toHaveBeenCalled();
  627. });
  628. it('caches promise', () => {
  629. const tree = TraceTree.FromTrace(traceWithEventId, traceMetadata);
  630. const request = mockSpansResponse([], 'project', 'event-id');
  631. tree.zoom(tree.root.children[0]!.children[0]!, true, {
  632. api: new MockApiClient(),
  633. organization: OrganizationFixture(),
  634. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  635. });
  636. tree.zoom(tree.root.children[0]!, true, {
  637. api: new MockApiClient(),
  638. organization: OrganizationFixture(),
  639. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  640. });
  641. expect(request).toHaveBeenCalledTimes(1);
  642. });
  643. it('zooms in on transaction node', async () => {
  644. const tree = TraceTree.FromTrace(traceWithEventId, traceMetadata);
  645. mockSpansResponse([makeSpan()], 'project', 'child-event-id');
  646. // Zoom mutates the list, so we need to build first
  647. tree.build();
  648. await tree.zoom(tree.root.children[0]!.children[0]!.children[0]!, true, {
  649. api: new MockApiClient(),
  650. organization: OrganizationFixture(),
  651. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  652. });
  653. expect(tree.build().serialize()).toMatchSnapshot();
  654. });
  655. it('maintains the span tree when parent is zoomed in', async () => {
  656. const tree = TraceTree.FromTrace(traceWithEventId, traceMetadata);
  657. // Zoom mutates the list, so we need to build first
  658. tree.build();
  659. // Zoom in on child span
  660. mockSpansResponse([makeSpan()], 'project', 'child-event-id');
  661. await tree.zoom(tree.root.children[0]!.children[0]!.children[0]!, true, {
  662. api: new MockApiClient(),
  663. organization: OrganizationFixture(),
  664. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  665. });
  666. // Then zoom in on a parent
  667. mockSpansResponse([makeSpan()], 'project', 'event-id');
  668. await tree.zoom(tree.root.children[0]!.children[0]!, true, {
  669. api: new MockApiClient(),
  670. organization: OrganizationFixture(),
  671. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  672. });
  673. expect(tree.build().serialize()).toMatchSnapshot();
  674. });
  675. it('reparents child transactions under spans with matching ids', async () => {
  676. const tree = TraceTree.FromTrace(
  677. makeTrace({
  678. transactions: [
  679. makeTransaction({
  680. transaction: 'root',
  681. event_id: 'parent-event-id',
  682. project_slug: 'project',
  683. children: [
  684. makeTransaction({
  685. transaction: 'child',
  686. parent_span_id: '0000',
  687. event_id: 'child-event-id',
  688. project_slug: 'project',
  689. }),
  690. ],
  691. }),
  692. ],
  693. }),
  694. traceMetadata
  695. );
  696. // Zoom mutates the list, so we need to build first
  697. tree.build();
  698. mockSpansResponse([makeSpan({span_id: '0001'})], 'project', 'child-event-id');
  699. await tree.zoom(tree.root.children[0]!.children[0]!.children[0]!, true, {
  700. api: new MockApiClient(),
  701. organization: OrganizationFixture(),
  702. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  703. });
  704. mockSpansResponse([makeSpan({span_id: '0000'})], 'project', 'parent-event-id');
  705. await tree.zoom(tree.root.children[0]!.children[0]!, true, {
  706. api: new MockApiClient(),
  707. organization: OrganizationFixture(),
  708. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  709. });
  710. expect(tree.build().serialize()).toMatchSnapshot();
  711. });
  712. it('preserves parent of nested child transactions', async () => {
  713. const tree = TraceTree.FromTrace(
  714. makeTrace({
  715. transactions: [
  716. makeTransaction({
  717. transaction: 'root',
  718. event_id: 'parent-event-id',
  719. project_slug: 'project',
  720. children: [
  721. makeTransaction({
  722. transaction: 'child',
  723. event_id: 'child-event-id',
  724. project_slug: 'project',
  725. parent_span_id: '0000',
  726. children: [
  727. makeTransaction({
  728. transaction: 'grandchild',
  729. event_id: 'grandchild-event-id',
  730. project_slug: 'project',
  731. }),
  732. ],
  733. }),
  734. ],
  735. }),
  736. ],
  737. }),
  738. traceMetadata
  739. );
  740. // Zoom mutates the list, so we need to build first
  741. tree.build();
  742. mockSpansResponse([makeSpan({span_id: '0000'})], 'project', 'parent-event-id');
  743. await tree.zoom(tree.root.children[0]!.children[0]!, true, {
  744. api: new MockApiClient(),
  745. organization: OrganizationFixture(),
  746. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  747. });
  748. const grandchild = findTransactionByEventId(tree, 'grandchild-event-id');
  749. const child = findTransactionByEventId(tree, 'child-event-id');
  750. expect(grandchild?.parent).toBe(child);
  751. expect(tree.serialize()).toMatchSnapshot();
  752. });
  753. it('zoomout returns tree back to a transaction tree', async () => {
  754. const tree = TraceTree.FromTrace(
  755. makeTrace({
  756. transactions: [
  757. makeTransaction({
  758. transaction: 'root',
  759. event_id: 'parent-event-id',
  760. project_slug: 'project',
  761. children: [
  762. makeTransaction({
  763. transaction: 'child',
  764. event_id: 'child-event-id',
  765. project_slug: 'project',
  766. parent_span_id: '0000',
  767. children: [
  768. makeTransaction({
  769. transaction: 'grandchild',
  770. event_id: 'grandchild-event-id',
  771. project_slug: 'project',
  772. }),
  773. ],
  774. }),
  775. ],
  776. }),
  777. ],
  778. }),
  779. traceMetadata
  780. );
  781. // Zoom mutates the list, so we need to build first
  782. const transactionTreeSnapshot = tree.build().serialize();
  783. mockSpansResponse([makeSpan({span_id: '0000'})], 'project', 'parent-event-id');
  784. for (const bool of [true, false]) {
  785. await tree.zoom(tree.root.children[0]!.children[0]!, bool, {
  786. api: new MockApiClient(),
  787. organization: OrganizationFixture(),
  788. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  789. });
  790. }
  791. expect(tree.serialize()).toEqual(transactionTreeSnapshot);
  792. });
  793. // @TODO This currently filters out all spans - we should preserve spans that are children of other
  794. // zoomed in transactions
  795. it('zooming out preserves spans of child zoomed in transaction', async () => {
  796. const tree = TraceTree.FromTrace(
  797. makeTrace({
  798. transactions: [
  799. makeTransaction({
  800. transaction: 'root',
  801. event_id: 'parent-event-id',
  802. project_slug: 'project',
  803. children: [
  804. makeTransaction({
  805. transaction: 'child',
  806. event_id: 'child-event-id',
  807. project_slug: 'project',
  808. children: [
  809. makeTransaction({
  810. transaction: 'grandchild',
  811. event_id: 'grandchild-event-id',
  812. project_slug: 'project',
  813. parent_span_id: '0000',
  814. }),
  815. ],
  816. }),
  817. ],
  818. }),
  819. ],
  820. }),
  821. traceMetadata
  822. );
  823. // Zoom mutates the list, so we need to build first
  824. tree.build();
  825. mockSpansResponse(
  826. [makeSpan({span_id: '0000', op: 'parent-op'})],
  827. 'project',
  828. 'child-event-id'
  829. );
  830. const child = findTransactionByEventId(tree, 'child-event-id');
  831. await tree.zoom(child!, true, {
  832. api: new MockApiClient(),
  833. organization: OrganizationFixture(),
  834. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  835. });
  836. mockSpansResponse(
  837. [makeSpan({span_id: '0001', op: 'child-op'})],
  838. 'project',
  839. 'grandchild-event-id'
  840. );
  841. const grandchild = findTransactionByEventId(tree, 'grandchild-event-id');
  842. await tree.zoom(grandchild!, true, {
  843. api: new MockApiClient(),
  844. organization: OrganizationFixture(),
  845. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  846. });
  847. await tree.zoom(child!, false, {
  848. api: new MockApiClient(),
  849. organization: OrganizationFixture(),
  850. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  851. });
  852. const spans = TraceTree.FindAll(tree.root, n => isSpanNode(n));
  853. expect(spans).toHaveLength(1);
  854. expect(tree.serialize()).toMatchSnapshot();
  855. });
  856. });
  857. describe('Find', () => {
  858. it('finds first node by predicate', () => {
  859. const tree = TraceTree.FromTrace(
  860. makeTrace({
  861. transactions: [
  862. makeTransaction({
  863. transaction: 'first',
  864. children: [makeTransaction({transaction: 'second'})],
  865. }),
  866. ],
  867. }),
  868. traceMetadata
  869. );
  870. const node = TraceTree.Find(tree.root, n => isTransactionNode(n));
  871. expect(node).not.toBeNull();
  872. expect((node as any).value.transaction).toBe('first');
  873. });
  874. it('returns null if no node is found', () => {
  875. const tree = TraceTree.FromTrace(trace, traceMetadata);
  876. const node = TraceTree.Find(tree.root, n => (n as any) === 'does not exist');
  877. expect(node).toBeNull();
  878. });
  879. });
  880. describe('FindByID', () => {
  881. it('finds transaction by event_id', () => {
  882. const traceWithError = makeTrace({
  883. transactions: [
  884. makeTransaction({transaction: 'first', event_id: 'first-event-id'}),
  885. ],
  886. });
  887. const tree = TraceTree.FromTrace(traceWithError, traceMetadata);
  888. const node = TraceTree.FindByID(tree.root, 'first-event-id');
  889. assertTransactionNode(node);
  890. expect(node.value.transaction).toBe('first');
  891. });
  892. it('matches by error event_id', () => {
  893. const traceWithError = makeTrace({
  894. transactions: [
  895. makeTransaction({
  896. transaction: 'first',
  897. event_id: 'txn-event-id',
  898. errors: [makeTraceError({event_id: 'error-event-id'})],
  899. }),
  900. ],
  901. });
  902. const tree = TraceTree.FromTrace(traceWithError, traceMetadata);
  903. const node = TraceTree.FindByID(tree.root, 'error-event-id');
  904. assertTransactionNode(node);
  905. expect(node.value.transaction).toBe('first');
  906. });
  907. });
  908. describe('FindAll', () => {
  909. it('finds all nodes by predicate', () => {
  910. const tree = TraceTree.FromTrace(trace, traceMetadata);
  911. const nodes = TraceTree.FindAll(tree.root, n => isTransactionNode(n));
  912. expect(nodes).toHaveLength(2);
  913. });
  914. });
  915. describe('DirectVisibleChildren', () => {
  916. it('returns children for transaction', () => {
  917. const tree = TraceTree.FromTrace(trace, traceMetadata);
  918. expect(TraceTree.DirectVisibleChildren(tree.root.children[0]!)).toEqual(
  919. tree.root.children[0]!.children
  920. );
  921. });
  922. it('returns tail for collapsed parent autogroup', () => {
  923. const tree = TraceTree.FromTrace(trace, traceMetadata);
  924. TraceTree.FromSpans(
  925. tree.root.children[0]!,
  926. parentAutogroupSpansWithTailChildren,
  927. makeEventTransaction()
  928. );
  929. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  930. const parentAutogroup = TraceTree.Find(tree.root, node =>
  931. isParentAutogroupedNode(node)
  932. ) as ParentAutogroupNode;
  933. expect(parentAutogroup).not.toBeNull();
  934. expect(TraceTree.DirectVisibleChildren(parentAutogroup)[0]).toBe(
  935. parentAutogroup.tail.children[0]
  936. );
  937. });
  938. it('returns head for expanded parent autogroup', () => {
  939. const tree = TraceTree.FromTrace(trace, traceMetadata);
  940. TraceTree.FromSpans(
  941. tree.root.children[0]!,
  942. parentAutogroupSpans,
  943. makeEventTransaction()
  944. );
  945. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  946. const parentAutogroup = TraceTree.Find(tree.root, node =>
  947. isParentAutogroupedNode(node)
  948. ) as ParentAutogroupNode;
  949. tree.expand(parentAutogroup, true);
  950. expect(TraceTree.DirectVisibleChildren(parentAutogroup)[0]).toBe(
  951. parentAutogroup.head
  952. );
  953. });
  954. });
  955. describe('HasVisibleChildren', () => {
  956. it('true when transaction has children', () => {
  957. const tree = TraceTree.FromTrace(
  958. makeTrace({
  959. transactions: [makeTransaction({children: [makeTransaction()]})],
  960. }),
  961. traceMetadata
  962. );
  963. expect(TraceTree.HasVisibleChildren(tree.root.children[0]!)).toBe(true);
  964. });
  965. describe('span', () => {
  966. it.each([true, false])('%s when span has children and is expanded', expanded => {
  967. const tree = TraceTree.FromTrace(
  968. makeTrace({
  969. transactions: [makeTransaction({children: [makeTransaction()]})],
  970. }),
  971. traceMetadata
  972. );
  973. TraceTree.FromSpans(
  974. tree.root.children[0]!,
  975. [
  976. makeSpan({span_id: '0000'}),
  977. makeSpan({span_id: '0001', parent_span_id: '0000'}),
  978. ],
  979. makeEventTransaction()
  980. );
  981. const span = TraceTree.Find(
  982. tree.root,
  983. node => isSpanNode(node) && node.value.span_id === '0000'
  984. )!;
  985. tree.expand(span, expanded);
  986. expect(TraceTree.HasVisibleChildren(span)).toBe(expanded);
  987. });
  988. });
  989. describe('sibling autogroup', () => {
  990. it.each([true, false])('%s when sibling autogroup is expanded', expanded => {
  991. const tree = TraceTree.FromTrace(trace, traceMetadata);
  992. TraceTree.FromSpans(
  993. tree.root.children[0]!,
  994. siblingAutogroupSpans,
  995. makeEventTransaction()
  996. );
  997. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  998. const siblingAutogroup = TraceTree.Find(tree.root, node =>
  999. isSiblingAutogroupedNode(node)
  1000. );
  1001. tree.expand(siblingAutogroup!, expanded);
  1002. expect(TraceTree.HasVisibleChildren(siblingAutogroup!)).toBe(expanded);
  1003. });
  1004. });
  1005. describe('parent autogroup', () => {
  1006. it.each([true, false])('%s when parent autogroup is expanded', expanded => {
  1007. const tree = TraceTree.FromTrace(trace, traceMetadata);
  1008. TraceTree.FromSpans(
  1009. tree.root.children[0]!,
  1010. parentAutogroupSpans,
  1011. makeEventTransaction()
  1012. );
  1013. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  1014. const parentAutogroup = TraceTree.Find(tree.root, node =>
  1015. isParentAutogroupedNode(node)
  1016. );
  1017. tree.expand(parentAutogroup!, expanded);
  1018. expect(TraceTree.HasVisibleChildren(parentAutogroup!)).toBe(expanded);
  1019. });
  1020. });
  1021. describe('parent autogroup when tail has children', () => {
  1022. // Always true because tail has children
  1023. it.each([true, false])('%s when parent autogroup is expanded', expanded => {
  1024. const tree = TraceTree.FromTrace(trace, traceMetadata);
  1025. TraceTree.FromSpans(
  1026. tree.root.children[0]!,
  1027. parentAutogroupSpansWithTailChildren,
  1028. makeEventTransaction()
  1029. );
  1030. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  1031. tree.build();
  1032. const parentAutogroup = TraceTree.Find(tree.root, node =>
  1033. isParentAutogroupedNode(node)
  1034. );
  1035. tree.expand(parentAutogroup!, expanded);
  1036. expect(TraceTree.HasVisibleChildren(parentAutogroup!)).toBe(true);
  1037. });
  1038. });
  1039. });
  1040. describe('IsLastChild', () => {
  1041. it('returns false if node is not last child', () => {
  1042. const tree = TraceTree.FromTrace(
  1043. makeTrace({
  1044. transactions: [makeTransaction(), makeTransaction()],
  1045. }),
  1046. traceMetadata
  1047. );
  1048. expect(TraceTree.IsLastChild(tree.root.children[0]!.children[0]!)).toBe(false);
  1049. });
  1050. it('returns true if node is last child', () => {
  1051. const tree = TraceTree.FromTrace(
  1052. makeTrace({
  1053. transactions: [makeTransaction(), makeTransaction()],
  1054. }),
  1055. traceMetadata
  1056. );
  1057. expect(TraceTree.IsLastChild(tree.root.children[0]!.children[1]!)).toBe(true);
  1058. });
  1059. });
  1060. describe('Invalidate', () => {
  1061. it('invalidates node', () => {
  1062. const tree = TraceTree.FromTrace(trace, traceMetadata);
  1063. tree.root.children[0]!.depth = 10;
  1064. tree.root.children[0]!.connectors = [1, 2, 3];
  1065. TraceTree.invalidate(tree.root.children[0]!, false);
  1066. expect(tree.root.children[0]!.depth).toBeUndefined();
  1067. expect(tree.root.children[0]!.connectors).toBeUndefined();
  1068. });
  1069. it('recursively invalidates children', () => {
  1070. const tree = TraceTree.FromTrace(trace, traceMetadata);
  1071. tree.root.children[0]!.depth = 10;
  1072. tree.root.children[0]!.connectors = [1, 2, 3];
  1073. TraceTree.invalidate(tree.root, true);
  1074. expect(tree.root.children[0]!.depth).toBeUndefined();
  1075. expect(tree.root.children[0]!.connectors).toBeUndefined();
  1076. });
  1077. });
  1078. describe('appendTree', () => {
  1079. it('appends tree to end of current tree', () => {
  1080. const tree = TraceTree.FromTrace(trace, {replay: null, meta: null});
  1081. tree.appendTree(TraceTree.FromTrace(trace, {replay: null, meta: null}));
  1082. expect(tree.build().serialize()).toMatchSnapshot();
  1083. });
  1084. it('appending extends trace space', () => {
  1085. const tree = TraceTree.FromTrace(
  1086. makeTrace({
  1087. transactions: [makeTransaction({start_timestamp: start, timestamp: start + 1})],
  1088. }),
  1089. {replay: null, meta: null}
  1090. );
  1091. const otherTree = TraceTree.FromTrace(
  1092. makeTrace({
  1093. transactions: [
  1094. makeTransaction({start_timestamp: start, timestamp: start + 10}),
  1095. ],
  1096. }),
  1097. {replay: null, meta: null}
  1098. );
  1099. tree.appendTree(otherTree);
  1100. expect(tree.root.space[0]).toBe(start * 1e3);
  1101. expect(tree.root.space[1]).toBe(10 * 1e3);
  1102. });
  1103. });
  1104. describe('PathToNode', () => {
  1105. const nestedTransactionTrace = makeTrace({
  1106. transactions: [
  1107. makeTransaction({
  1108. start_timestamp: start,
  1109. timestamp: start + 2,
  1110. transaction: 'parent',
  1111. span_id: 'parent-span-id',
  1112. event_id: 'parent-event-id',
  1113. children: [
  1114. makeTransaction({
  1115. start_timestamp: start + 1,
  1116. timestamp: start + 4,
  1117. transaction: 'child',
  1118. event_id: 'child-event-id',
  1119. }),
  1120. ],
  1121. }),
  1122. ],
  1123. });
  1124. it('path to transaction node', () => {
  1125. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1126. const transactionNode = TraceTree.Find(
  1127. tree.root,
  1128. node => isTransactionNode(node) && node.value.transaction === 'child'
  1129. )!;
  1130. const path = TraceTree.PathToNode(transactionNode);
  1131. expect(path).toEqual(['txn-child-event-id']);
  1132. });
  1133. it('path to span includes parent txn', () => {
  1134. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1135. const child = TraceTree.Find(
  1136. tree.root,
  1137. node => isTransactionNode(node) && node.value.transaction === 'child'
  1138. )!;
  1139. TraceTree.FromSpans(
  1140. child,
  1141. [makeSpan({span_id: 'span-id'})],
  1142. makeEventTransaction()
  1143. );
  1144. const span = TraceTree.Find(tree.root, node => isSpanNode(node))!;
  1145. const path = TraceTree.PathToNode(span);
  1146. expect(path).toEqual(['span-span-id', 'txn-child-event-id']);
  1147. });
  1148. describe('parent autogroup', () => {
  1149. const pathParentAutogroupSpans = [
  1150. makeSpan({op: 'db', description: 'redis', span_id: 'head-span-id'}),
  1151. makeSpan({
  1152. op: 'db',
  1153. description: 'redis',
  1154. span_id: 'tail-span-id',
  1155. parent_span_id: 'head-span-id',
  1156. }),
  1157. makeSpan({
  1158. op: 'http',
  1159. description: 'request',
  1160. span_id: 'child-span-id',
  1161. parent_span_id: 'tail-span-id',
  1162. }),
  1163. ];
  1164. it('parent autogroup', () => {
  1165. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1166. const child = TraceTree.Find(
  1167. tree.root,
  1168. node => isTransactionNode(node) && node.value.transaction === 'child'
  1169. )!;
  1170. TraceTree.FromSpans(child, pathParentAutogroupSpans, makeEventTransaction());
  1171. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  1172. const parentAutogroup = TraceTree.Find(tree.root, node =>
  1173. isParentAutogroupedNode(node)
  1174. )!;
  1175. const path = TraceTree.PathToNode(parentAutogroup);
  1176. expect(path).toEqual(['ag-head-span-id', 'txn-child-event-id']);
  1177. });
  1178. it('path to child of parent autogroup skips autogroup', () => {
  1179. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1180. const child = TraceTree.Find(
  1181. tree.root,
  1182. node => isTransactionNode(node) && node.value.transaction === 'child'
  1183. )!;
  1184. TraceTree.FromSpans(child, pathParentAutogroupSpans, makeEventTransaction());
  1185. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  1186. const parentAutogroup = TraceTree.Find(tree.root, node =>
  1187. isParentAutogroupedNode(node)
  1188. ) as ParentAutogroupNode;
  1189. expect(TraceTree.PathToNode(parentAutogroup.tail)).toEqual([
  1190. 'span-tail-span-id',
  1191. 'txn-child-event-id',
  1192. ]);
  1193. const requestSpan = TraceTree.Find(
  1194. tree.root,
  1195. node => isSpanNode(node) && node.value.description === 'request'
  1196. )!;
  1197. expect(TraceTree.PathToNode(requestSpan)).toEqual([
  1198. 'span-child-span-id',
  1199. 'txn-child-event-id',
  1200. ]);
  1201. });
  1202. });
  1203. describe('sibling autogroup', () => {
  1204. const pathSiblingAutogroupSpans = [
  1205. makeSpan({
  1206. op: 'db',
  1207. description: 'redis',
  1208. span_id: '0',
  1209. start_timestamp: start,
  1210. timestamp: start + 1,
  1211. }),
  1212. makeSpan({
  1213. op: 'db',
  1214. description: 'redis',
  1215. start_timestamp: start,
  1216. timestamp: start + 1,
  1217. span_id: '1',
  1218. }),
  1219. makeSpan({
  1220. op: 'db',
  1221. description: 'redis',
  1222. start_timestamp: start,
  1223. timestamp: start + 1,
  1224. }),
  1225. makeSpan({
  1226. op: 'db',
  1227. description: 'redis',
  1228. start_timestamp: start,
  1229. timestamp: start + 1,
  1230. }),
  1231. makeSpan({
  1232. op: 'db',
  1233. description: 'redis',
  1234. start_timestamp: start,
  1235. timestamp: start + 1,
  1236. }),
  1237. ];
  1238. it('path to sibling autogroup', () => {
  1239. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1240. const child = TraceTree.Find(
  1241. tree.root,
  1242. node => isTransactionNode(node) && node.value.transaction === 'child'
  1243. )!;
  1244. TraceTree.FromSpans(child, pathSiblingAutogroupSpans, makeEventTransaction());
  1245. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  1246. const siblingAutogroup = TraceTree.Find(tree.root, node =>
  1247. isSiblingAutogroupedNode(node)
  1248. ) as SiblingAutogroupNode;
  1249. const path = TraceTree.PathToNode(siblingAutogroup);
  1250. expect(path).toEqual(['ag-0', 'txn-child-event-id']);
  1251. });
  1252. it('path to child of sibling autogroup skips autogroup', () => {
  1253. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1254. const child = TraceTree.Find(
  1255. tree.root,
  1256. node => isTransactionNode(node) && node.value.transaction === 'child'
  1257. )!;
  1258. TraceTree.FromSpans(child, pathSiblingAutogroupSpans, makeEventTransaction());
  1259. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  1260. const siblingAutogroup = TraceTree.Find(tree.root, node =>
  1261. isSiblingAutogroupedNode(node)
  1262. ) as SiblingAutogroupNode;
  1263. const path = TraceTree.PathToNode(siblingAutogroup.children[1]!);
  1264. expect(path).toEqual(['span-1', 'txn-child-event-id']);
  1265. });
  1266. });
  1267. it('path to missing instrumentation node', () => {
  1268. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1269. const missingInstrumentationSpans = [
  1270. makeSpan({
  1271. op: 'db',
  1272. description: 'redis',
  1273. span_id: '0',
  1274. start_timestamp: start,
  1275. timestamp: start + 1,
  1276. }),
  1277. makeSpan({
  1278. op: 'db',
  1279. description: 'redis',
  1280. start_timestamp: start + 2,
  1281. timestamp: start + 4,
  1282. }),
  1283. ];
  1284. const child = TraceTree.Find(
  1285. tree.root,
  1286. node => isTransactionNode(node) && node.value.transaction === 'child'
  1287. )!;
  1288. TraceTree.FromSpans(child, missingInstrumentationSpans, makeEventTransaction());
  1289. TraceTree.DetectMissingInstrumentation(tree.root);
  1290. const missingInstrumentationNode = TraceTree.Find(tree.root, node =>
  1291. isMissingInstrumentationNode(node)
  1292. )!;
  1293. const path = TraceTree.PathToNode(missingInstrumentationNode);
  1294. expect(path).toEqual(['ms-0', 'txn-child-event-id']);
  1295. });
  1296. });
  1297. describe('ExpandToPath', () => {
  1298. const organization = OrganizationFixture();
  1299. const api = new MockApiClient();
  1300. const nestedTransactionTrace = makeTrace({
  1301. transactions: [
  1302. makeTransaction({
  1303. start_timestamp: start,
  1304. timestamp: start + 2,
  1305. transaction: 'parent',
  1306. span_id: 'parent-span-id',
  1307. event_id: 'parent-event-id',
  1308. children: [
  1309. makeTransaction({
  1310. start_timestamp: start + 1,
  1311. timestamp: start + 4,
  1312. transaction: 'child',
  1313. event_id: 'child-event-id',
  1314. project_slug: 'project',
  1315. }),
  1316. ],
  1317. }),
  1318. ],
  1319. });
  1320. it('expands transactions from path segments', async () => {
  1321. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1322. const child = TraceTree.Find(
  1323. tree.root,
  1324. node => isTransactionNode(node) && node.value.transaction === 'child'
  1325. )!;
  1326. await TraceTree.ExpandToPath(tree, TraceTree.PathToNode(child), {
  1327. api,
  1328. organization,
  1329. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  1330. });
  1331. expect(tree.build().serialize()).toMatchSnapshot();
  1332. });
  1333. it('discards non txns segments', async () => {
  1334. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1335. const child = TraceTree.Find(
  1336. tree.root,
  1337. node => isTransactionNode(node) && node.value.transaction === 'child'
  1338. )!;
  1339. const request = mockSpansResponse([makeSpan()], 'project', 'child-event-id');
  1340. await TraceTree.ExpandToPath(tree, ['span-0', ...TraceTree.PathToNode(child)], {
  1341. api,
  1342. organization,
  1343. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  1344. });
  1345. expect(request).toHaveBeenCalled();
  1346. expect(tree.build().serialize()).toMatchSnapshot();
  1347. });
  1348. });
  1349. });