traceTree.spec.tsx 46 KB

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