traceTree.spec.tsx 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {waitFor} from 'sentry-test/reactTestingLibrary';
  3. import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
  4. import {EntryType, type Event} from 'sentry/types';
  5. import type {
  6. TraceFullDetailed,
  7. TraceSplitResults,
  8. } from 'sentry/utils/performance/quickTrace/types';
  9. import {TraceType} from '../traceDetails/newTraceDetailsContent';
  10. import {
  11. isAutogroupedNode,
  12. isMissingInstrumentationNode,
  13. isSpanNode,
  14. isTransactionNode,
  15. } from './guards';
  16. import {
  17. ParentAutogroupNode,
  18. type SiblingAutogroupNode,
  19. TraceTree,
  20. TraceTreeNode,
  21. } from './traceTree';
  22. function makeTrace(
  23. overrides: Partial<TraceSplitResults<TraceFullDetailed>>
  24. ): TraceSplitResults<TraceFullDetailed> {
  25. return {
  26. transactions: [],
  27. orphan_errors: [],
  28. ...overrides,
  29. } as TraceSplitResults<TraceFullDetailed>;
  30. }
  31. function makeTransaction(overrides: Partial<TraceFullDetailed> = {}): TraceFullDetailed {
  32. return {
  33. children: [],
  34. start_timestamp: 0,
  35. timestamp: 1,
  36. transaction: 'transaction',
  37. 'transaction.op': '',
  38. 'transaction.status': '',
  39. ...overrides,
  40. } as TraceFullDetailed;
  41. }
  42. function makeSpan(overrides: Partial<RawSpanType> = {}): RawSpanType {
  43. return {
  44. op: '',
  45. description: '',
  46. span_id: '',
  47. start_timestamp: 0,
  48. timestamp: 10,
  49. ...overrides,
  50. } as RawSpanType;
  51. }
  52. function makeTraceError(
  53. overrides: Partial<TraceTree.TraceError> = {}
  54. ): TraceTree.TraceError {
  55. return {
  56. title: 'MaybeEncodingError: Error sending result',
  57. level: 'error',
  58. data: {},
  59. ...overrides,
  60. } as TraceTree.TraceError;
  61. }
  62. function makeEvent(overrides: Partial<Event> = {}, spans: RawSpanType[] = []): Event {
  63. return {
  64. entries: [{type: EntryType.SPANS, data: spans}],
  65. ...overrides,
  66. } as Event;
  67. }
  68. function assertSpanNode(
  69. node: TraceTreeNode<TraceTree.NodeValue>
  70. ): asserts node is TraceTreeNode<TraceTree.Span> {
  71. if (!isSpanNode(node)) {
  72. throw new Error('node is not a span');
  73. }
  74. }
  75. // function assertTraceNode(
  76. // node: TraceTreeNode<TraceTree.NodeValue>
  77. // ): asserts node is TraceTreeNode<TraceTree.Trace> {
  78. // if (!isTraceNode(node)) {
  79. // throw new Error('node is not a trace');
  80. // }
  81. // }
  82. function assertTransactionNode(
  83. node: TraceTreeNode<TraceTree.NodeValue> | null
  84. ): asserts node is TraceTreeNode<TraceTree.Transaction> {
  85. if (!node || !isTransactionNode(node)) {
  86. throw new Error('node is not a transaction');
  87. }
  88. }
  89. function assertMissingInstrumentationNode(
  90. node: TraceTreeNode<TraceTree.NodeValue>
  91. ): asserts node is TraceTreeNode<TraceTree.MissingInstrumentationSpan> {
  92. if (!isMissingInstrumentationNode(node)) {
  93. throw new Error('node is not a missing instrumentation node');
  94. }
  95. }
  96. function assertAutogroupedNode(
  97. node: TraceTreeNode<TraceTree.NodeValue>
  98. ): asserts node is ParentAutogroupNode | SiblingAutogroupNode {
  99. if (!isAutogroupedNode(node)) {
  100. throw new Error('node is not a autogrouped node');
  101. }
  102. }
  103. function assertParentAutogroupedNode(
  104. node: TraceTreeNode<TraceTree.NodeValue>
  105. ): asserts node is ParentAutogroupNode {
  106. if (!(node instanceof ParentAutogroupNode)) {
  107. throw new Error('node is not a parent autogrouped node');
  108. }
  109. }
  110. // function _assertSiblingAutogroupedNode(
  111. // node: TraceTreeNode<TraceTree.NodeValue>
  112. // ): asserts node is ParentAutogroupNode {
  113. // if (!(node instanceof SiblingAutogroupNode)) {
  114. // throw new Error('node is not a parent node');
  115. // }
  116. // }
  117. describe('TreeNode', () => {
  118. it('expands transaction nodes by default', () => {
  119. const node = new TraceTreeNode(null, makeTransaction(), {
  120. project_slug: '',
  121. event_id: '',
  122. });
  123. expect(node.expanded).toBe(true);
  124. });
  125. it('points parent to node', () => {
  126. const root = new TraceTreeNode(null, makeTransaction(), {
  127. project_slug: '',
  128. event_id: '',
  129. });
  130. const child = new TraceTreeNode(root, makeTransaction(), {
  131. project_slug: '',
  132. event_id: '',
  133. });
  134. expect(child.parent).toBe(root);
  135. });
  136. it('depth', () => {
  137. const root = new TraceTreeNode(null, makeTransaction(), {
  138. project_slug: '',
  139. event_id: '',
  140. });
  141. const child = new TraceTreeNode(root, makeTransaction(), {
  142. project_slug: '',
  143. event_id: '',
  144. });
  145. const grandChild = new TraceTreeNode(child, makeTransaction(), {
  146. project_slug: '',
  147. event_id: '',
  148. });
  149. expect(grandChild.depth).toBe(1);
  150. });
  151. it('getVisibleChildren', () => {
  152. const root = new TraceTreeNode(null, makeTransaction(), {
  153. project_slug: '',
  154. event_id: '',
  155. });
  156. const child = new TraceTreeNode(root, makeTransaction(), {
  157. project_slug: '',
  158. event_id: '',
  159. });
  160. root.children.push(child);
  161. expect(root.getVisibleChildren()).toHaveLength(1);
  162. expect(root.getVisibleChildren()[0]).toBe(child);
  163. root.expanded = false;
  164. expect(root.getVisibleChildren()).toHaveLength(0);
  165. });
  166. it('getVisibleChildrenCount', () => {
  167. const root = new TraceTreeNode(null, makeTransaction(), {
  168. project_slug: '',
  169. event_id: '',
  170. });
  171. const child = new TraceTreeNode(root, makeTransaction(), {
  172. project_slug: '',
  173. event_id: '',
  174. });
  175. root.children.push(child);
  176. expect(root.getVisibleChildrenCount()).toBe(1);
  177. root.expanded = false;
  178. expect(root.getVisibleChildrenCount()).toBe(0);
  179. });
  180. describe('path', () => {
  181. describe('nested transactions', () => {
  182. let child: any = null;
  183. for (let i = 0; i < 3; i++) {
  184. const node = new TraceTreeNode(
  185. child,
  186. makeTransaction({
  187. event_id: i === 0 ? 'parent' : i === 1 ? 'child' : 'grandchild',
  188. }),
  189. {
  190. project_slug: '',
  191. event_id: '',
  192. }
  193. );
  194. child = node;
  195. }
  196. it('first txn node', () => {
  197. expect(child.parent.parent.path).toEqual(['txn:parent']);
  198. });
  199. it('leafmost node', () => {
  200. expect(child.path).toEqual(['txn:grandchild', 'txn:child', 'txn:parent']);
  201. });
  202. });
  203. describe('spans', () => {
  204. const tree = TraceTree.FromTrace(
  205. makeTrace({
  206. transactions: [
  207. makeTransaction({
  208. transaction: '/',
  209. project_slug: 'project',
  210. event_id: 'event_id',
  211. }),
  212. ],
  213. })
  214. );
  215. MockApiClient.addMockResponse({
  216. url: '/organizations/org-slug/events/project:event_id/',
  217. method: 'GET',
  218. body: makeEvent({}, [makeSpan({description: 'span', op: 'db', span_id: 'span'})]),
  219. });
  220. tree.zoomIn(tree.list[1], true, {
  221. api: new MockApiClient(),
  222. organization: OrganizationFixture(),
  223. });
  224. it('when span is a child of a txn', async () => {
  225. await waitFor(() => {
  226. expect(tree.list.length).toBe(3);
  227. });
  228. expect(tree.list[tree.list.length - 1].path).toEqual([
  229. 'span:span',
  230. 'txn:event_id',
  231. ]);
  232. });
  233. });
  234. describe('autogrouped children', () => {
  235. const tree = TraceTree.FromTrace(
  236. makeTrace({
  237. transactions: [
  238. makeTransaction({
  239. transaction: '/',
  240. project_slug: 'project',
  241. event_id: 'event_id',
  242. }),
  243. ],
  244. })
  245. );
  246. MockApiClient.addMockResponse({
  247. url: '/organizations/org-slug/events/project:event_id/',
  248. method: 'GET',
  249. body: makeEvent({}, [
  250. makeSpan({description: 'span', op: 'db', span_id: '2'}),
  251. makeSpan({description: 'span', op: 'db', span_id: '3', parent_span_id: '2'}),
  252. makeSpan({description: 'span', op: 'db', span_id: '4', parent_span_id: '3'}),
  253. makeSpan({description: 'span', op: 'db', span_id: '5', parent_span_id: '4'}),
  254. ]),
  255. });
  256. tree.zoomIn(tree.list[1], true, {
  257. api: new MockApiClient(),
  258. organization: OrganizationFixture(),
  259. });
  260. it('autogrouped node', async () => {
  261. await waitFor(() => {
  262. expect(tree.list.length).toBe(3);
  263. });
  264. tree.expand(tree.list[2], true);
  265. assertAutogroupedNode(tree.list[2]);
  266. expect(tree.list[2].path).toEqual(['ag:2', 'txn:event_id']);
  267. });
  268. it('child is part of autogrouping', () => {
  269. expect(tree.list[tree.list.length - 1].path).toEqual([
  270. 'span:5',
  271. 'ag:2',
  272. 'txn:event_id',
  273. ]);
  274. });
  275. });
  276. describe('non expanded direct children autogrouped path', () => {
  277. const tree = TraceTree.FromTrace(
  278. makeTrace({
  279. transactions: [
  280. makeTransaction({
  281. transaction: '/',
  282. project_slug: 'project',
  283. event_id: 'event_id',
  284. }),
  285. ],
  286. })
  287. );
  288. MockApiClient.addMockResponse({
  289. url: '/organizations/org-slug/events/project:event_id/',
  290. method: 'GET',
  291. body: makeEvent({}, [
  292. makeSpan({description: 'span', op: 'db', span_id: '2'}),
  293. makeSpan({description: 'span', op: 'db', span_id: '3', parent_span_id: '2'}),
  294. makeSpan({description: 'span', op: 'db', span_id: '4', parent_span_id: '3'}),
  295. makeSpan({description: 'span', op: 'db', span_id: '5', parent_span_id: '4'}),
  296. makeSpan({description: 'span', op: '6', span_id: '6', parent_span_id: '5'}),
  297. ]),
  298. });
  299. tree.zoomIn(tree.list[1], true, {
  300. api: new MockApiClient(),
  301. organization: OrganizationFixture(),
  302. });
  303. it('autogrouped node', async () => {
  304. await waitFor(() => {
  305. expect(tree.list.length).toBe(4);
  306. });
  307. assertAutogroupedNode(tree.list[2]);
  308. expect(tree.list[2].path).toEqual(['ag:2', 'txn:event_id']);
  309. });
  310. it('span node skips autogrouped node because it is not expanded', async () => {
  311. await waitFor(() => {
  312. expect(tree.list.length).toBe(4);
  313. });
  314. expect(tree.list[tree.list.length - 1].path).toEqual(['span:6', 'txn:event_id']);
  315. });
  316. });
  317. it.todo('sibling autogrouped node paths');
  318. it.todo('nested transactions autogrouped node paths');
  319. });
  320. });
  321. describe('TraceTree', () => {
  322. beforeEach(() => {
  323. MockApiClient.clearMockResponses();
  324. });
  325. it('builds from transactions', () => {
  326. const tree = TraceTree.FromTrace(
  327. makeTrace({
  328. transactions: [
  329. makeTransaction({
  330. children: [],
  331. }),
  332. makeTransaction({
  333. children: [],
  334. }),
  335. ],
  336. })
  337. );
  338. expect(tree.list).toHaveLength(3);
  339. });
  340. it('builds orphan errors as well', () => {
  341. const tree = TraceTree.FromTrace(
  342. makeTrace({
  343. transactions: [
  344. makeTransaction({
  345. children: [],
  346. }),
  347. makeTransaction({
  348. children: [],
  349. }),
  350. ],
  351. orphan_errors: [makeTraceError()],
  352. })
  353. );
  354. expect(tree.list).toHaveLength(4);
  355. });
  356. it('calculates correct trace type', () => {
  357. let tree = TraceTree.FromTrace(
  358. makeTrace({
  359. transactions: [],
  360. orphan_errors: [],
  361. })
  362. );
  363. expect(TraceTree.GetTraceType(tree.root)).toBe(TraceType.EMPTY_TRACE);
  364. tree = TraceTree.FromTrace(
  365. makeTrace({
  366. transactions: [
  367. makeTransaction({
  368. children: [],
  369. }),
  370. makeTransaction({
  371. children: [],
  372. }),
  373. ],
  374. orphan_errors: [],
  375. })
  376. );
  377. expect(TraceTree.GetTraceType(tree.root)).toBe(TraceType.NO_ROOT);
  378. tree = TraceTree.FromTrace(
  379. makeTrace({
  380. transactions: [
  381. makeTransaction({
  382. parent_span_id: null,
  383. children: [],
  384. }),
  385. ],
  386. orphan_errors: [],
  387. })
  388. );
  389. expect(TraceTree.GetTraceType(tree.root)).toBe(TraceType.ONE_ROOT);
  390. tree = TraceTree.FromTrace(
  391. makeTrace({
  392. transactions: [
  393. makeTransaction({
  394. parent_span_id: null,
  395. children: [],
  396. }),
  397. makeTransaction({
  398. children: [],
  399. }),
  400. ],
  401. orphan_errors: [],
  402. })
  403. );
  404. expect(TraceTree.GetTraceType(tree.root)).toBe(TraceType.BROKEN_SUBTRACES);
  405. tree = TraceTree.FromTrace(
  406. makeTrace({
  407. transactions: [
  408. makeTransaction({
  409. parent_span_id: null,
  410. children: [],
  411. }),
  412. makeTransaction({
  413. parent_span_id: null,
  414. children: [],
  415. }),
  416. ],
  417. orphan_errors: [],
  418. })
  419. );
  420. expect(TraceTree.GetTraceType(tree.root)).toBe(TraceType.MULTIPLE_ROOTS);
  421. tree = TraceTree.FromTrace(
  422. makeTrace({
  423. transactions: [],
  424. orphan_errors: [makeTraceError()],
  425. })
  426. );
  427. expect(TraceTree.GetTraceType(tree.root)).toBe(TraceType.ONLY_ERRORS);
  428. });
  429. it('builds from spans when root is a transaction node', () => {
  430. const root = new TraceTreeNode(
  431. null,
  432. makeTransaction({
  433. children: [],
  434. }),
  435. {project_slug: '', event_id: ''}
  436. );
  437. const node = TraceTree.FromSpans(
  438. root,
  439. [
  440. makeSpan({start_timestamp: 0, op: '1', span_id: '1'}),
  441. makeSpan({start_timestamp: 1, op: '2', span_id: '2', parent_span_id: '1'}),
  442. makeSpan({start_timestamp: 2, op: '3', span_id: '3', parent_span_id: '2'}),
  443. makeSpan({start_timestamp: 3, op: '4', span_id: '4', parent_span_id: '1'}),
  444. ],
  445. {sdk: undefined}
  446. );
  447. if (!isSpanNode(node.children[0])) {
  448. throw new Error('Child needs to be a span');
  449. }
  450. expect(node.children[0].value.span_id).toBe('1');
  451. expect(node.children[0].value.start_timestamp).toBe(0);
  452. expect(node.children.length).toBe(1);
  453. assertSpanNode(node.children[0].children[0]);
  454. assertSpanNode(node.children[0].children[0].children[0]);
  455. assertSpanNode(node.children[0].children[1]);
  456. expect(node.children[0].children[0].value.start_timestamp).toBe(1);
  457. expect(node.children[0].children[0].children[0].value.start_timestamp).toBe(2);
  458. expect(node.children[0].children[1].value.start_timestamp).toBe(3);
  459. });
  460. it('builds from spans and copies txn nodes', () => {
  461. // transaction transaction
  462. // - child transaction -> - span
  463. // - span
  464. // - child-transaction
  465. // - span
  466. const root = new TraceTreeNode(
  467. null,
  468. makeTransaction({
  469. children: [],
  470. }),
  471. {project_slug: '', event_id: ''}
  472. );
  473. root.children.push(
  474. new TraceTreeNode(
  475. root,
  476. makeTransaction({
  477. parent_span_id: 'child-transaction',
  478. }),
  479. {project_slug: '', event_id: ''}
  480. )
  481. );
  482. const node = TraceTree.FromSpans(
  483. root,
  484. [
  485. makeSpan({start_timestamp: 0, timestamp: 0.1, op: 'span', span_id: 'none'}),
  486. makeSpan({
  487. start_timestamp: 0.1,
  488. timestamp: 0.2,
  489. op: 'child-transaction',
  490. span_id: 'child-transaction',
  491. }),
  492. makeSpan({start_timestamp: 0.2, timestamp: 0.25, op: 'span', span_id: 'none'}),
  493. ],
  494. {sdk: undefined}
  495. );
  496. assertSpanNode(node.children[1]);
  497. assertTransactionNode(node.children[1].children[0]);
  498. });
  499. it('builds from spans and copies txn nodes to nested children', () => {
  500. // parent transaction parent transaction
  501. // - child transaction -> - span
  502. // - grandchild transaction -> - child-transaction
  503. // - grandchild-transaction
  504. //
  505. const root = new TraceTreeNode(
  506. null,
  507. makeTransaction({
  508. span_id: 'parent-transaction',
  509. children: [],
  510. }),
  511. {project_slug: '', event_id: ''}
  512. );
  513. let start: TraceTreeNode<TraceTree.NodeValue> = root;
  514. for (let i = 0; i < 2; i++) {
  515. const node = new TraceTreeNode(
  516. start,
  517. makeTransaction({
  518. transaction: `${i === 0 ? 'child' : 'grandchild'}-transaction`,
  519. parent_span_id: `${i === 0 ? 'child' : 'grandchild'}-transaction`,
  520. }),
  521. {project_slug: '', event_id: ''}
  522. );
  523. start.children.push(node);
  524. start = node;
  525. }
  526. const node = TraceTree.FromSpans(
  527. root,
  528. [
  529. makeSpan({start_timestamp: 0, timestamp: 0.1, op: 'span', span_id: 'none'}),
  530. makeSpan({
  531. start_timestamp: 0.1,
  532. timestamp: 0.2,
  533. op: 'child-transaction',
  534. span_id: 'child-transaction',
  535. }),
  536. ],
  537. {sdk: undefined}
  538. );
  539. assertSpanNode(node.children[1]);
  540. assertTransactionNode(node.children[1].children[0]);
  541. assertTransactionNode(node.children[1].children[0].children[0]);
  542. });
  543. it('injects missing spans', () => {
  544. const root = new TraceTreeNode(
  545. null,
  546. makeTransaction({
  547. children: [],
  548. }),
  549. {project_slug: '', event_id: ''}
  550. );
  551. const date = new Date().getTime();
  552. const node = TraceTree.FromSpans(
  553. root,
  554. [
  555. makeSpan({
  556. start_timestamp: date,
  557. timestamp: date + 1,
  558. span_id: '1',
  559. op: 'span 1',
  560. }),
  561. makeSpan({
  562. start_timestamp: date + 2,
  563. timestamp: date + 4,
  564. op: 'span 2',
  565. span_id: '2',
  566. }),
  567. ],
  568. {sdk: undefined}
  569. );
  570. assertSpanNode(node.children[0]);
  571. assertMissingInstrumentationNode(node.children[1]);
  572. assertSpanNode(node.children[2]);
  573. expect(node.children.length).toBe(3);
  574. expect(node.children[0].value.op).toBe('span 1');
  575. expect(node.children[1].value.type).toBe('missing_instrumentation');
  576. expect(node.children[2].value.op).toBe('span 2');
  577. });
  578. it('does not inject missing spans for javascript platform', () => {
  579. const root = new TraceTreeNode(
  580. null,
  581. makeTransaction({
  582. children: [],
  583. }),
  584. {project_slug: '', event_id: ''}
  585. );
  586. const date = new Date().getTime();
  587. const node = TraceTree.FromSpans(
  588. root,
  589. [
  590. makeSpan({
  591. start_timestamp: date,
  592. timestamp: date + 1,
  593. span_id: '1',
  594. op: 'span 1',
  595. }),
  596. makeSpan({
  597. start_timestamp: date + 2,
  598. timestamp: date + 4,
  599. op: 'span 2',
  600. span_id: '2',
  601. }),
  602. ],
  603. {sdk: 'sentry.javascript.browser'}
  604. );
  605. assertSpanNode(node.children[0]);
  606. assertSpanNode(node.children[1]);
  607. expect(node.children.length).toBe(2);
  608. expect(node.children[0].value.op).toBe('span 1');
  609. expect(node.children[1].value.op).toBe('span 2');
  610. });
  611. it('builds and preserves list order', async () => {
  612. const organization = OrganizationFixture();
  613. const api = new MockApiClient();
  614. const tree = TraceTree.FromTrace(
  615. makeTrace({
  616. transactions: [
  617. makeTransaction({
  618. transaction: 'txn 1',
  619. start_timestamp: 0,
  620. children: [makeTransaction({start_timestamp: 1, transaction: 'txn 2'})],
  621. }),
  622. ],
  623. })
  624. );
  625. tree.expand(tree.list[0], true);
  626. const node = tree.list[1];
  627. const request = MockApiClient.addMockResponse({
  628. url: '/organizations/org-slug/events/undefined:undefined/',
  629. method: 'GET',
  630. body: makeEvent({startTimestamp: 0}, [
  631. makeSpan({start_timestamp: 1, op: 'span 1', span_id: '1'}),
  632. makeSpan({
  633. start_timestamp: 2,
  634. op: 'span 2',
  635. span_id: '2',
  636. parent_span_id: '1',
  637. }),
  638. makeSpan({start_timestamp: 3, op: 'span 3', parent_span_id: '2'}),
  639. makeSpan({start_timestamp: 4, op: 'span 4', parent_span_id: '1'}),
  640. ]),
  641. });
  642. // 0
  643. // 1
  644. // 2
  645. // 3
  646. // 4
  647. tree.zoomIn(node, true, {api, organization});
  648. await waitFor(() => {
  649. expect(node.zoomedIn).toBe(true);
  650. });
  651. expect(request).toHaveBeenCalled();
  652. expect(tree.list.length).toBe(6);
  653. assertTransactionNode(tree.list[1]);
  654. assertSpanNode(tree.list[2]);
  655. assertSpanNode(tree.list[3]);
  656. expect(tree.list[1].value.start_timestamp).toBe(0);
  657. expect(tree.list[2].value.start_timestamp).toBe(1);
  658. expect(tree.list[3].value.start_timestamp).toBe(2);
  659. });
  660. it('preserves input order', () => {
  661. const firstChild = makeTransaction({
  662. start_timestamp: 0,
  663. timestamp: 1,
  664. children: [],
  665. });
  666. const secondChild = makeTransaction({
  667. start_timestamp: 1,
  668. timestamp: 2,
  669. children: [],
  670. });
  671. const tree = TraceTree.FromTrace(
  672. makeTrace({
  673. transactions: [
  674. makeTransaction({
  675. start_timestamp: 0,
  676. timestamp: 2,
  677. children: [firstChild, secondChild],
  678. }),
  679. makeTransaction({
  680. start_timestamp: 2,
  681. timestamp: 4,
  682. }),
  683. ],
  684. })
  685. );
  686. expect(tree.list).toHaveLength(5);
  687. expect(tree.expand(tree.list[1], false)).toBe(true);
  688. expect(tree.list).toHaveLength(3);
  689. expect(tree.expand(tree.list[1], true)).toBe(true);
  690. expect(tree.list).toHaveLength(5);
  691. expect(tree.list[2].value).toBe(firstChild);
  692. expect(tree.list[3].value).toBe(secondChild);
  693. });
  694. it('creates children -> parent references', () => {
  695. const tree = TraceTree.FromTrace(
  696. makeTrace({
  697. transactions: [
  698. makeTransaction({
  699. start_timestamp: 0,
  700. timestamp: 2,
  701. children: [makeTransaction({start_timestamp: 1, timestamp: 2})],
  702. }),
  703. makeTransaction({
  704. start_timestamp: 2,
  705. timestamp: 4,
  706. }),
  707. ],
  708. })
  709. );
  710. expect(tree.list).toHaveLength(4);
  711. expect(tree.list[2].parent?.value).toBe(tree.list[1].value);
  712. });
  713. it('establishes parent-child relationships', () => {
  714. const tree = TraceTree.FromTrace(
  715. makeTrace({
  716. transactions: [
  717. makeTransaction({
  718. children: [makeTransaction()],
  719. }),
  720. ],
  721. })
  722. );
  723. expect(tree.root.children).toHaveLength(1);
  724. expect(tree.root.children[0].children).toHaveLength(1);
  725. });
  726. it('isLastChild', () => {
  727. const tree = TraceTree.FromTrace(
  728. makeTrace({
  729. transactions: [
  730. makeTransaction({
  731. children: [makeTransaction(), makeTransaction()],
  732. }),
  733. makeTransaction(),
  734. ],
  735. orphan_errors: [],
  736. })
  737. );
  738. tree.expand(tree.list[1], true);
  739. expect(tree.list[0].isLastChild).toBe(true);
  740. expect(tree.list[1].isLastChild).toBe(false);
  741. expect(tree.list[2].isLastChild).toBe(false);
  742. expect(tree.list[3].isLastChild).toBe(true);
  743. expect(tree.list[4].isLastChild).toBe(true);
  744. });
  745. describe('connectors', () => {
  746. it('computes transaction connectors', () => {
  747. const tree = TraceTree.FromTrace(
  748. makeTrace({
  749. transactions: [
  750. makeTransaction({
  751. transaction: 'sibling',
  752. children: [
  753. makeTransaction({transaction: 'child'}),
  754. makeTransaction({transaction: 'child'}),
  755. ],
  756. }),
  757. makeTransaction({transaction: 'sibling'}),
  758. ],
  759. })
  760. );
  761. // -1 root
  762. // ------ list begins here
  763. // 0 transaction
  764. // 0 |- sibling
  765. // -1, 2| | - child
  766. // -1| | - child
  767. // 0 |- sibling
  768. tree.expand(tree.list[1], true);
  769. expect(tree.list.length).toBe(5);
  770. expect(tree.list[0].connectors.length).toBe(0);
  771. expect(tree.list[1].connectors.length).toBe(1);
  772. expect(tree.list[1].connectors[0]).toBe(-1);
  773. expect(tree.list[2].connectors[0]).toBe(-1);
  774. expect(tree.list[2].connectors[1]).toBe(2);
  775. expect(tree.list[2].connectors.length).toBe(2);
  776. expect(tree.list[3].connectors[0]).toBe(-1);
  777. expect(tree.list[3].connectors.length).toBe(1);
  778. expect(tree.list[4].connectors.length).toBe(0);
  779. });
  780. it('computes span connectors', async () => {
  781. const tree = TraceTree.FromTrace(
  782. makeTrace({
  783. transactions: [
  784. makeTransaction({
  785. project_slug: 'project',
  786. event_id: 'event_id',
  787. transaction: 'transaction',
  788. children: [],
  789. }),
  790. ],
  791. })
  792. );
  793. // root
  794. // |- node1 []
  795. // |- node2 []
  796. MockApiClient.addMockResponse({
  797. url: '/organizations/org-slug/events/project:event_id/',
  798. method: 'GET',
  799. body: makeEvent({}, [makeSpan({start_timestamp: 0, op: 'span', span_id: '1'})]),
  800. });
  801. expect(tree.list.length).toBe(2);
  802. tree.zoomIn(tree.list[1], true, {
  803. api: new MockApiClient(),
  804. organization: OrganizationFixture(),
  805. });
  806. await waitFor(() => {
  807. expect(tree.list.length).toBe(3);
  808. });
  809. // root
  810. // |- node1 []
  811. // |- node2 []
  812. // |- span1 []
  813. const span = tree.list[tree.list.length - 1];
  814. expect(span.connectors.length).toBe(0);
  815. });
  816. });
  817. describe('expanding', () => {
  818. it('expands a node and updates the list', () => {
  819. const tree = TraceTree.FromTrace(
  820. makeTrace({transactions: [makeTransaction({children: [makeTransaction()]})]})
  821. );
  822. const node = tree.list[1];
  823. expect(tree.expand(node, false)).toBe(true);
  824. expect(tree.list.length).toBe(2);
  825. expect(node.expanded).toBe(false);
  826. expect(tree.expand(node, true)).toBe(true);
  827. expect(node.expanded).toBe(true);
  828. // Assert that the list has been updated
  829. expect(tree.list).toHaveLength(3);
  830. expect(tree.list[2]).toBe(node.children[0]);
  831. });
  832. it('collapses a node and updates the list', () => {
  833. const tree = TraceTree.FromTrace(
  834. makeTrace({transactions: [makeTransaction({children: [makeTransaction()]})]})
  835. );
  836. const node = tree.list[1];
  837. tree.expand(node, true);
  838. expect(tree.list.length).toBe(3);
  839. expect(tree.expand(node, false)).toBe(true);
  840. expect(node.expanded).toBe(false);
  841. // Assert that the list has been updated
  842. expect(tree.list).toHaveLength(2);
  843. expect(tree.list[1]).toBe(node);
  844. });
  845. it('preserves children expanded state', () => {
  846. const tree = TraceTree.FromTrace(
  847. makeTrace({
  848. transactions: [
  849. makeTransaction({
  850. children: [
  851. makeTransaction({children: [makeTransaction({start_timestamp: 1000})]}),
  852. makeTransaction({start_timestamp: 5}),
  853. ],
  854. }),
  855. ],
  856. })
  857. );
  858. expect(tree.expand(tree.list[2], false)).toBe(true);
  859. // Assert that the list has been updated
  860. expect(tree.list).toHaveLength(4);
  861. expect(tree.expand(tree.list[2], true)).toBe(true);
  862. expect(tree.list.length).toBe(5);
  863. expect(tree.list[tree.list.length - 1].value).toEqual(
  864. makeTransaction({start_timestamp: 5})
  865. );
  866. });
  867. it('expanding or collapsing a zoomed in node doesnt do anything', async () => {
  868. const organization = OrganizationFixture();
  869. const api = new MockApiClient();
  870. const tree = TraceTree.FromTrace(
  871. makeTrace({transactions: [makeTransaction({children: [makeTransaction()]})]})
  872. );
  873. const node = tree.list[0];
  874. const request = MockApiClient.addMockResponse({
  875. url: '/organizations/org-slug/events/undefined:undefined/',
  876. method: 'GET',
  877. body: makeEvent(),
  878. });
  879. tree.zoomIn(node, true, {api, organization});
  880. await waitFor(() => {
  881. expect(node.zoomedIn).toBe(true);
  882. });
  883. expect(request).toHaveBeenCalled();
  884. expect(tree.expand(node, true)).toBe(false);
  885. });
  886. });
  887. describe('zooming', () => {
  888. it('marks node as zoomed in', async () => {
  889. const organization = OrganizationFixture();
  890. const api = new MockApiClient();
  891. const tree = TraceTree.FromTrace(
  892. makeTrace({
  893. transactions: [
  894. makeTransaction({project_slug: 'project', event_id: 'event_id'}),
  895. ],
  896. })
  897. );
  898. const request = MockApiClient.addMockResponse({
  899. url: '/organizations/org-slug/events/project:event_id/',
  900. method: 'GET',
  901. body: makeEvent(),
  902. });
  903. const node = tree.list[1];
  904. expect(node.zoomedIn).toBe(false);
  905. tree.zoomIn(node, true, {api, organization});
  906. await waitFor(() => {
  907. expect(node.zoomedIn).toBe(true);
  908. });
  909. expect(request).toHaveBeenCalled();
  910. });
  911. it('fetches spans for node when zooming in', async () => {
  912. const tree = TraceTree.FromTrace(
  913. makeTrace({
  914. transactions: [
  915. makeTransaction({
  916. transaction: 'txn',
  917. project_slug: 'project',
  918. event_id: 'event_id',
  919. }),
  920. ],
  921. })
  922. );
  923. const request = MockApiClient.addMockResponse({
  924. url: '/organizations/org-slug/events/project:event_id/',
  925. method: 'GET',
  926. body: makeEvent({}, [makeSpan()]),
  927. });
  928. const node = tree.list[1];
  929. expect(node.children).toHaveLength(0);
  930. tree.zoomIn(node, true, {
  931. api: new MockApiClient(),
  932. organization: OrganizationFixture(),
  933. });
  934. expect(request).toHaveBeenCalled();
  935. await waitFor(() => {
  936. expect(node.children).toHaveLength(1);
  937. });
  938. // Assert that the children have been updated
  939. assertTransactionNode(node.children[0].parent);
  940. expect(node.children[0].parent.value.transaction).toBe('txn');
  941. expect(node.children[0].depth).toBe(node.depth + 1);
  942. });
  943. it('zooms out', async () => {
  944. const tree = TraceTree.FromTrace(
  945. makeTrace({
  946. transactions: [
  947. makeTransaction({project_slug: 'project', event_id: 'event_id'}),
  948. ],
  949. })
  950. );
  951. MockApiClient.addMockResponse({
  952. url: '/organizations/org-slug/events/project:event_id/',
  953. method: 'GET',
  954. body: makeEvent({}, [makeSpan({span_id: 'span1', description: 'span1'})]),
  955. });
  956. tree.zoomIn(tree.list[1], true, {
  957. api: new MockApiClient(),
  958. organization: OrganizationFixture(),
  959. });
  960. await waitFor(() => {
  961. assertSpanNode(tree.list[1].children[0]);
  962. expect(tree.list[1].children[0].value.description).toBe('span1');
  963. });
  964. tree.zoomIn(tree.list[1], false, {
  965. api: new MockApiClient(),
  966. organization: OrganizationFixture(),
  967. });
  968. await waitFor(() => {
  969. // Assert child no longer points to children
  970. expect(tree.list[1].zoomedIn).toBe(false);
  971. expect(tree.list[1].children[0]).toBe(undefined);
  972. expect(tree.list[2]).toBe(undefined);
  973. });
  974. });
  975. it('zooms in and out', async () => {
  976. const tree = TraceTree.FromTrace(
  977. makeTrace({
  978. transactions: [
  979. makeTransaction({project_slug: 'project', event_id: 'event_id'}),
  980. ],
  981. })
  982. );
  983. MockApiClient.addMockResponse({
  984. url: '/organizations/org-slug/events/project:event_id/',
  985. method: 'GET',
  986. body: makeEvent({}, [makeSpan({span_id: 'span 1', description: 'span1'})]),
  987. });
  988. // Zoom in
  989. tree.zoomIn(tree.list[1], true, {
  990. api: new MockApiClient(),
  991. organization: OrganizationFixture(),
  992. });
  993. await waitFor(() => {
  994. expect(tree.list[1].zoomedIn).toBe(true);
  995. assertSpanNode(tree.list[1].children[0]);
  996. expect(tree.list[1].children[0].value.description).toBe('span1');
  997. });
  998. // Zoom out
  999. tree.zoomIn(tree.list[1], false, {
  1000. api: new MockApiClient(),
  1001. organization: OrganizationFixture(),
  1002. });
  1003. await waitFor(() => {
  1004. expect(tree.list[2]).toBe(undefined);
  1005. });
  1006. // Zoom in
  1007. tree.zoomIn(tree.list[1], true, {
  1008. api: new MockApiClient(),
  1009. organization: OrganizationFixture(),
  1010. });
  1011. await waitFor(() => {
  1012. assertSpanNode(tree.list[1].children[0]);
  1013. expect(tree.list[1].children[0].value?.description).toBe('span1');
  1014. });
  1015. });
  1016. it('zooms in and out preserving siblings', async () => {
  1017. const tree = TraceTree.FromTrace(
  1018. makeTrace({
  1019. transactions: [
  1020. makeTransaction({
  1021. project_slug: 'project',
  1022. event_id: 'event_id',
  1023. start_timestamp: 0,
  1024. children: [
  1025. makeTransaction({
  1026. start_timestamp: 1,
  1027. timestamp: 2,
  1028. project_slug: 'other_project',
  1029. event_id: 'event_id',
  1030. }),
  1031. makeTransaction({start_timestamp: 2, timestamp: 3}),
  1032. ],
  1033. }),
  1034. ],
  1035. })
  1036. );
  1037. const request = MockApiClient.addMockResponse({
  1038. url: '/organizations/org-slug/events/other_project:event_id/',
  1039. method: 'GET',
  1040. body: makeEvent({}, [makeSpan({description: 'span1'})]),
  1041. });
  1042. tree.expand(tree.list[1], true);
  1043. tree.zoomIn(tree.list[2], true, {
  1044. api: new MockApiClient(),
  1045. organization: OrganizationFixture(),
  1046. });
  1047. expect(request).toHaveBeenCalled();
  1048. // Zoom in
  1049. await waitFor(() => {
  1050. expect(tree.list.length).toBe(5);
  1051. });
  1052. // Zoom out
  1053. tree.zoomIn(tree.list[2], false, {
  1054. api: new MockApiClient(),
  1055. organization: OrganizationFixture(),
  1056. });
  1057. await waitFor(() => {
  1058. expect(tree.list.length).toBe(4);
  1059. });
  1060. });
  1061. it('preserves expanded state when zooming in and out', async () => {
  1062. const tree = TraceTree.FromTrace(
  1063. makeTrace({
  1064. transactions: [
  1065. makeTransaction({
  1066. project_slug: 'project',
  1067. event_id: 'event_id',
  1068. children: [
  1069. makeTransaction({project_slug: 'other_project', event_id: 'event_id'}),
  1070. ],
  1071. }),
  1072. ],
  1073. })
  1074. );
  1075. MockApiClient.addMockResponse({
  1076. url: '/organizations/org-slug/events/project:event_id/',
  1077. method: 'GET',
  1078. body: makeEvent({}, [
  1079. makeSpan({description: 'span1'}),
  1080. makeSpan({description: 'span2'}),
  1081. ]),
  1082. });
  1083. tree.expand(tree.list[1], true);
  1084. expect(tree.list.length).toBe(3);
  1085. tree.zoomIn(tree.list[1], true, {
  1086. api: new MockApiClient(),
  1087. organization: OrganizationFixture(),
  1088. });
  1089. await waitFor(() => {
  1090. expect(tree.list.length).toBe(4);
  1091. });
  1092. tree.zoomIn(tree.list[1], false, {
  1093. api: new MockApiClient(),
  1094. organization: OrganizationFixture(),
  1095. });
  1096. await waitFor(() => {
  1097. expect(tree.list.length).toBe(3);
  1098. });
  1099. expect(tree.list[1].expanded).toBe(true);
  1100. });
  1101. });
  1102. describe('autogrouping', () => {
  1103. it('auto groups sibling spans and preserves tail spans', () => {
  1104. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  1105. project_slug: '',
  1106. event_id: '',
  1107. });
  1108. for (let i = 0; i < 5; i++) {
  1109. root.children.push(
  1110. new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
  1111. project_slug: '',
  1112. event_id: '',
  1113. })
  1114. );
  1115. }
  1116. root.children.push(
  1117. new TraceTreeNode(root, makeSpan({description: 'span', op: 'http'}), {
  1118. project_slug: '',
  1119. event_id: '',
  1120. })
  1121. );
  1122. expect(root.children.length).toBe(6);
  1123. TraceTree.AutogroupSiblingSpanNodes(root);
  1124. expect(root.children.length).toBe(2);
  1125. });
  1126. it('autogroups when number of children is exactly 5', () => {
  1127. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  1128. project_slug: '',
  1129. event_id: '',
  1130. });
  1131. for (let i = 0; i < 5; i++) {
  1132. root.children.push(
  1133. new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
  1134. project_slug: '',
  1135. event_id: '',
  1136. })
  1137. );
  1138. }
  1139. expect(root.children.length).toBe(5);
  1140. TraceTree.AutogroupSiblingSpanNodes(root);
  1141. expect(root.children.length).toBe(1);
  1142. });
  1143. it('adds autogrouped siblings as children under autogrouped node', () => {
  1144. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  1145. project_slug: '',
  1146. event_id: '',
  1147. });
  1148. for (let i = 0; i < 5; i++) {
  1149. root.children.push(
  1150. new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
  1151. project_slug: '',
  1152. event_id: '',
  1153. })
  1154. );
  1155. }
  1156. expect(root.children.length).toBe(5);
  1157. TraceTree.AutogroupSiblingSpanNodes(root);
  1158. expect(root.children.length).toBe(1);
  1159. const autoGroupedNode = root.children[0];
  1160. assertAutogroupedNode(autoGroupedNode);
  1161. expect(autoGroupedNode.groupCount).toBe(5);
  1162. expect(autoGroupedNode.children.length).toBe(5);
  1163. });
  1164. it('autogroups when number of children is > 5', () => {
  1165. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  1166. project_slug: '',
  1167. event_id: '',
  1168. });
  1169. for (let i = 0; i < 7; i++) {
  1170. root.children.push(
  1171. new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
  1172. project_slug: '',
  1173. event_id: '',
  1174. })
  1175. );
  1176. }
  1177. expect(root.children.length).toBe(7);
  1178. TraceTree.AutogroupSiblingSpanNodes(root);
  1179. expect(root.children.length).toBe(1);
  1180. });
  1181. it('autogroups direct children case', () => {
  1182. // db db db
  1183. // http -> parent autogroup (3) -> parent autogroup (3)
  1184. // http http
  1185. // http http
  1186. // http
  1187. const root: TraceTreeNode<TraceTree.Span> = new TraceTreeNode(
  1188. null,
  1189. makeSpan({
  1190. description: `span1`,
  1191. span_id: `1`,
  1192. op: 'db',
  1193. }),
  1194. {project_slug: '', event_id: ''}
  1195. );
  1196. let last: TraceTreeNode<any> = root;
  1197. for (let i = 0; i < 3; i++) {
  1198. const node = new TraceTreeNode(
  1199. last,
  1200. makeSpan({
  1201. description: `span${i}`,
  1202. span_id: `${i}`,
  1203. op: 'http',
  1204. }),
  1205. {
  1206. project_slug: '',
  1207. event_id: '',
  1208. }
  1209. );
  1210. last.children.push(node);
  1211. last = node;
  1212. }
  1213. if (!root) {
  1214. throw new Error('root is null');
  1215. }
  1216. expect(root.children.length).toBe(1);
  1217. expect(root.children[0].children.length).toBe(1);
  1218. TraceTree.AutogroupDirectChildrenSpanNodes(root);
  1219. expect(root.children.length).toBe(1);
  1220. assertAutogroupedNode(root.children[0]);
  1221. expect(root.children[0].children.length).toBe(0);
  1222. root.children[0].expanded = true;
  1223. expect((root.children[0].children[0].value as RawSpanType).description).toBe(
  1224. 'span0'
  1225. );
  1226. });
  1227. it('autogrouping direct children skips rendering intermediary nodes', () => {
  1228. // db db db
  1229. // http autogrouped (3) autogrouped (3)
  1230. // http -> db -> http
  1231. // http http
  1232. // db http
  1233. // db
  1234. const root = new TraceTreeNode(
  1235. null,
  1236. makeSpan({span_id: 'span1', description: 'span1', op: 'db'}),
  1237. {
  1238. project_slug: '',
  1239. event_id: '',
  1240. }
  1241. );
  1242. let last = root;
  1243. for (let i = 0; i < 4; i++) {
  1244. const node = new TraceTreeNode(
  1245. last,
  1246. makeSpan({
  1247. span_id: `span`,
  1248. description: `span`,
  1249. op: i === 3 ? 'db' : 'http',
  1250. }),
  1251. {
  1252. project_slug: '',
  1253. event_id: '',
  1254. }
  1255. );
  1256. last.children.push(node);
  1257. last = node;
  1258. }
  1259. TraceTree.AutogroupDirectChildrenSpanNodes(root);
  1260. const autoGroupedNode = root.children[0];
  1261. assertAutogroupedNode(autoGroupedNode);
  1262. expect(autoGroupedNode.children.length).toBe(1);
  1263. expect((autoGroupedNode.children[0].value as RawSpanType).op).toBe('db');
  1264. autoGroupedNode.expanded = true;
  1265. expect(autoGroupedNode.children.length).toBe(1);
  1266. expect((autoGroupedNode.children[0].value as RawSpanType).op).toBe('http');
  1267. });
  1268. it('nested direct autogrouping', () => {
  1269. // db db db
  1270. // http -> parent autogroup (3) -> parent autogroup (3)
  1271. // http db http
  1272. // http parent autogroup (3) http
  1273. // db http
  1274. // http db
  1275. // http parent autogrouped (3)
  1276. // http http
  1277. // http
  1278. // http
  1279. const root = new TraceTreeNode(
  1280. null,
  1281. makeSpan({span_id: 'span', description: 'span', op: 'db'}),
  1282. {
  1283. project_slug: '',
  1284. event_id: '',
  1285. }
  1286. );
  1287. let last = root;
  1288. for (let i = 0; i < 3; i++) {
  1289. if (i === 1) {
  1290. const autogroupBreakingSpan = new TraceTreeNode(
  1291. last,
  1292. makeSpan({span_id: 'span', description: 'span', op: 'db'}),
  1293. {
  1294. project_slug: '',
  1295. event_id: '',
  1296. }
  1297. );
  1298. last.children.push(autogroupBreakingSpan);
  1299. last = autogroupBreakingSpan;
  1300. } else {
  1301. for (let j = 0; j < 3; j++) {
  1302. const node = new TraceTreeNode(
  1303. last,
  1304. makeSpan({span_id: `span${j}`, description: `span${j}`, op: 'http'}),
  1305. {
  1306. project_slug: '',
  1307. event_id: '',
  1308. }
  1309. );
  1310. last.children.push(node);
  1311. last = node;
  1312. }
  1313. }
  1314. }
  1315. TraceTree.AutogroupDirectChildrenSpanNodes(root);
  1316. assertAutogroupedNode(root.children[0]);
  1317. assertAutogroupedNode(root.children[0].children[0].children[0]);
  1318. });
  1319. it('sibling autogrouping', () => {
  1320. // db db
  1321. // http sibling autogrouped (5)
  1322. // http
  1323. // http ->
  1324. // http
  1325. // http
  1326. const root = new TraceTreeNode(
  1327. null,
  1328. makeTransaction({start_timestamp: 0, timestamp: 10}),
  1329. {
  1330. project_slug: '',
  1331. event_id: '',
  1332. }
  1333. );
  1334. for (let i = 0; i < 5; i++) {
  1335. root.children.push(
  1336. new TraceTreeNode(root, makeSpan({start_timestamp: i, timestamp: i + 1}), {
  1337. project_slug: '',
  1338. event_id: '',
  1339. })
  1340. );
  1341. }
  1342. TraceTree.AutogroupSiblingSpanNodes(root);
  1343. expect(root.children.length).toBe(1);
  1344. assertAutogroupedNode(root.children[0]);
  1345. });
  1346. it('multiple sibling autogrouping', () => {
  1347. // db db
  1348. // http sibling autogrouped (5)
  1349. // http db
  1350. // http -> sibling autogrouped (5)
  1351. // http
  1352. // http
  1353. // db
  1354. // http
  1355. // http
  1356. // http
  1357. // http
  1358. // http
  1359. const root = new TraceTreeNode(
  1360. null,
  1361. makeTransaction({start_timestamp: 0, timestamp: 10}),
  1362. {
  1363. project_slug: '',
  1364. event_id: '',
  1365. }
  1366. );
  1367. for (let i = 0; i < 10; i++) {
  1368. if (i === 5) {
  1369. root.children.push(
  1370. new TraceTreeNode(
  1371. root,
  1372. makeSpan({start_timestamp: i, timestamp: i + 1, op: 'db'}),
  1373. {
  1374. project_slug: '',
  1375. event_id: '',
  1376. }
  1377. )
  1378. );
  1379. }
  1380. root.children.push(
  1381. new TraceTreeNode(
  1382. root,
  1383. makeSpan({start_timestamp: i, timestamp: i + 1, op: 'http'}),
  1384. {
  1385. project_slug: '',
  1386. event_id: '',
  1387. }
  1388. )
  1389. );
  1390. }
  1391. TraceTree.AutogroupSiblingSpanNodes(root);
  1392. assertAutogroupedNode(root.children[0]);
  1393. expect(root.children).toHaveLength(3);
  1394. assertAutogroupedNode(root.children[2]);
  1395. });
  1396. it('renders children of autogrouped direct children nodes', async () => {
  1397. const tree = TraceTree.FromTrace(
  1398. makeTrace({
  1399. transactions: [
  1400. makeTransaction({
  1401. transaction: '/',
  1402. project_slug: 'project',
  1403. event_id: 'event_id',
  1404. }),
  1405. ],
  1406. })
  1407. );
  1408. MockApiClient.addMockResponse({
  1409. url: '/organizations/org-slug/events/project:event_id/',
  1410. method: 'GET',
  1411. body: makeEvent({}, [
  1412. makeSpan({description: 'parent span', op: 'http', span_id: '1'}),
  1413. makeSpan({description: 'span', op: 'db', span_id: '2', parent_span_id: '1'}),
  1414. makeSpan({description: 'span', op: 'db', span_id: '3', parent_span_id: '2'}),
  1415. makeSpan({description: 'span', op: 'db', span_id: '4', parent_span_id: '3'}),
  1416. makeSpan({description: 'span', op: 'db', span_id: '5', parent_span_id: '4'}),
  1417. makeSpan({
  1418. description: 'span',
  1419. op: 'redis',
  1420. span_id: '6',
  1421. parent_span_id: '5',
  1422. }),
  1423. makeSpan({description: 'span', op: 'https', parent_span_id: '1'}),
  1424. ]),
  1425. });
  1426. expect(tree.list.length).toBe(2);
  1427. tree.zoomIn(tree.list[1], true, {
  1428. api: new MockApiClient(),
  1429. organization: OrganizationFixture(),
  1430. });
  1431. await waitFor(() => {
  1432. expect(tree.list.length).toBe(6);
  1433. });
  1434. const autogroupedNode = tree.list[tree.list.length - 3];
  1435. assertParentAutogroupedNode(autogroupedNode);
  1436. expect('autogrouped_by' in autogroupedNode?.value).toBeTruthy();
  1437. expect(autogroupedNode.groupCount).toBe(4);
  1438. expect(autogroupedNode.head.value.span_id).toBe('2');
  1439. expect(autogroupedNode.tail.value.span_id).toBe('5');
  1440. // Expand autogrouped node
  1441. expect(tree.expand(autogroupedNode, true)).toBe(true);
  1442. expect(tree.list.length).toBe(10);
  1443. // Collapse autogrouped node
  1444. expect(tree.expand(autogroupedNode, false)).toBe(true);
  1445. expect(tree.list.length).toBe(6);
  1446. expect(autogroupedNode.children[0].depth).toBe(4);
  1447. });
  1448. });
  1449. });