traceTree.spec.tsx 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539
  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. transaction_child_count_map: {
  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. span_count: 0,
  414. span_count_map: {},
  415. },
  416. replay: null,
  417. }
  418. );
  419. expect(findTransactionByEventId(tree, 'transaction')?.canFetch).toBe(true);
  420. expect(findTransactionByEventId(tree, 'no-span-count-transaction')?.canFetch).toBe(
  421. true
  422. );
  423. expect(findTransactionByEventId(tree, 'no-spans-transaction')?.canFetch).toBe(
  424. false
  425. );
  426. });
  427. it('initializes canFetch to true if no spanChildrenCount', () => {
  428. const tree = TraceTree.FromTrace(
  429. makeTrace({
  430. transactions: [
  431. makeTransaction({
  432. event_id: 'transaction',
  433. children: [],
  434. }),
  435. ],
  436. }),
  437. {meta: null, replay: null}
  438. );
  439. expect(findTransactionByEventId(tree, 'transaction')?.canFetch).toBe(true);
  440. });
  441. });
  442. describe('events', () => {
  443. it('does not dispatch timeline change when spans fall inside the trace bounds', async () => {
  444. const t = makeTrace({
  445. transactions: [
  446. makeTransaction({
  447. start_timestamp: start,
  448. timestamp: start + 2,
  449. event_id: 'event-id',
  450. project_slug: 'project',
  451. children: [],
  452. }),
  453. ],
  454. orphan_errors: [],
  455. });
  456. const tree = TraceTree.FromTrace(t, traceMetadata);
  457. const listener = jest.fn();
  458. tree.on('trace timeline change', listener);
  459. const txn = TraceTree.Find(tree.root, n => isTransactionNode(n))!;
  460. mockSpansResponse(
  461. [makeSpan({start_timestamp: start + 0.5, timestamp: start + 1})],
  462. 'project',
  463. 'event-id'
  464. );
  465. await tree.zoom(txn, true, {
  466. api: new MockApiClient(),
  467. organization: OrganizationFixture(),
  468. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  469. });
  470. expect(listener).not.toHaveBeenCalled();
  471. });
  472. it('dispatches timeline change when span timestamp > trace timestamp', async () => {
  473. const t = makeTrace({
  474. transactions: [
  475. makeTransaction({
  476. start_timestamp: start,
  477. timestamp: start + 1,
  478. event_id: 'event-id',
  479. project_slug: 'project',
  480. children: [],
  481. }),
  482. ],
  483. orphan_errors: [],
  484. });
  485. const tree = TraceTree.FromTrace(t, traceMetadata);
  486. const listener = jest.fn();
  487. tree.on('trace timeline change', listener);
  488. const txn = TraceTree.Find(tree.root, n => isTransactionNode(n))!;
  489. const transactionSpaceBounds = JSON.stringify(txn.space);
  490. mockSpansResponse(
  491. [makeSpan({start_timestamp: start, timestamp: start + 1.2})],
  492. 'project',
  493. 'event-id'
  494. );
  495. await tree.zoom(txn, true, {
  496. api: new MockApiClient(),
  497. organization: OrganizationFixture(),
  498. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  499. });
  500. expect(JSON.stringify(txn.space)).toEqual(transactionSpaceBounds);
  501. expect(listener).toHaveBeenCalledWith([start * 1e3, 1200]);
  502. });
  503. });
  504. describe('ForEachChild', () => {
  505. it('iterates dfs', () => {
  506. const tree = TraceTree.FromTrace(
  507. makeTrace({
  508. transactions: [
  509. makeTransaction({
  510. transaction: 'root',
  511. children: [
  512. makeTransaction({transaction: 'child'}),
  513. makeTransaction({transaction: 'other_child'}),
  514. ],
  515. }),
  516. ],
  517. }),
  518. {meta: null, replay: null}
  519. );
  520. const visitedNodes: string[] = [];
  521. TraceTree.ForEachChild(tree.root, node => {
  522. if (isTransactionNode(node)) {
  523. visitedNodes.push(node.value.transaction);
  524. }
  525. });
  526. expect(visitedNodes).toEqual(['root', 'child', 'other_child']);
  527. });
  528. });
  529. describe('expand', () => {
  530. it('expanding a parent autogroup node shows head to tail chain', () => {
  531. const tree = TraceTree.FromTrace(trace, traceMetadata);
  532. TraceTree.FromSpans(
  533. tree.root.children[0]!.children[0]!,
  534. parentAutogroupSpansWithTailChildren,
  535. makeEventTransaction()
  536. );
  537. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  538. const parentAutogroupNode = TraceTree.Find(tree.root, n =>
  539. isParentAutogroupedNode(n)
  540. )!;
  541. tree.expand(parentAutogroupNode, true);
  542. expect(tree.build().serialize()).toMatchSnapshot();
  543. });
  544. it('collapsing a parent autogroup node shows tail chain', () => {
  545. const tree = TraceTree.FromTrace(trace, traceMetadata);
  546. TraceTree.FromSpans(
  547. tree.root.children[0]!.children[0]!,
  548. parentAutogroupSpansWithTailChildren,
  549. makeEventTransaction()
  550. );
  551. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  552. const parentAutogroupNode = TraceTree.Find(tree.root, n =>
  553. isParentAutogroupedNode(n)
  554. )!;
  555. tree.expand(parentAutogroupNode, true);
  556. tree.expand(parentAutogroupNode, false);
  557. expect(tree.build().serialize()).toMatchSnapshot();
  558. });
  559. it('collapsing intermediary children is preserved', () => {
  560. const tree = TraceTree.FromTrace(trace, traceMetadata);
  561. TraceTree.FromSpans(
  562. tree.root.children[0]!.children[0]!,
  563. parentAutogroupSpansWithTailChildren,
  564. makeEventTransaction()
  565. );
  566. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  567. const parentAutogroupNode = TraceTree.Find(tree.root, n =>
  568. isParentAutogroupedNode(n)
  569. )! as ParentAutogroupNode;
  570. // Expand the chain and collapse an intermediary child
  571. tree.expand(parentAutogroupNode, true);
  572. tree.expand(parentAutogroupNode.head, false);
  573. const snapshot = tree.build().serialize();
  574. // Collapse the autogroup node and expand it again
  575. tree.expand(parentAutogroupNode, false);
  576. tree.expand(parentAutogroupNode, true);
  577. // Assert that the snapshot is preserved and we only render the parent autogroup chain
  578. // up to the collapsed span
  579. expect(tree.build().serialize()).toEqual(snapshot);
  580. expect(tree.build().serialize()).toMatchSnapshot();
  581. });
  582. it('expanding a sibling autogroup node shows sibling span', () => {
  583. const tree = TraceTree.FromTrace(trace, traceMetadata);
  584. TraceTree.FromSpans(
  585. tree.root.children[0]!.children[0]!,
  586. siblingAutogroupSpans,
  587. makeEventTransaction()
  588. );
  589. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  590. TraceTree.ForEachChild(tree.root, n => {
  591. if (isSiblingAutogroupedNode(n)) {
  592. tree.expand(n, true);
  593. }
  594. });
  595. expect(tree.build().serialize()).toMatchSnapshot();
  596. });
  597. it('collapsing a sibling autogroup node hides children', () => {
  598. const tree = TraceTree.FromTrace(trace, traceMetadata);
  599. TraceTree.FromSpans(
  600. tree.root.children[0]!.children[0]!,
  601. siblingAutogroupSpans,
  602. makeEventTransaction()
  603. );
  604. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  605. TraceTree.ForEachChild(tree.root, n => {
  606. if (isSiblingAutogroupedNode(n)) {
  607. tree.expand(n, true);
  608. }
  609. });
  610. TraceTree.ForEachChild(tree.root, n => {
  611. if (isSiblingAutogroupedNode(n)) {
  612. tree.expand(n, false);
  613. }
  614. });
  615. expect(tree.build().serialize()).toMatchSnapshot();
  616. });
  617. });
  618. describe('zoom', () => {
  619. it('does nothing if node cannot fetch', () => {
  620. const tree = TraceTree.FromTrace(traceWithEventId, traceMetadata);
  621. const request = mockSpansResponse([], 'project', 'event-id');
  622. tree.root.children[0]!.children[0]!.canFetch = false;
  623. tree.zoom(tree.root.children[0]!.children[0]!, true, {
  624. api: new MockApiClient(),
  625. organization: OrganizationFixture(),
  626. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  627. });
  628. expect(request).not.toHaveBeenCalled();
  629. });
  630. it('caches promise', () => {
  631. const tree = TraceTree.FromTrace(traceWithEventId, traceMetadata);
  632. const request = mockSpansResponse([], 'project', 'event-id');
  633. tree.zoom(tree.root.children[0]!.children[0]!, true, {
  634. api: new MockApiClient(),
  635. organization: OrganizationFixture(),
  636. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  637. });
  638. tree.zoom(tree.root.children[0]!, true, {
  639. api: new MockApiClient(),
  640. organization: OrganizationFixture(),
  641. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  642. });
  643. expect(request).toHaveBeenCalledTimes(1);
  644. });
  645. it('zooms in on transaction node', async () => {
  646. const tree = TraceTree.FromTrace(traceWithEventId, traceMetadata);
  647. mockSpansResponse([makeSpan()], 'project', 'child-event-id');
  648. // Zoom mutates the list, so we need to build first
  649. tree.build();
  650. await tree.zoom(tree.root.children[0]!.children[0]!.children[0]!, true, {
  651. api: new MockApiClient(),
  652. organization: OrganizationFixture(),
  653. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  654. });
  655. expect(tree.build().serialize()).toMatchSnapshot();
  656. });
  657. it('maintains the span tree when parent is zoomed in', async () => {
  658. const tree = TraceTree.FromTrace(traceWithEventId, traceMetadata);
  659. // Zoom mutates the list, so we need to build first
  660. tree.build();
  661. // Zoom in on child span
  662. mockSpansResponse([makeSpan()], 'project', 'child-event-id');
  663. await tree.zoom(tree.root.children[0]!.children[0]!.children[0]!, true, {
  664. api: new MockApiClient(),
  665. organization: OrganizationFixture(),
  666. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  667. });
  668. // Then zoom in on a parent
  669. mockSpansResponse([makeSpan()], 'project', 'event-id');
  670. await tree.zoom(tree.root.children[0]!.children[0]!, true, {
  671. api: new MockApiClient(),
  672. organization: OrganizationFixture(),
  673. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  674. });
  675. expect(tree.build().serialize()).toMatchSnapshot();
  676. });
  677. it('reparents child transactions under spans with matching ids', async () => {
  678. const tree = TraceTree.FromTrace(
  679. makeTrace({
  680. transactions: [
  681. makeTransaction({
  682. transaction: 'root',
  683. event_id: 'parent-event-id',
  684. project_slug: 'project',
  685. children: [
  686. makeTransaction({
  687. transaction: 'child',
  688. parent_span_id: '0000',
  689. event_id: 'child-event-id',
  690. project_slug: 'project',
  691. }),
  692. ],
  693. }),
  694. ],
  695. }),
  696. traceMetadata
  697. );
  698. // Zoom mutates the list, so we need to build first
  699. tree.build();
  700. mockSpansResponse([makeSpan({span_id: '0001'})], 'project', 'child-event-id');
  701. await tree.zoom(tree.root.children[0]!.children[0]!.children[0]!, true, {
  702. api: new MockApiClient(),
  703. organization: OrganizationFixture(),
  704. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  705. });
  706. mockSpansResponse([makeSpan({span_id: '0000'})], 'project', 'parent-event-id');
  707. await tree.zoom(tree.root.children[0]!.children[0]!, true, {
  708. api: new MockApiClient(),
  709. organization: OrganizationFixture(),
  710. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  711. });
  712. expect(tree.build().serialize()).toMatchSnapshot();
  713. });
  714. it('preserves parent of nested child transactions', async () => {
  715. const tree = TraceTree.FromTrace(
  716. makeTrace({
  717. transactions: [
  718. makeTransaction({
  719. transaction: 'root',
  720. event_id: 'parent-event-id',
  721. project_slug: 'project',
  722. children: [
  723. makeTransaction({
  724. transaction: 'child',
  725. event_id: 'child-event-id',
  726. project_slug: 'project',
  727. parent_span_id: '0000',
  728. children: [
  729. makeTransaction({
  730. transaction: 'grandchild',
  731. event_id: 'grandchild-event-id',
  732. project_slug: 'project',
  733. }),
  734. ],
  735. }),
  736. ],
  737. }),
  738. ],
  739. }),
  740. traceMetadata
  741. );
  742. // Zoom mutates the list, so we need to build first
  743. tree.build();
  744. mockSpansResponse([makeSpan({span_id: '0000'})], 'project', 'parent-event-id');
  745. await tree.zoom(tree.root.children[0]!.children[0]!, true, {
  746. api: new MockApiClient(),
  747. organization: OrganizationFixture(),
  748. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  749. });
  750. const grandchild = findTransactionByEventId(tree, 'grandchild-event-id');
  751. const child = findTransactionByEventId(tree, 'child-event-id');
  752. expect(grandchild?.parent).toBe(child);
  753. expect(tree.serialize()).toMatchSnapshot();
  754. });
  755. it('zoomout returns tree back to a transaction tree', async () => {
  756. const tree = TraceTree.FromTrace(
  757. makeTrace({
  758. transactions: [
  759. makeTransaction({
  760. transaction: 'root',
  761. event_id: 'parent-event-id',
  762. project_slug: 'project',
  763. children: [
  764. makeTransaction({
  765. transaction: 'child',
  766. event_id: 'child-event-id',
  767. project_slug: 'project',
  768. parent_span_id: '0000',
  769. children: [
  770. makeTransaction({
  771. transaction: 'grandchild',
  772. event_id: 'grandchild-event-id',
  773. project_slug: 'project',
  774. }),
  775. ],
  776. }),
  777. ],
  778. }),
  779. ],
  780. }),
  781. traceMetadata
  782. );
  783. // Zoom mutates the list, so we need to build first
  784. const transactionTreeSnapshot = tree.build().serialize();
  785. mockSpansResponse([makeSpan({span_id: '0000'})], 'project', 'parent-event-id');
  786. for (const bool of [true, false]) {
  787. await tree.zoom(tree.root.children[0]!.children[0]!, bool, {
  788. api: new MockApiClient(),
  789. organization: OrganizationFixture(),
  790. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  791. });
  792. }
  793. expect(tree.serialize()).toEqual(transactionTreeSnapshot);
  794. });
  795. // @TODO This currently filters out all spans - we should preserve spans that are children of other
  796. // zoomed in transactions
  797. it('zooming out preserves spans of child zoomed in transaction', async () => {
  798. const tree = TraceTree.FromTrace(
  799. makeTrace({
  800. transactions: [
  801. makeTransaction({
  802. transaction: 'root',
  803. event_id: 'parent-event-id',
  804. project_slug: 'project',
  805. children: [
  806. makeTransaction({
  807. transaction: 'child',
  808. event_id: 'child-event-id',
  809. project_slug: 'project',
  810. children: [
  811. makeTransaction({
  812. transaction: 'grandchild',
  813. event_id: 'grandchild-event-id',
  814. project_slug: 'project',
  815. parent_span_id: '0000',
  816. }),
  817. ],
  818. }),
  819. ],
  820. }),
  821. ],
  822. }),
  823. traceMetadata
  824. );
  825. // Zoom mutates the list, so we need to build first
  826. tree.build();
  827. mockSpansResponse(
  828. [makeSpan({span_id: '0000', op: 'parent-op'})],
  829. 'project',
  830. 'child-event-id'
  831. );
  832. const child = findTransactionByEventId(tree, 'child-event-id');
  833. await tree.zoom(child!, true, {
  834. api: new MockApiClient(),
  835. organization: OrganizationFixture(),
  836. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  837. });
  838. mockSpansResponse(
  839. [makeSpan({span_id: '0001', op: 'child-op'})],
  840. 'project',
  841. 'grandchild-event-id'
  842. );
  843. const grandchild = findTransactionByEventId(tree, 'grandchild-event-id');
  844. await tree.zoom(grandchild!, true, {
  845. api: new MockApiClient(),
  846. organization: OrganizationFixture(),
  847. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  848. });
  849. await tree.zoom(child!, false, {
  850. api: new MockApiClient(),
  851. organization: OrganizationFixture(),
  852. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  853. });
  854. const spans = TraceTree.FindAll(tree.root, n => isSpanNode(n));
  855. expect(spans).toHaveLength(1);
  856. expect(tree.serialize()).toMatchSnapshot();
  857. });
  858. });
  859. describe('Find', () => {
  860. it('finds first node by predicate', () => {
  861. const tree = TraceTree.FromTrace(
  862. makeTrace({
  863. transactions: [
  864. makeTransaction({
  865. transaction: 'first',
  866. children: [makeTransaction({transaction: 'second'})],
  867. }),
  868. ],
  869. }),
  870. traceMetadata
  871. );
  872. const node = TraceTree.Find(tree.root, n => isTransactionNode(n));
  873. expect(node).not.toBeNull();
  874. expect((node as any).value.transaction).toBe('first');
  875. });
  876. it('returns null if no node is found', () => {
  877. const tree = TraceTree.FromTrace(trace, traceMetadata);
  878. const node = TraceTree.Find(tree.root, n => (n as any) === 'does not exist');
  879. expect(node).toBeNull();
  880. });
  881. });
  882. describe('FindByID', () => {
  883. it('finds transaction by event_id', () => {
  884. const traceWithError = makeTrace({
  885. transactions: [
  886. makeTransaction({transaction: 'first', event_id: 'first-event-id'}),
  887. ],
  888. });
  889. const tree = TraceTree.FromTrace(traceWithError, traceMetadata);
  890. const node = TraceTree.FindByID(tree.root, 'first-event-id');
  891. assertTransactionNode(node);
  892. expect(node.value.transaction).toBe('first');
  893. });
  894. it('matches by error event_id', () => {
  895. const traceWithError = makeTrace({
  896. transactions: [
  897. makeTransaction({
  898. transaction: 'first',
  899. event_id: 'txn-event-id',
  900. errors: [makeTraceError({event_id: 'error-event-id'})],
  901. }),
  902. ],
  903. });
  904. const tree = TraceTree.FromTrace(traceWithError, traceMetadata);
  905. const node = TraceTree.FindByID(tree.root, 'error-event-id');
  906. assertTransactionNode(node);
  907. expect(node.value.transaction).toBe('first');
  908. });
  909. });
  910. describe('FindAll', () => {
  911. it('finds all nodes by predicate', () => {
  912. const tree = TraceTree.FromTrace(trace, traceMetadata);
  913. const nodes = TraceTree.FindAll(tree.root, n => isTransactionNode(n));
  914. expect(nodes).toHaveLength(2);
  915. });
  916. });
  917. describe('DirectVisibleChildren', () => {
  918. it('returns children for transaction', () => {
  919. const tree = TraceTree.FromTrace(trace, traceMetadata);
  920. expect(TraceTree.DirectVisibleChildren(tree.root.children[0]!)).toEqual(
  921. tree.root.children[0]!.children
  922. );
  923. });
  924. it('returns tail for collapsed parent autogroup', () => {
  925. const tree = TraceTree.FromTrace(trace, traceMetadata);
  926. TraceTree.FromSpans(
  927. tree.root.children[0]!,
  928. parentAutogroupSpansWithTailChildren,
  929. makeEventTransaction()
  930. );
  931. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  932. const parentAutogroup = TraceTree.Find(tree.root, node =>
  933. isParentAutogroupedNode(node)
  934. ) as ParentAutogroupNode;
  935. expect(parentAutogroup).not.toBeNull();
  936. expect(TraceTree.DirectVisibleChildren(parentAutogroup)[0]).toBe(
  937. parentAutogroup.tail.children[0]
  938. );
  939. });
  940. it('returns head for expanded parent autogroup', () => {
  941. const tree = TraceTree.FromTrace(trace, traceMetadata);
  942. TraceTree.FromSpans(
  943. tree.root.children[0]!,
  944. parentAutogroupSpans,
  945. makeEventTransaction()
  946. );
  947. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  948. const parentAutogroup = TraceTree.Find(tree.root, node =>
  949. isParentAutogroupedNode(node)
  950. ) as ParentAutogroupNode;
  951. tree.expand(parentAutogroup, true);
  952. expect(TraceTree.DirectVisibleChildren(parentAutogroup)[0]).toBe(
  953. parentAutogroup.head
  954. );
  955. });
  956. });
  957. describe('HasVisibleChildren', () => {
  958. it('true when transaction has children', () => {
  959. const tree = TraceTree.FromTrace(
  960. makeTrace({
  961. transactions: [makeTransaction({children: [makeTransaction()]})],
  962. }),
  963. traceMetadata
  964. );
  965. expect(TraceTree.HasVisibleChildren(tree.root.children[0]!)).toBe(true);
  966. });
  967. describe('span', () => {
  968. it.each([true, false])('%s when span has children and is expanded', expanded => {
  969. const tree = TraceTree.FromTrace(
  970. makeTrace({
  971. transactions: [makeTransaction({children: [makeTransaction()]})],
  972. }),
  973. traceMetadata
  974. );
  975. TraceTree.FromSpans(
  976. tree.root.children[0]!,
  977. [
  978. makeSpan({span_id: '0000'}),
  979. makeSpan({span_id: '0001', parent_span_id: '0000'}),
  980. ],
  981. makeEventTransaction()
  982. );
  983. const span = TraceTree.Find(
  984. tree.root,
  985. node => isSpanNode(node) && node.value.span_id === '0000'
  986. )!;
  987. tree.expand(span, expanded);
  988. expect(TraceTree.HasVisibleChildren(span)).toBe(expanded);
  989. });
  990. });
  991. describe('sibling autogroup', () => {
  992. it.each([true, false])('%s when sibling autogroup is expanded', expanded => {
  993. const tree = TraceTree.FromTrace(trace, traceMetadata);
  994. TraceTree.FromSpans(
  995. tree.root.children[0]!,
  996. siblingAutogroupSpans,
  997. makeEventTransaction()
  998. );
  999. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  1000. const siblingAutogroup = TraceTree.Find(tree.root, node =>
  1001. isSiblingAutogroupedNode(node)
  1002. );
  1003. tree.expand(siblingAutogroup!, expanded);
  1004. expect(TraceTree.HasVisibleChildren(siblingAutogroup!)).toBe(expanded);
  1005. });
  1006. });
  1007. describe('parent autogroup', () => {
  1008. it.each([true, false])('%s when parent autogroup is expanded', expanded => {
  1009. const tree = TraceTree.FromTrace(trace, traceMetadata);
  1010. TraceTree.FromSpans(
  1011. tree.root.children[0]!,
  1012. parentAutogroupSpans,
  1013. makeEventTransaction()
  1014. );
  1015. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  1016. const parentAutogroup = TraceTree.Find(tree.root, node =>
  1017. isParentAutogroupedNode(node)
  1018. );
  1019. tree.expand(parentAutogroup!, expanded);
  1020. expect(TraceTree.HasVisibleChildren(parentAutogroup!)).toBe(expanded);
  1021. });
  1022. });
  1023. describe('parent autogroup when tail has children', () => {
  1024. // Always true because tail has children
  1025. it.each([true, false])('%s when parent autogroup is expanded', expanded => {
  1026. const tree = TraceTree.FromTrace(trace, traceMetadata);
  1027. TraceTree.FromSpans(
  1028. tree.root.children[0]!,
  1029. parentAutogroupSpansWithTailChildren,
  1030. makeEventTransaction()
  1031. );
  1032. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  1033. tree.build();
  1034. const parentAutogroup = TraceTree.Find(tree.root, node =>
  1035. isParentAutogroupedNode(node)
  1036. );
  1037. tree.expand(parentAutogroup!, expanded);
  1038. expect(TraceTree.HasVisibleChildren(parentAutogroup!)).toBe(true);
  1039. });
  1040. });
  1041. });
  1042. describe('IsLastChild', () => {
  1043. it('returns false if node is not last child', () => {
  1044. const tree = TraceTree.FromTrace(
  1045. makeTrace({
  1046. transactions: [makeTransaction(), makeTransaction()],
  1047. }),
  1048. traceMetadata
  1049. );
  1050. expect(TraceTree.IsLastChild(tree.root.children[0]!.children[0]!)).toBe(false);
  1051. });
  1052. it('returns true if node is last child', () => {
  1053. const tree = TraceTree.FromTrace(
  1054. makeTrace({
  1055. transactions: [makeTransaction(), makeTransaction()],
  1056. }),
  1057. traceMetadata
  1058. );
  1059. expect(TraceTree.IsLastChild(tree.root.children[0]!.children[1]!)).toBe(true);
  1060. });
  1061. });
  1062. describe('Invalidate', () => {
  1063. it('invalidates node', () => {
  1064. const tree = TraceTree.FromTrace(trace, traceMetadata);
  1065. tree.root.children[0]!.depth = 10;
  1066. tree.root.children[0]!.connectors = [1, 2, 3];
  1067. TraceTree.invalidate(tree.root.children[0]!, false);
  1068. expect(tree.root.children[0]!.depth).toBeUndefined();
  1069. expect(tree.root.children[0]!.connectors).toBeUndefined();
  1070. });
  1071. it('recursively invalidates children', () => {
  1072. const tree = TraceTree.FromTrace(trace, traceMetadata);
  1073. tree.root.children[0]!.depth = 10;
  1074. tree.root.children[0]!.connectors = [1, 2, 3];
  1075. TraceTree.invalidate(tree.root, true);
  1076. expect(tree.root.children[0]!.depth).toBeUndefined();
  1077. expect(tree.root.children[0]!.connectors).toBeUndefined();
  1078. });
  1079. });
  1080. describe('appendTree', () => {
  1081. it('appends tree to end of current tree', () => {
  1082. const tree = TraceTree.FromTrace(trace, {replay: null, meta: null});
  1083. tree.appendTree(TraceTree.FromTrace(trace, {replay: null, meta: null}));
  1084. expect(tree.build().serialize()).toMatchSnapshot();
  1085. });
  1086. it('appending extends trace space', () => {
  1087. const tree = TraceTree.FromTrace(
  1088. makeTrace({
  1089. transactions: [makeTransaction({start_timestamp: start, timestamp: start + 1})],
  1090. }),
  1091. {replay: null, meta: null}
  1092. );
  1093. const otherTree = TraceTree.FromTrace(
  1094. makeTrace({
  1095. transactions: [
  1096. makeTransaction({start_timestamp: start, timestamp: start + 10}),
  1097. ],
  1098. }),
  1099. {replay: null, meta: null}
  1100. );
  1101. tree.appendTree(otherTree);
  1102. expect(tree.root.space[0]).toBe(start * 1e3);
  1103. expect(tree.root.space[1]).toBe(10 * 1e3);
  1104. });
  1105. });
  1106. describe('PathToNode', () => {
  1107. const nestedTransactionTrace = makeTrace({
  1108. transactions: [
  1109. makeTransaction({
  1110. start_timestamp: start,
  1111. timestamp: start + 2,
  1112. transaction: 'parent',
  1113. span_id: 'parent-span-id',
  1114. event_id: 'parent-event-id',
  1115. children: [
  1116. makeTransaction({
  1117. start_timestamp: start + 1,
  1118. timestamp: start + 4,
  1119. transaction: 'child',
  1120. event_id: 'child-event-id',
  1121. }),
  1122. ],
  1123. }),
  1124. ],
  1125. });
  1126. it('path to transaction node', () => {
  1127. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1128. const transactionNode = TraceTree.Find(
  1129. tree.root,
  1130. node => isTransactionNode(node) && node.value.transaction === 'child'
  1131. )!;
  1132. const path = TraceTree.PathToNode(transactionNode);
  1133. expect(path).toEqual(['txn-child-event-id']);
  1134. });
  1135. it('path to span includes parent txn', () => {
  1136. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1137. const child = TraceTree.Find(
  1138. tree.root,
  1139. node => isTransactionNode(node) && node.value.transaction === 'child'
  1140. )!;
  1141. TraceTree.FromSpans(
  1142. child,
  1143. [makeSpan({span_id: 'span-id'})],
  1144. makeEventTransaction()
  1145. );
  1146. const span = TraceTree.Find(tree.root, node => isSpanNode(node))!;
  1147. const path = TraceTree.PathToNode(span);
  1148. expect(path).toEqual(['span-span-id', 'txn-child-event-id']);
  1149. });
  1150. describe('parent autogroup', () => {
  1151. const pathParentAutogroupSpans = [
  1152. makeSpan({op: 'db', description: 'redis', span_id: 'head-span-id'}),
  1153. makeSpan({
  1154. op: 'db',
  1155. description: 'redis',
  1156. span_id: 'tail-span-id',
  1157. parent_span_id: 'head-span-id',
  1158. }),
  1159. makeSpan({
  1160. op: 'http',
  1161. description: 'request',
  1162. span_id: 'child-span-id',
  1163. parent_span_id: 'tail-span-id',
  1164. }),
  1165. ];
  1166. it('parent autogroup', () => {
  1167. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1168. const child = TraceTree.Find(
  1169. tree.root,
  1170. node => isTransactionNode(node) && node.value.transaction === 'child'
  1171. )!;
  1172. TraceTree.FromSpans(child, pathParentAutogroupSpans, makeEventTransaction());
  1173. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  1174. const parentAutogroup = TraceTree.Find(tree.root, node =>
  1175. isParentAutogroupedNode(node)
  1176. )!;
  1177. const path = TraceTree.PathToNode(parentAutogroup);
  1178. expect(path).toEqual(['ag-head-span-id', 'txn-child-event-id']);
  1179. });
  1180. it('path to child of parent autogroup skips autogroup', () => {
  1181. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1182. const child = TraceTree.Find(
  1183. tree.root,
  1184. node => isTransactionNode(node) && node.value.transaction === 'child'
  1185. )!;
  1186. TraceTree.FromSpans(child, pathParentAutogroupSpans, makeEventTransaction());
  1187. TraceTree.AutogroupDirectChildrenSpanNodes(tree.root);
  1188. const parentAutogroup = TraceTree.Find(tree.root, node =>
  1189. isParentAutogroupedNode(node)
  1190. ) as ParentAutogroupNode;
  1191. expect(TraceTree.PathToNode(parentAutogroup.tail)).toEqual([
  1192. 'span-tail-span-id',
  1193. 'txn-child-event-id',
  1194. ]);
  1195. const requestSpan = TraceTree.Find(
  1196. tree.root,
  1197. node => isSpanNode(node) && node.value.description === 'request'
  1198. )!;
  1199. expect(TraceTree.PathToNode(requestSpan)).toEqual([
  1200. 'span-child-span-id',
  1201. 'txn-child-event-id',
  1202. ]);
  1203. });
  1204. });
  1205. describe('sibling autogroup', () => {
  1206. const pathSiblingAutogroupSpans = [
  1207. makeSpan({
  1208. op: 'db',
  1209. description: 'redis',
  1210. span_id: '0',
  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. span_id: '1',
  1220. }),
  1221. makeSpan({
  1222. op: 'db',
  1223. description: 'redis',
  1224. start_timestamp: start,
  1225. timestamp: start + 1,
  1226. }),
  1227. makeSpan({
  1228. op: 'db',
  1229. description: 'redis',
  1230. start_timestamp: start,
  1231. timestamp: start + 1,
  1232. }),
  1233. makeSpan({
  1234. op: 'db',
  1235. description: 'redis',
  1236. start_timestamp: start,
  1237. timestamp: start + 1,
  1238. }),
  1239. ];
  1240. it('path to sibling autogroup', () => {
  1241. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1242. const child = TraceTree.Find(
  1243. tree.root,
  1244. node => isTransactionNode(node) && node.value.transaction === 'child'
  1245. )!;
  1246. TraceTree.FromSpans(child, pathSiblingAutogroupSpans, makeEventTransaction());
  1247. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  1248. const siblingAutogroup = TraceTree.Find(tree.root, node =>
  1249. isSiblingAutogroupedNode(node)
  1250. ) as SiblingAutogroupNode;
  1251. const path = TraceTree.PathToNode(siblingAutogroup);
  1252. expect(path).toEqual(['ag-0', 'txn-child-event-id']);
  1253. });
  1254. it('path to child of sibling autogroup skips autogroup', () => {
  1255. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1256. const child = TraceTree.Find(
  1257. tree.root,
  1258. node => isTransactionNode(node) && node.value.transaction === 'child'
  1259. )!;
  1260. TraceTree.FromSpans(child, pathSiblingAutogroupSpans, makeEventTransaction());
  1261. TraceTree.AutogroupSiblingSpanNodes(tree.root);
  1262. const siblingAutogroup = TraceTree.Find(tree.root, node =>
  1263. isSiblingAutogroupedNode(node)
  1264. ) as SiblingAutogroupNode;
  1265. const path = TraceTree.PathToNode(siblingAutogroup.children[1]!);
  1266. expect(path).toEqual(['span-1', 'txn-child-event-id']);
  1267. });
  1268. });
  1269. it('path to missing instrumentation node', () => {
  1270. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1271. const missingInstrumentationSpans = [
  1272. makeSpan({
  1273. op: 'db',
  1274. description: 'redis',
  1275. span_id: '0',
  1276. start_timestamp: start,
  1277. timestamp: start + 1,
  1278. }),
  1279. makeSpan({
  1280. op: 'db',
  1281. description: 'redis',
  1282. start_timestamp: start + 2,
  1283. timestamp: start + 4,
  1284. }),
  1285. ];
  1286. const child = TraceTree.Find(
  1287. tree.root,
  1288. node => isTransactionNode(node) && node.value.transaction === 'child'
  1289. )!;
  1290. TraceTree.FromSpans(child, missingInstrumentationSpans, makeEventTransaction());
  1291. TraceTree.DetectMissingInstrumentation(tree.root);
  1292. const missingInstrumentationNode = TraceTree.Find(tree.root, node =>
  1293. isMissingInstrumentationNode(node)
  1294. )!;
  1295. const path = TraceTree.PathToNode(missingInstrumentationNode);
  1296. expect(path).toEqual(['ms-0', 'txn-child-event-id']);
  1297. });
  1298. });
  1299. describe('ExpandToPath', () => {
  1300. const organization = OrganizationFixture();
  1301. const api = new MockApiClient();
  1302. const nestedTransactionTrace = makeTrace({
  1303. transactions: [
  1304. makeTransaction({
  1305. start_timestamp: start,
  1306. timestamp: start + 2,
  1307. transaction: 'parent',
  1308. span_id: 'parent-span-id',
  1309. event_id: 'parent-event-id',
  1310. children: [
  1311. makeTransaction({
  1312. start_timestamp: start + 1,
  1313. timestamp: start + 4,
  1314. transaction: 'child',
  1315. event_id: 'child-event-id',
  1316. project_slug: 'project',
  1317. }),
  1318. ],
  1319. }),
  1320. ],
  1321. });
  1322. it('expands transactions from path segments', async () => {
  1323. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1324. const child = TraceTree.Find(
  1325. tree.root,
  1326. node => isTransactionNode(node) && node.value.transaction === 'child'
  1327. )!;
  1328. await TraceTree.ExpandToPath(tree, TraceTree.PathToNode(child), {
  1329. api,
  1330. organization,
  1331. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  1332. });
  1333. expect(tree.build().serialize()).toMatchSnapshot();
  1334. });
  1335. it('discards non txns segments', async () => {
  1336. const tree = TraceTree.FromTrace(nestedTransactionTrace, traceMetadata);
  1337. const child = TraceTree.Find(
  1338. tree.root,
  1339. node => isTransactionNode(node) && node.value.transaction === 'child'
  1340. )!;
  1341. const request = mockSpansResponse([makeSpan()], 'project', 'child-event-id');
  1342. await TraceTree.ExpandToPath(tree, ['span-0', ...TraceTree.PathToNode(child)], {
  1343. api,
  1344. organization,
  1345. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  1346. });
  1347. expect(request).toHaveBeenCalled();
  1348. expect(tree.build().serialize()).toMatchSnapshot();
  1349. });
  1350. });
  1351. });