traceTree.spec.tsx 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272
  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, type EventTransaction} from 'sentry/types';
  5. import type {
  6. TraceFullDetailed,
  7. TracePerformanceIssue,
  8. TraceSplitResults,
  9. } from 'sentry/utils/performance/quickTrace/types';
  10. import {TraceType} from '../traceDetails/newTraceDetailsContent';
  11. import {
  12. isAutogroupedNode,
  13. isMissingInstrumentationNode,
  14. isSpanNode,
  15. isTraceErrorNode,
  16. isTransactionNode,
  17. } from './guards';
  18. import {
  19. ParentAutogroupNode,
  20. SiblingAutogroupNode,
  21. TraceTree,
  22. TraceTreeNode,
  23. } from './traceTree';
  24. function makeTrace(
  25. overrides: Partial<TraceSplitResults<TraceFullDetailed>>
  26. ): TraceSplitResults<TraceFullDetailed> {
  27. return {
  28. transactions: [],
  29. orphan_errors: [],
  30. ...overrides,
  31. } as TraceSplitResults<TraceFullDetailed>;
  32. }
  33. function makeTransaction(overrides: Partial<TraceFullDetailed> = {}): TraceFullDetailed {
  34. return {
  35. children: [],
  36. start_timestamp: 0,
  37. timestamp: 1,
  38. transaction: 'transaction',
  39. 'transaction.op': '',
  40. 'transaction.status': '',
  41. performance_issues: [],
  42. errors: [],
  43. ...overrides,
  44. } as TraceFullDetailed;
  45. }
  46. function makeSpan(overrides: Partial<RawSpanType> = {}): TraceTree.Span {
  47. return {
  48. op: '',
  49. description: '',
  50. span_id: '',
  51. start_timestamp: 0,
  52. timestamp: 10,
  53. event: makeEvent(),
  54. errors: [],
  55. performance_issues: [],
  56. childTransaction: undefined,
  57. ...overrides,
  58. } as TraceTree.Span;
  59. }
  60. function makeTraceError(
  61. overrides: Partial<TraceTree.TraceError> = {}
  62. ): TraceTree.TraceError {
  63. return {
  64. title: 'MaybeEncodingError: Error sending result',
  65. level: 'error',
  66. event_type: 'error',
  67. data: {},
  68. ...overrides,
  69. } as TraceTree.TraceError;
  70. }
  71. function makeTracePerformanceIssue(
  72. overrides: Partial<TracePerformanceIssue> = {}
  73. ): TracePerformanceIssue {
  74. return {
  75. culprit: 'code',
  76. end: new Date().toISOString(),
  77. span: [],
  78. start: new Date().toISOString(),
  79. suspect_spans: ['sus span'],
  80. type: 0,
  81. issue_short_id: 'issue short id',
  82. ...overrides,
  83. } as TracePerformanceIssue;
  84. }
  85. function makeEvent(overrides: Partial<Event> = {}, spans: RawSpanType[] = []): Event {
  86. return {
  87. entries: [{type: EntryType.SPANS, data: spans}],
  88. ...overrides,
  89. } as Event;
  90. }
  91. function assertSpanNode(
  92. node: TraceTreeNode<TraceTree.NodeValue>
  93. ): asserts node is TraceTreeNode<TraceTree.Span> {
  94. if (!isSpanNode(node)) {
  95. throw new Error('node is not a span');
  96. }
  97. }
  98. // function assertTraceNode(
  99. // node: TraceTreeNode<TraceTree.NodeValue>
  100. // ): asserts node is TraceTreeNode<TraceTree.Trace> {
  101. // if (!isTraceNode(node)) {
  102. // throw new Error('node is not a trace');
  103. // }
  104. // }
  105. function assertTransactionNode(
  106. node: TraceTreeNode<TraceTree.NodeValue> | null
  107. ): asserts node is TraceTreeNode<TraceTree.Transaction> {
  108. if (!node || !isTransactionNode(node)) {
  109. throw new Error('node is not a transaction');
  110. }
  111. }
  112. function assertMissingInstrumentationNode(
  113. node: TraceTreeNode<TraceTree.NodeValue>
  114. ): asserts node is TraceTreeNode<TraceTree.MissingInstrumentationSpan> {
  115. if (!isMissingInstrumentationNode(node)) {
  116. throw new Error('node is not a missing instrumentation node');
  117. }
  118. }
  119. function assertTraceErrorNode(
  120. node: TraceTreeNode<TraceTree.NodeValue>
  121. ): asserts node is TraceTreeNode<TraceTree.TraceError> {
  122. if (!isTraceErrorNode(node)) {
  123. throw new Error('node is not a trace error node');
  124. }
  125. }
  126. function assertAutogroupedNode(
  127. node: TraceTreeNode<TraceTree.NodeValue>
  128. ): asserts node is ParentAutogroupNode | SiblingAutogroupNode {
  129. if (!isAutogroupedNode(node)) {
  130. throw new Error('node is not a autogrouped node');
  131. }
  132. }
  133. function assertParentAutogroupedNode(
  134. node: TraceTreeNode<TraceTree.NodeValue>
  135. ): asserts node is ParentAutogroupNode {
  136. if (!(node instanceof ParentAutogroupNode)) {
  137. throw new Error('node is not a parent autogrouped node');
  138. }
  139. }
  140. function assertSiblingAutogroupedNode(
  141. node: TraceTreeNode<TraceTree.NodeValue>
  142. ): asserts node is ParentAutogroupNode {
  143. if (!(node instanceof SiblingAutogroupNode)) {
  144. throw new Error('node is not a parent node');
  145. }
  146. }
  147. describe('TreeNode', () => {
  148. it('expands transaction nodes by default', () => {
  149. const node = new TraceTreeNode(null, makeTransaction(), {
  150. project_slug: '',
  151. event_id: '',
  152. });
  153. expect(node.expanded).toBe(true);
  154. });
  155. it('points parent to node', () => {
  156. const root = new TraceTreeNode(null, makeTransaction(), {
  157. project_slug: '',
  158. event_id: '',
  159. });
  160. const child = new TraceTreeNode(root, makeTransaction(), {
  161. project_slug: '',
  162. event_id: '',
  163. });
  164. expect(child.parent).toBe(root);
  165. });
  166. it('depth', () => {
  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. const grandChild = new TraceTreeNode(child, makeTransaction(), {
  176. project_slug: '',
  177. event_id: '',
  178. });
  179. expect(grandChild.depth).toBe(1);
  180. });
  181. it('getVisibleChildren', () => {
  182. const root = new TraceTreeNode(null, makeTransaction(), {
  183. project_slug: '',
  184. event_id: '',
  185. });
  186. const child = new TraceTreeNode(root, makeTransaction(), {
  187. project_slug: '',
  188. event_id: '',
  189. });
  190. root.children.push(child);
  191. expect(root.getVisibleChildren()).toHaveLength(1);
  192. expect(root.getVisibleChildren()[0]).toBe(child);
  193. root.expanded = false;
  194. expect(root.getVisibleChildren()).toHaveLength(0);
  195. });
  196. it('getVisibleChildrenCount', () => {
  197. const root = new TraceTreeNode(null, makeTransaction(), {
  198. project_slug: '',
  199. event_id: '',
  200. });
  201. const child = new TraceTreeNode(root, makeTransaction(), {
  202. project_slug: '',
  203. event_id: '',
  204. });
  205. root.children.push(child);
  206. expect(root.getVisibleChildrenCount()).toBe(1);
  207. root.expanded = false;
  208. expect(root.getVisibleChildrenCount()).toBe(0);
  209. });
  210. describe('indicators', () => {
  211. it('collects indicator', () => {
  212. const tree = TraceTree.FromTrace(
  213. makeTrace({
  214. transactions: [
  215. makeTransaction({
  216. start_timestamp: 0,
  217. timestamp: 1,
  218. }),
  219. ],
  220. }),
  221. {
  222. measurements: {ttfb: {value: 0, unit: 'millisecond'}},
  223. } as unknown as EventTransaction
  224. );
  225. expect(tree.indicators.length).toBe(1);
  226. expect(tree.indicators[0].start).toBe(0);
  227. });
  228. it('converts timestamp to milliseconds', () => {
  229. const tree = TraceTree.FromTrace(
  230. makeTrace({
  231. transactions: [
  232. makeTransaction({
  233. start_timestamp: 0,
  234. timestamp: 1,
  235. }),
  236. ],
  237. }),
  238. {
  239. measurements: {
  240. ttfb: {value: 500, unit: 'millisecond'},
  241. fcp: {value: 0.5, unit: 'second'},
  242. lcp: {value: 500_000_000, unit: 'nanosecond'},
  243. },
  244. } as unknown as EventTransaction
  245. );
  246. expect(tree.indicators[0].start).toBe(500);
  247. expect(tree.indicators[1].start).toBe(500);
  248. expect(tree.indicators[2].start).toBe(500);
  249. });
  250. it('extends end timestamp to include measurement', () => {
  251. const tree = TraceTree.FromTrace(
  252. makeTrace({
  253. transactions: [
  254. makeTransaction({
  255. start_timestamp: 0,
  256. timestamp: 1,
  257. }),
  258. ],
  259. }),
  260. {
  261. measurements: {
  262. ttfb: {value: 2, unit: 'second'},
  263. },
  264. } as unknown as EventTransaction
  265. );
  266. expect(tree.root.space).toEqual([0, 2000]);
  267. });
  268. it('adjusts end and converst timestamp to ms', () => {
  269. const tree = TraceTree.FromTrace(
  270. makeTrace({
  271. transactions: [
  272. makeTransaction({
  273. start_timestamp: 0,
  274. timestamp: 1,
  275. }),
  276. ],
  277. }),
  278. {
  279. measurements: {
  280. ttfb: {value: 2000, unit: 'millisecond'},
  281. },
  282. } as unknown as EventTransaction
  283. );
  284. expect(tree.root.space).toEqual([0, 2000]);
  285. expect(tree.indicators[0].start).toBe(2000);
  286. });
  287. it('sorts measurements by start', () => {
  288. const tree = TraceTree.FromTrace(
  289. makeTrace({
  290. transactions: [
  291. makeTransaction({
  292. start_timestamp: 0,
  293. timestamp: 1,
  294. }),
  295. ],
  296. }),
  297. {
  298. measurements: {
  299. ttfb: {value: 2000, unit: 'millisecond'},
  300. lcp: {value: 1000, unit: 'millisecond'},
  301. },
  302. } as unknown as EventTransaction
  303. );
  304. expect(tree.indicators[0].start).toBe(1000);
  305. expect(tree.indicators[1].start).toBe(2000);
  306. });
  307. });
  308. describe('parent autogrouped node segments', () => {
  309. it('collapses durations', () => {
  310. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  311. project_slug: '',
  312. event_id: '',
  313. });
  314. let parent = root;
  315. for (let i = 0; i < 5; i++) {
  316. const node = new TraceTreeNode(
  317. parent,
  318. makeSpan({
  319. description: 'span',
  320. op: 'db',
  321. start_timestamp: i,
  322. timestamp: i + 1,
  323. span_id: i.toString(),
  324. parent_span_id: parent.value.span_id,
  325. }),
  326. {
  327. project_slug: '',
  328. event_id: '',
  329. }
  330. );
  331. parent.children.push(node);
  332. parent = node;
  333. }
  334. TraceTree.AutogroupDirectChildrenSpanNodes(root);
  335. const autogroupedNode = root.children[0];
  336. assertParentAutogroupedNode(autogroupedNode);
  337. expect(autogroupedNode.autogroupedSegments).toEqual([[0, 5000]]);
  338. });
  339. it('does not collapse durations when there is a gap', () => {
  340. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  341. project_slug: '',
  342. event_id: '',
  343. });
  344. let parent = root;
  345. const ts = [
  346. [0, 1],
  347. [1.5, 2],
  348. [2.5, 3],
  349. [3.5, 4],
  350. [4.5, 5],
  351. ];
  352. for (let i = 0; i < 5; i++) {
  353. const node = new TraceTreeNode(
  354. parent,
  355. makeSpan({
  356. description: 'span',
  357. op: 'db',
  358. start_timestamp: ts[i][0],
  359. timestamp: ts[i][1],
  360. span_id: i.toString(),
  361. parent_span_id: parent.value.span_id,
  362. }),
  363. {
  364. project_slug: '',
  365. event_id: '',
  366. }
  367. );
  368. parent.children.push(node);
  369. parent = node;
  370. }
  371. for (let i = 1; i < ts.length; i++) {
  372. ts[i][0] *= 1000;
  373. ts[i][1] = 0.5 * 1000;
  374. }
  375. ts[0][0] = 0;
  376. ts[0][1] = 1 * 1000;
  377. TraceTree.AutogroupDirectChildrenSpanNodes(root);
  378. const autogroupedNode = root.children[0];
  379. assertParentAutogroupedNode(autogroupedNode);
  380. expect(autogroupedNode.autogroupedSegments).toEqual(ts);
  381. });
  382. });
  383. describe('sibling autogrouped node segments', () => {
  384. it('collapses durations', () => {
  385. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  386. project_slug: '',
  387. event_id: '',
  388. });
  389. for (let i = 0; i < 5; i++) {
  390. root.children.push(
  391. new TraceTreeNode(
  392. root,
  393. makeSpan({
  394. description: 'span',
  395. op: 'db',
  396. start_timestamp: i,
  397. timestamp: i + 1,
  398. }),
  399. {
  400. project_slug: '',
  401. event_id: '',
  402. }
  403. )
  404. );
  405. }
  406. TraceTree.AutogroupSiblingSpanNodes(root);
  407. const autogroupedNode = root.children[0];
  408. assertAutogroupedNode(autogroupedNode);
  409. expect(autogroupedNode.autogroupedSegments).toEqual([[0, 5000]]);
  410. });
  411. it('does not collapse durations when there is a gap', () => {
  412. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  413. project_slug: '',
  414. event_id: '',
  415. });
  416. const ts = [
  417. [0, 1],
  418. [1.5, 2],
  419. [2.5, 3],
  420. [3.5, 4],
  421. [4.5, 5],
  422. ];
  423. for (let i = 0; i < 5; i++) {
  424. root.children.push(
  425. new TraceTreeNode(
  426. root,
  427. makeSpan({
  428. description: 'span',
  429. op: 'db',
  430. start_timestamp: ts[i][0],
  431. timestamp: ts[i][1],
  432. }),
  433. {
  434. project_slug: '',
  435. event_id: '',
  436. }
  437. )
  438. );
  439. }
  440. for (let i = 0; i < ts.length; i++) {
  441. ts[i][0] *= 1000;
  442. ts[i][1] = 0.5 * 1000;
  443. }
  444. ts[0][0] = 0;
  445. ts[0][1] = 1 * 1000;
  446. TraceTree.AutogroupSiblingSpanNodes(root);
  447. const autogroupedNode = root.children[0];
  448. assertAutogroupedNode(autogroupedNode);
  449. expect(autogroupedNode.autogroupedSegments).toEqual(ts);
  450. });
  451. });
  452. describe('path', () => {
  453. describe('nested transactions', () => {
  454. let child: any = null;
  455. for (let i = 0; i < 3; i++) {
  456. const node = new TraceTreeNode(
  457. child,
  458. makeTransaction({
  459. event_id: i === 0 ? 'parent' : i === 1 ? 'child' : 'grandchild',
  460. }),
  461. {
  462. project_slug: '',
  463. event_id: '',
  464. }
  465. );
  466. child = node;
  467. }
  468. it('first txn node', () => {
  469. expect(child.parent.parent.path).toEqual(['txn:parent']);
  470. });
  471. it('leafmost node', () => {
  472. expect(child.path).toEqual(['txn:grandchild', 'txn:child', 'txn:parent']);
  473. });
  474. });
  475. it('orphan errors', () => {
  476. const tree = TraceTree.FromTrace(
  477. makeTrace({
  478. transactions: [],
  479. orphan_errors: [makeTraceError({event_id: 'error_id'})],
  480. })
  481. );
  482. expect(tree.list[1].path).toEqual(['error:error_id']);
  483. });
  484. describe('spans', () => {
  485. const tree = TraceTree.FromTrace(
  486. makeTrace({
  487. transactions: [
  488. makeTransaction({
  489. transaction: '/',
  490. project_slug: 'project',
  491. event_id: 'event_id',
  492. }),
  493. ],
  494. })
  495. );
  496. MockApiClient.addMockResponse({
  497. url: '/organizations/org-slug/events/project:event_id/',
  498. method: 'GET',
  499. body: makeEvent({}, [
  500. makeSpan({
  501. description: 'span',
  502. op: 'db',
  503. span_id: 'span',
  504. start_timestamp: 0,
  505. timestamp: 1,
  506. }),
  507. makeSpan({
  508. description: 'span',
  509. op: 'db',
  510. span_id: 'span',
  511. start_timestamp: 1.5,
  512. timestamp: 2,
  513. }),
  514. ]),
  515. });
  516. tree.zoomIn(tree.list[1], true, {
  517. api: new MockApiClient(),
  518. organization: OrganizationFixture(),
  519. });
  520. it('when span is a child of a txn', async () => {
  521. await waitFor(() => {
  522. expect(tree.list.length).toBe(5);
  523. });
  524. expect(tree.list[tree.list.length - 1].path).toEqual([
  525. 'span:span',
  526. 'txn:event_id',
  527. ]);
  528. });
  529. it('missing instrumentation', () => {
  530. expect(tree.list[3].path).toEqual(['ms:span', 'txn:event_id']);
  531. });
  532. });
  533. describe('autogrouped children', () => {
  534. const tree = TraceTree.FromTrace(
  535. makeTrace({
  536. transactions: [
  537. makeTransaction({
  538. transaction: '/',
  539. project_slug: 'project',
  540. event_id: 'event_id',
  541. }),
  542. ],
  543. })
  544. );
  545. MockApiClient.addMockResponse({
  546. url: '/organizations/org-slug/events/project:event_id/',
  547. method: 'GET',
  548. body: makeEvent({}, [
  549. makeSpan({description: 'span', op: 'db', span_id: '2'}),
  550. makeSpan({description: 'span', op: 'db', span_id: '3', parent_span_id: '2'}),
  551. makeSpan({description: 'span', op: 'db', span_id: '4', parent_span_id: '3'}),
  552. makeSpan({description: 'span', op: 'db', span_id: '5', parent_span_id: '4'}),
  553. ]),
  554. });
  555. tree.zoomIn(tree.list[1], true, {
  556. api: new MockApiClient(),
  557. organization: OrganizationFixture(),
  558. });
  559. it('autogrouped node', async () => {
  560. await waitFor(() => {
  561. expect(tree.list.length).toBe(3);
  562. });
  563. tree.expand(tree.list[2], true);
  564. assertAutogroupedNode(tree.list[2]);
  565. expect(tree.list[2].path).toEqual(['ag:2', 'txn:event_id']);
  566. });
  567. it('child is part of autogrouping', () => {
  568. expect(tree.list[tree.list.length - 1].path).toEqual([
  569. 'span:5',
  570. 'ag:2',
  571. 'txn:event_id',
  572. ]);
  573. });
  574. });
  575. describe('collapses some node by default', () => {
  576. it('android okhttp', async () => {
  577. const tree = TraceTree.FromTrace(
  578. makeTrace({
  579. transactions: [
  580. makeTransaction({
  581. transaction: '/',
  582. project_slug: 'project',
  583. event_id: 'event_id',
  584. }),
  585. ],
  586. })
  587. );
  588. MockApiClient.addMockResponse({
  589. url: '/organizations/org-slug/events/project:event_id/',
  590. method: 'GET',
  591. body: makeEvent({}, [
  592. makeSpan({
  593. description: 'span',
  594. span_id: '2',
  595. op: 'http.client',
  596. origin: 'auto.http.okhttp',
  597. }),
  598. makeSpan({
  599. description: 'span',
  600. op: 'http.client.tls',
  601. span_id: '3',
  602. parent_span_id: '2',
  603. }),
  604. ]),
  605. });
  606. tree.zoomIn(tree.list[1], true, {
  607. api: new MockApiClient(),
  608. organization: OrganizationFixture(),
  609. });
  610. await waitFor(() => {
  611. // trace
  612. // - transaction
  613. // - http.client
  614. // ^ child of http.client is not visible
  615. expect(tree.list.length).toBe(3);
  616. });
  617. });
  618. });
  619. describe('non expanded direct children autogrouped path', () => {
  620. const tree = TraceTree.FromTrace(
  621. makeTrace({
  622. transactions: [
  623. makeTransaction({
  624. transaction: '/',
  625. project_slug: 'project',
  626. event_id: 'event_id',
  627. }),
  628. ],
  629. })
  630. );
  631. MockApiClient.addMockResponse({
  632. url: '/organizations/org-slug/events/project:event_id/',
  633. method: 'GET',
  634. body: makeEvent({}, [
  635. makeSpan({description: 'span', op: 'db', span_id: '2'}),
  636. makeSpan({description: 'span', op: 'db', span_id: '3', parent_span_id: '2'}),
  637. makeSpan({description: 'span', op: 'db', span_id: '4', parent_span_id: '3'}),
  638. makeSpan({description: 'span', op: 'db', span_id: '5', parent_span_id: '4'}),
  639. makeSpan({description: 'span', op: '6', span_id: '6', parent_span_id: '5'}),
  640. ]),
  641. });
  642. tree.zoomIn(tree.list[1], true, {
  643. api: new MockApiClient(),
  644. organization: OrganizationFixture(),
  645. });
  646. it('autogrouped node', async () => {
  647. await waitFor(() => {
  648. expect(tree.list.length).toBe(4);
  649. });
  650. assertAutogroupedNode(tree.list[2]);
  651. expect(tree.list[2].path).toEqual(['ag:2', 'txn:event_id']);
  652. });
  653. it('span node skips autogrouped node because it is not expanded', async () => {
  654. await waitFor(() => {
  655. expect(tree.list.length).toBe(4);
  656. });
  657. expect(tree.list[tree.list.length - 1].path).toEqual(['span:6', 'txn:event_id']);
  658. });
  659. });
  660. });
  661. });
  662. describe('TraceTree', () => {
  663. beforeEach(() => {
  664. MockApiClient.clearMockResponses();
  665. });
  666. it('builds from transactions', () => {
  667. const tree = TraceTree.FromTrace(
  668. makeTrace({
  669. transactions: [
  670. makeTransaction({
  671. children: [],
  672. }),
  673. makeTransaction({
  674. children: [],
  675. }),
  676. ],
  677. })
  678. );
  679. expect(tree.list).toHaveLength(3);
  680. });
  681. it('builds orphan errors', () => {
  682. const tree = TraceTree.FromTrace(
  683. makeTrace({
  684. transactions: [
  685. makeTransaction({
  686. children: [],
  687. }),
  688. makeTransaction({
  689. children: [],
  690. }),
  691. ],
  692. orphan_errors: [makeTraceError()],
  693. })
  694. );
  695. expect(tree.list).toHaveLength(4);
  696. });
  697. it('processes orphan errors without timestamps', () => {
  698. const tree = TraceTree.FromTrace(
  699. makeTrace({
  700. transactions: [
  701. makeTransaction({
  702. children: [],
  703. }),
  704. ],
  705. orphan_errors: [
  706. {
  707. level: 'error',
  708. title: 'Error',
  709. event_type: 'error',
  710. } as TraceTree.TraceError,
  711. ],
  712. })
  713. );
  714. expect(tree.list).toHaveLength(3);
  715. });
  716. it('sorts orphan errors', () => {
  717. const tree = TraceTree.FromTrace(
  718. makeTrace({
  719. transactions: [
  720. makeTransaction({
  721. start_timestamp: 0,
  722. timestamp: 1,
  723. }),
  724. makeTransaction({
  725. start_timestamp: 2,
  726. timestamp: 3,
  727. }),
  728. ],
  729. orphan_errors: [makeTraceError({timestamp: 1, level: 'error'})],
  730. })
  731. );
  732. expect(tree.list).toHaveLength(4);
  733. assertTraceErrorNode(tree.list[2]);
  734. });
  735. it('adjusts trace bounds by orphan error timestamp as well', () => {
  736. const tree = TraceTree.FromTrace(
  737. makeTrace({
  738. transactions: [
  739. makeTransaction({
  740. start_timestamp: 0.1,
  741. timestamp: 0.15,
  742. children: [],
  743. }),
  744. makeTransaction({
  745. start_timestamp: 0.2,
  746. timestamp: 0.25,
  747. children: [],
  748. }),
  749. ],
  750. orphan_errors: [
  751. makeTraceError({timestamp: 0.05}),
  752. makeTraceError({timestamp: 0.3}),
  753. ],
  754. })
  755. );
  756. expect(tree.list).toHaveLength(5);
  757. expect(tree.root.space).toStrictEqual([
  758. 0.05 * tree.root.multiplier,
  759. (0.3 - 0.05) * tree.root.multiplier,
  760. ]);
  761. });
  762. it('calculates correct trace type', () => {
  763. let tree = TraceTree.FromTrace(
  764. makeTrace({
  765. transactions: [],
  766. orphan_errors: [],
  767. })
  768. );
  769. expect(tree.shape).toBe(TraceType.EMPTY_TRACE);
  770. tree = TraceTree.FromTrace(
  771. makeTrace({
  772. transactions: [
  773. makeTransaction({
  774. children: [],
  775. }),
  776. makeTransaction({
  777. children: [],
  778. }),
  779. ],
  780. orphan_errors: [],
  781. })
  782. );
  783. expect(tree.shape).toBe(TraceType.NO_ROOT);
  784. tree = TraceTree.FromTrace(
  785. makeTrace({
  786. transactions: [
  787. makeTransaction({
  788. parent_span_id: null,
  789. children: [],
  790. }),
  791. ],
  792. orphan_errors: [],
  793. })
  794. );
  795. expect(tree.shape).toBe(TraceType.ONE_ROOT);
  796. tree = TraceTree.FromTrace(
  797. makeTrace({
  798. transactions: [
  799. makeTransaction({
  800. parent_span_id: null,
  801. children: [],
  802. }),
  803. makeTransaction({
  804. children: [],
  805. }),
  806. ],
  807. orphan_errors: [],
  808. })
  809. );
  810. expect(tree.shape).toBe(TraceType.BROKEN_SUBTRACES);
  811. tree = TraceTree.FromTrace(
  812. makeTrace({
  813. transactions: [
  814. makeTransaction({
  815. parent_span_id: null,
  816. children: [],
  817. }),
  818. makeTransaction({
  819. parent_span_id: null,
  820. children: [],
  821. }),
  822. ],
  823. orphan_errors: [],
  824. })
  825. );
  826. expect(tree.shape).toBe(TraceType.MULTIPLE_ROOTS);
  827. tree = TraceTree.FromTrace(
  828. makeTrace({
  829. transactions: [],
  830. orphan_errors: [makeTraceError()],
  831. })
  832. );
  833. expect(tree.shape).toBe(TraceType.ONLY_ERRORS);
  834. });
  835. it('builds from spans when root is a transaction node', () => {
  836. const root = new TraceTreeNode(
  837. null,
  838. makeTransaction({
  839. children: [],
  840. }),
  841. {project_slug: '', event_id: ''}
  842. );
  843. const node = TraceTree.FromSpans(
  844. root,
  845. makeEvent(),
  846. [
  847. makeSpan({start_timestamp: 0, op: '1', span_id: '1'}),
  848. makeSpan({start_timestamp: 1, op: '2', span_id: '2', parent_span_id: '1'}),
  849. makeSpan({start_timestamp: 2, op: '3', span_id: '3', parent_span_id: '2'}),
  850. makeSpan({start_timestamp: 3, op: '4', span_id: '4', parent_span_id: '1'}),
  851. ],
  852. {sdk: undefined}
  853. );
  854. if (!isSpanNode(node.children[0])) {
  855. throw new Error('Child needs to be a span');
  856. }
  857. expect(node.children[0].value.span_id).toBe('1');
  858. expect(node.children[0].value.start_timestamp).toBe(0);
  859. expect(node.children.length).toBe(1);
  860. assertSpanNode(node.children[0].children[0]);
  861. assertSpanNode(node.children[0].children[0].children[0]);
  862. assertSpanNode(node.children[0].children[1]);
  863. expect(node.children[0].children[0].value.start_timestamp).toBe(1);
  864. expect(node.children[0].children[0].children[0].value.start_timestamp).toBe(2);
  865. expect(node.children[0].children[1].value.start_timestamp).toBe(3);
  866. });
  867. it('builds from spans and copies txn nodes', () => {
  868. // transaction transaction
  869. // - child transaction -> - span
  870. // - span
  871. // - child-transaction
  872. // - span
  873. const root = new TraceTreeNode(
  874. null,
  875. makeTransaction({
  876. children: [],
  877. }),
  878. {project_slug: '', event_id: ''}
  879. );
  880. root.children.push(
  881. new TraceTreeNode(
  882. root,
  883. makeTransaction({
  884. parent_span_id: 'child-transaction',
  885. }),
  886. {project_slug: '', event_id: ''}
  887. )
  888. );
  889. const node = TraceTree.FromSpans(
  890. root,
  891. makeEvent(),
  892. [
  893. makeSpan({start_timestamp: 0, timestamp: 0.1, op: 'span', span_id: 'none'}),
  894. makeSpan({
  895. start_timestamp: 0.1,
  896. timestamp: 0.2,
  897. op: 'child-transaction',
  898. span_id: 'child-transaction',
  899. }),
  900. makeSpan({start_timestamp: 0.2, timestamp: 0.25, op: 'span', span_id: 'none'}),
  901. ],
  902. {sdk: undefined}
  903. );
  904. assertSpanNode(node.children[1]);
  905. assertTransactionNode(node.children[1].children[0]);
  906. });
  907. it('builds from spans and copies txn nodes to nested children', () => {
  908. // parent transaction parent transaction
  909. // - child transaction -> - span
  910. // - grandchild transaction -> - child-transaction
  911. // - grandchild-transaction
  912. //
  913. const root = new TraceTreeNode(
  914. null,
  915. makeTransaction({
  916. span_id: 'parent-transaction',
  917. children: [],
  918. }),
  919. {project_slug: '', event_id: ''}
  920. );
  921. let start: TraceTreeNode<TraceTree.NodeValue> = root;
  922. for (let i = 0; i < 2; i++) {
  923. const node = new TraceTreeNode(
  924. start,
  925. makeTransaction({
  926. transaction: `${i === 0 ? 'child' : 'grandchild'}-transaction`,
  927. parent_span_id: `${i === 0 ? 'child' : 'grandchild'}-transaction`,
  928. }),
  929. {project_slug: '', event_id: ''}
  930. );
  931. start.children.push(node);
  932. start = node;
  933. }
  934. const node = TraceTree.FromSpans(
  935. root,
  936. makeEvent(),
  937. [
  938. makeSpan({start_timestamp: 0, timestamp: 0.1, op: 'span', span_id: 'none'}),
  939. makeSpan({
  940. start_timestamp: 0.1,
  941. timestamp: 0.2,
  942. op: 'child-transaction',
  943. span_id: 'child-transaction',
  944. }),
  945. ],
  946. {sdk: undefined}
  947. );
  948. assertSpanNode(node.children[1]);
  949. assertTransactionNode(node.children[1].children[0]);
  950. assertTransactionNode(node.children[1].children[0].children[0]);
  951. });
  952. it('injects missing spans', () => {
  953. const root = new TraceTreeNode(
  954. null,
  955. makeTransaction({
  956. children: [],
  957. }),
  958. {project_slug: '', event_id: ''}
  959. );
  960. const date = new Date().getTime();
  961. const node = TraceTree.FromSpans(
  962. root,
  963. makeEvent(),
  964. [
  965. makeSpan({
  966. start_timestamp: date,
  967. timestamp: date + 1,
  968. span_id: '1',
  969. op: 'span 1',
  970. }),
  971. makeSpan({
  972. start_timestamp: date + 2,
  973. timestamp: date + 4,
  974. op: 'span 2',
  975. span_id: '2',
  976. }),
  977. ],
  978. {sdk: undefined}
  979. );
  980. assertSpanNode(node.children[0]);
  981. assertMissingInstrumentationNode(node.children[1]);
  982. assertSpanNode(node.children[2]);
  983. expect(node.children.length).toBe(3);
  984. expect(node.children[0].value.op).toBe('span 1');
  985. expect(node.children[1].value.type).toBe('missing_instrumentation');
  986. expect(node.children[2].value.op).toBe('span 2');
  987. });
  988. it('does not inject missing spans for javascript platform', () => {
  989. const root = new TraceTreeNode(
  990. null,
  991. makeTransaction({
  992. children: [],
  993. }),
  994. {project_slug: '', event_id: ''}
  995. );
  996. const date = new Date().getTime();
  997. const node = TraceTree.FromSpans(
  998. root,
  999. makeEvent(),
  1000. [
  1001. makeSpan({
  1002. start_timestamp: date,
  1003. timestamp: date + 1,
  1004. span_id: '1',
  1005. op: 'span 1',
  1006. }),
  1007. makeSpan({
  1008. start_timestamp: date + 2,
  1009. timestamp: date + 4,
  1010. op: 'span 2',
  1011. span_id: '2',
  1012. }),
  1013. ],
  1014. {sdk: 'sentry.javascript.browser'}
  1015. );
  1016. assertSpanNode(node.children[0]);
  1017. assertSpanNode(node.children[1]);
  1018. expect(node.children.length).toBe(2);
  1019. expect(node.children[0].value.op).toBe('span 1');
  1020. expect(node.children[1].value.op).toBe('span 2');
  1021. });
  1022. it('builds and preserves list order', async () => {
  1023. const organization = OrganizationFixture();
  1024. const api = new MockApiClient();
  1025. const tree = TraceTree.FromTrace(
  1026. makeTrace({
  1027. transactions: [
  1028. makeTransaction({
  1029. transaction: 'txn 1',
  1030. start_timestamp: 0,
  1031. children: [makeTransaction({start_timestamp: 1, transaction: 'txn 2'})],
  1032. }),
  1033. ],
  1034. })
  1035. );
  1036. tree.expand(tree.list[0], true);
  1037. const node = tree.list[1];
  1038. const request = MockApiClient.addMockResponse({
  1039. url: '/organizations/org-slug/events/undefined:undefined/',
  1040. method: 'GET',
  1041. body: makeEvent({startTimestamp: 0}, [
  1042. makeSpan({start_timestamp: 1, op: 'span 1', span_id: '1'}),
  1043. makeSpan({
  1044. start_timestamp: 2,
  1045. op: 'span 2',
  1046. span_id: '2',
  1047. parent_span_id: '1',
  1048. }),
  1049. makeSpan({start_timestamp: 3, op: 'span 3', parent_span_id: '2'}),
  1050. makeSpan({start_timestamp: 4, op: 'span 4', parent_span_id: '1'}),
  1051. ]),
  1052. });
  1053. // 0
  1054. // 1
  1055. // 2
  1056. // 3
  1057. // 4
  1058. tree.zoomIn(node, true, {api, organization});
  1059. await waitFor(() => {
  1060. expect(node.zoomedIn).toBe(true);
  1061. });
  1062. expect(request).toHaveBeenCalled();
  1063. expect(tree.list.length).toBe(6);
  1064. assertTransactionNode(tree.list[1]);
  1065. assertSpanNode(tree.list[2]);
  1066. assertSpanNode(tree.list[3]);
  1067. expect(tree.list[1].value.start_timestamp).toBe(0);
  1068. expect(tree.list[2].value.start_timestamp).toBe(1);
  1069. expect(tree.list[3].value.start_timestamp).toBe(2);
  1070. });
  1071. it('preserves input order', () => {
  1072. const firstChild = makeTransaction({
  1073. start_timestamp: 0,
  1074. timestamp: 1,
  1075. children: [],
  1076. });
  1077. const secondChild = makeTransaction({
  1078. start_timestamp: 1,
  1079. timestamp: 2,
  1080. children: [],
  1081. });
  1082. const tree = TraceTree.FromTrace(
  1083. makeTrace({
  1084. transactions: [
  1085. makeTransaction({
  1086. start_timestamp: 0,
  1087. timestamp: 2,
  1088. children: [firstChild, secondChild],
  1089. }),
  1090. makeTransaction({
  1091. start_timestamp: 2,
  1092. timestamp: 4,
  1093. }),
  1094. ],
  1095. })
  1096. );
  1097. expect(tree.list).toHaveLength(5);
  1098. expect(tree.expand(tree.list[1], false)).toBe(true);
  1099. expect(tree.list).toHaveLength(3);
  1100. expect(tree.expand(tree.list[1], true)).toBe(true);
  1101. expect(tree.list).toHaveLength(5);
  1102. expect(tree.list[2].value).toBe(firstChild);
  1103. expect(tree.list[3].value).toBe(secondChild);
  1104. });
  1105. it('creates children -> parent references', () => {
  1106. const tree = TraceTree.FromTrace(
  1107. makeTrace({
  1108. transactions: [
  1109. makeTransaction({
  1110. start_timestamp: 0,
  1111. timestamp: 2,
  1112. children: [makeTransaction({start_timestamp: 1, timestamp: 2})],
  1113. }),
  1114. makeTransaction({
  1115. start_timestamp: 2,
  1116. timestamp: 4,
  1117. }),
  1118. ],
  1119. })
  1120. );
  1121. expect(tree.list).toHaveLength(4);
  1122. expect(tree.list[2].parent?.value).toBe(tree.list[1].value);
  1123. });
  1124. it('establishes parent-child relationships', () => {
  1125. const tree = TraceTree.FromTrace(
  1126. makeTrace({
  1127. transactions: [
  1128. makeTransaction({
  1129. children: [makeTransaction()],
  1130. }),
  1131. ],
  1132. })
  1133. );
  1134. expect(tree.root.children).toHaveLength(1);
  1135. expect(tree.root.children[0].children).toHaveLength(1);
  1136. });
  1137. it('isLastChild', () => {
  1138. const tree = TraceTree.FromTrace(
  1139. makeTrace({
  1140. transactions: [
  1141. makeTransaction({
  1142. children: [makeTransaction(), makeTransaction()],
  1143. }),
  1144. makeTransaction(),
  1145. ],
  1146. orphan_errors: [],
  1147. })
  1148. );
  1149. tree.expand(tree.list[1], true);
  1150. expect(tree.list[0].isLastChild).toBe(true);
  1151. expect(tree.list[1].isLastChild).toBe(false);
  1152. expect(tree.list[2].isLastChild).toBe(false);
  1153. expect(tree.list[3].isLastChild).toBe(true);
  1154. expect(tree.list[4].isLastChild).toBe(true);
  1155. });
  1156. describe('connectors', () => {
  1157. it('computes transaction connectors', () => {
  1158. const tree = TraceTree.FromTrace(
  1159. makeTrace({
  1160. transactions: [
  1161. makeTransaction({
  1162. transaction: 'sibling',
  1163. children: [
  1164. makeTransaction({transaction: 'child'}),
  1165. makeTransaction({transaction: 'child'}),
  1166. ],
  1167. }),
  1168. makeTransaction({transaction: 'sibling'}),
  1169. ],
  1170. })
  1171. );
  1172. // -1 root
  1173. // ------ list begins here
  1174. // 0 transaction
  1175. // 0 |- sibling
  1176. // -1, 2| | - child
  1177. // -1| | - child
  1178. // 0 |- sibling
  1179. tree.expand(tree.list[1], true);
  1180. expect(tree.list.length).toBe(5);
  1181. expect(tree.list[0].connectors.length).toBe(0);
  1182. expect(tree.list[1].connectors.length).toBe(1);
  1183. expect(tree.list[1].connectors[0]).toBe(-1);
  1184. expect(tree.list[2].connectors[0]).toBe(-1);
  1185. expect(tree.list[2].connectors[1]).toBe(2);
  1186. expect(tree.list[2].connectors.length).toBe(2);
  1187. expect(tree.list[3].connectors[0]).toBe(-1);
  1188. expect(tree.list[3].connectors.length).toBe(1);
  1189. expect(tree.list[4].connectors.length).toBe(0);
  1190. });
  1191. it('computes span connectors', async () => {
  1192. const tree = TraceTree.FromTrace(
  1193. makeTrace({
  1194. transactions: [
  1195. makeTransaction({
  1196. project_slug: 'project',
  1197. event_id: 'event_id',
  1198. transaction: 'transaction',
  1199. children: [],
  1200. }),
  1201. ],
  1202. })
  1203. );
  1204. // root
  1205. // |- node1 []
  1206. // |- node2 []
  1207. MockApiClient.addMockResponse({
  1208. url: '/organizations/org-slug/events/project:event_id/',
  1209. method: 'GET',
  1210. body: makeEvent({}, [makeSpan({start_timestamp: 0, op: 'span', span_id: '1'})]),
  1211. });
  1212. expect(tree.list.length).toBe(2);
  1213. tree.zoomIn(tree.list[1], true, {
  1214. api: new MockApiClient(),
  1215. organization: OrganizationFixture(),
  1216. });
  1217. await waitFor(() => {
  1218. expect(tree.list.length).toBe(3);
  1219. });
  1220. // root
  1221. // |- node1 []
  1222. // |- node2 []
  1223. // |- span1 []
  1224. const span = tree.list[tree.list.length - 1];
  1225. expect(span.connectors.length).toBe(0);
  1226. });
  1227. });
  1228. describe('expanding', () => {
  1229. it('expands a node and updates the list', () => {
  1230. const tree = TraceTree.FromTrace(
  1231. makeTrace({transactions: [makeTransaction({children: [makeTransaction()]})]})
  1232. );
  1233. const node = tree.list[1];
  1234. expect(tree.expand(node, false)).toBe(true);
  1235. expect(tree.list.length).toBe(2);
  1236. expect(node.expanded).toBe(false);
  1237. expect(tree.expand(node, true)).toBe(true);
  1238. expect(node.expanded).toBe(true);
  1239. // Assert that the list has been updated
  1240. expect(tree.list).toHaveLength(3);
  1241. expect(tree.list[2]).toBe(node.children[0]);
  1242. });
  1243. it('collapses a node and updates the list', () => {
  1244. const tree = TraceTree.FromTrace(
  1245. makeTrace({transactions: [makeTransaction({children: [makeTransaction()]})]})
  1246. );
  1247. const node = tree.list[1];
  1248. tree.expand(node, true);
  1249. expect(tree.list.length).toBe(3);
  1250. expect(tree.expand(node, false)).toBe(true);
  1251. expect(node.expanded).toBe(false);
  1252. // Assert that the list has been updated
  1253. expect(tree.list).toHaveLength(2);
  1254. expect(tree.list[1]).toBe(node);
  1255. });
  1256. it('preserves children expanded state', () => {
  1257. const tree = TraceTree.FromTrace(
  1258. makeTrace({
  1259. transactions: [
  1260. makeTransaction({
  1261. children: [
  1262. makeTransaction({children: [makeTransaction({start_timestamp: 1000})]}),
  1263. makeTransaction({start_timestamp: 5}),
  1264. ],
  1265. }),
  1266. ],
  1267. })
  1268. );
  1269. expect(tree.expand(tree.list[2], false)).toBe(true);
  1270. // Assert that the list has been updated
  1271. expect(tree.list).toHaveLength(4);
  1272. expect(tree.expand(tree.list[2], true)).toBe(true);
  1273. expect(tree.list.length).toBe(5);
  1274. expect(tree.list[tree.list.length - 1].value).toEqual(
  1275. makeTransaction({start_timestamp: 5})
  1276. );
  1277. });
  1278. it('expanding or collapsing a zoomed in node doesnt do anything', async () => {
  1279. const organization = OrganizationFixture();
  1280. const api = new MockApiClient();
  1281. const tree = TraceTree.FromTrace(
  1282. makeTrace({transactions: [makeTransaction({children: [makeTransaction()]})]})
  1283. );
  1284. const node = tree.list[0];
  1285. const request = MockApiClient.addMockResponse({
  1286. url: '/organizations/org-slug/events/undefined:undefined/',
  1287. method: 'GET',
  1288. body: makeEvent(),
  1289. });
  1290. tree.zoomIn(node, true, {api, organization});
  1291. await waitFor(() => {
  1292. expect(node.zoomedIn).toBe(true);
  1293. });
  1294. expect(request).toHaveBeenCalled();
  1295. expect(tree.expand(node, true)).toBe(false);
  1296. });
  1297. });
  1298. describe('zooming', () => {
  1299. it('marks node as zoomed in', async () => {
  1300. const organization = OrganizationFixture();
  1301. const api = new MockApiClient();
  1302. const tree = TraceTree.FromTrace(
  1303. makeTrace({
  1304. transactions: [
  1305. makeTransaction({project_slug: 'project', event_id: 'event_id'}),
  1306. ],
  1307. })
  1308. );
  1309. const request = MockApiClient.addMockResponse({
  1310. url: '/organizations/org-slug/events/project:event_id/',
  1311. method: 'GET',
  1312. body: makeEvent(),
  1313. });
  1314. const node = tree.list[1];
  1315. expect(node.zoomedIn).toBe(false);
  1316. tree.zoomIn(node, true, {api, organization});
  1317. await waitFor(() => {
  1318. expect(node.zoomedIn).toBe(true);
  1319. });
  1320. expect(request).toHaveBeenCalled();
  1321. });
  1322. it('fetches spans for node when zooming in', async () => {
  1323. const tree = TraceTree.FromTrace(
  1324. makeTrace({
  1325. transactions: [
  1326. makeTransaction({
  1327. transaction: 'txn',
  1328. project_slug: 'project',
  1329. event_id: 'event_id',
  1330. }),
  1331. ],
  1332. })
  1333. );
  1334. const request = MockApiClient.addMockResponse({
  1335. url: '/organizations/org-slug/events/project:event_id/',
  1336. method: 'GET',
  1337. body: makeEvent({}, [makeSpan()]),
  1338. });
  1339. const node = tree.list[1];
  1340. expect(node.children).toHaveLength(0);
  1341. tree.zoomIn(node, true, {
  1342. api: new MockApiClient(),
  1343. organization: OrganizationFixture(),
  1344. });
  1345. expect(request).toHaveBeenCalled();
  1346. await waitFor(() => {
  1347. expect(node.children).toHaveLength(1);
  1348. });
  1349. // Assert that the children have been updated
  1350. assertTransactionNode(node.children[0].parent);
  1351. expect(node.children[0].parent.value.transaction).toBe('txn');
  1352. expect(node.children[0].depth).toBe(node.depth + 1);
  1353. });
  1354. it('handles bottom up zooming', async () => {
  1355. const tree = TraceTree.FromTrace(
  1356. makeTrace({
  1357. transactions: [
  1358. makeTransaction({
  1359. transaction: 'transaction',
  1360. project_slug: 'project',
  1361. event_id: 'event_id',
  1362. children: [
  1363. makeTransaction({
  1364. parent_span_id: 'span',
  1365. transaction: 'child transaction',
  1366. project_slug: 'child_project',
  1367. event_id: 'child_event_id',
  1368. }),
  1369. ],
  1370. }),
  1371. ],
  1372. })
  1373. );
  1374. const first_request = MockApiClient.addMockResponse({
  1375. url: '/organizations/org-slug/events/project:event_id/',
  1376. method: 'GET',
  1377. body: makeEvent({}, [makeSpan({op: 'db', span_id: 'span'})]),
  1378. });
  1379. const second_request = MockApiClient.addMockResponse({
  1380. url: '/organizations/org-slug/events/child_project:child_event_id/',
  1381. method: 'GET',
  1382. body: makeEvent({}, [
  1383. makeSpan({op: 'db', span_id: 'span'}),
  1384. makeSpan({op: 'db', span_id: 'span 1', parent_span_id: 'span'}),
  1385. makeSpan({op: 'db', span_id: 'span 2', parent_span_id: 'span 1'}),
  1386. makeSpan({op: 'db', span_id: 'span 3', parent_span_id: 'span 2'}),
  1387. makeSpan({op: 'db', span_id: 'span 4', parent_span_id: 'span 3'}),
  1388. makeSpan({op: 'db', span_id: 'span 5', parent_span_id: 'span 4'}),
  1389. ]),
  1390. });
  1391. tree.zoomIn(tree.list[2], true, {
  1392. api: new MockApiClient(),
  1393. organization: OrganizationFixture(),
  1394. });
  1395. await waitFor(() => {
  1396. expect(second_request).toHaveBeenCalled();
  1397. });
  1398. assertParentAutogroupedNode(tree.list[tree.list.length - 1]);
  1399. tree.zoomIn(tree.list[1], true, {
  1400. api: new MockApiClient(),
  1401. organization: OrganizationFixture(),
  1402. });
  1403. await waitFor(() => {
  1404. expect(first_request).toHaveBeenCalled();
  1405. });
  1406. assertParentAutogroupedNode(tree.list[tree.list.length - 1]);
  1407. });
  1408. it('zooms out', async () => {
  1409. const tree = TraceTree.FromTrace(
  1410. makeTrace({
  1411. transactions: [
  1412. makeTransaction({project_slug: 'project', event_id: 'event_id'}),
  1413. ],
  1414. })
  1415. );
  1416. MockApiClient.addMockResponse({
  1417. url: '/organizations/org-slug/events/project:event_id/',
  1418. method: 'GET',
  1419. body: makeEvent({}, [makeSpan({span_id: 'span1', description: 'span1'})]),
  1420. });
  1421. tree.zoomIn(tree.list[1], true, {
  1422. api: new MockApiClient(),
  1423. organization: OrganizationFixture(),
  1424. });
  1425. await waitFor(() => {
  1426. assertSpanNode(tree.list[1].children[0]);
  1427. expect(tree.list[1].children[0].value.description).toBe('span1');
  1428. });
  1429. tree.zoomIn(tree.list[1], false, {
  1430. api: new MockApiClient(),
  1431. organization: OrganizationFixture(),
  1432. });
  1433. await waitFor(() => {
  1434. // Assert child no longer points to children
  1435. expect(tree.list[1].zoomedIn).toBe(false);
  1436. expect(tree.list[1].children[0]).toBe(undefined);
  1437. expect(tree.list[2]).toBe(undefined);
  1438. });
  1439. });
  1440. it('zooms in and out', async () => {
  1441. const tree = TraceTree.FromTrace(
  1442. makeTrace({
  1443. transactions: [
  1444. makeTransaction({project_slug: 'project', event_id: 'event_id'}),
  1445. ],
  1446. })
  1447. );
  1448. MockApiClient.addMockResponse({
  1449. url: '/organizations/org-slug/events/project:event_id/',
  1450. method: 'GET',
  1451. body: makeEvent({}, [makeSpan({span_id: 'span 1', description: 'span1'})]),
  1452. });
  1453. // Zoom in
  1454. tree.zoomIn(tree.list[1], true, {
  1455. api: new MockApiClient(),
  1456. organization: OrganizationFixture(),
  1457. });
  1458. await waitFor(() => {
  1459. expect(tree.list[1].zoomedIn).toBe(true);
  1460. assertSpanNode(tree.list[1].children[0]);
  1461. expect(tree.list[1].children[0].value.description).toBe('span1');
  1462. });
  1463. // Zoom out
  1464. tree.zoomIn(tree.list[1], false, {
  1465. api: new MockApiClient(),
  1466. organization: OrganizationFixture(),
  1467. });
  1468. await waitFor(() => {
  1469. expect(tree.list[2]).toBe(undefined);
  1470. });
  1471. // Zoom in
  1472. tree.zoomIn(tree.list[1], true, {
  1473. api: new MockApiClient(),
  1474. organization: OrganizationFixture(),
  1475. });
  1476. await waitFor(() => {
  1477. assertSpanNode(tree.list[1].children[0]);
  1478. expect(tree.list[1].children[0].value?.description).toBe('span1');
  1479. });
  1480. });
  1481. it('zooms in and out preserving siblings', async () => {
  1482. const tree = TraceTree.FromTrace(
  1483. makeTrace({
  1484. transactions: [
  1485. makeTransaction({
  1486. project_slug: 'project',
  1487. event_id: 'event_id',
  1488. start_timestamp: 0,
  1489. children: [
  1490. makeTransaction({
  1491. start_timestamp: 1,
  1492. timestamp: 2,
  1493. project_slug: 'other_project',
  1494. event_id: 'event_id',
  1495. }),
  1496. makeTransaction({start_timestamp: 2, timestamp: 3}),
  1497. ],
  1498. }),
  1499. ],
  1500. })
  1501. );
  1502. const request = MockApiClient.addMockResponse({
  1503. url: '/organizations/org-slug/events/other_project:event_id/',
  1504. method: 'GET',
  1505. body: makeEvent({}, [makeSpan({description: 'span1'})]),
  1506. });
  1507. tree.expand(tree.list[1], true);
  1508. tree.zoomIn(tree.list[2], true, {
  1509. api: new MockApiClient(),
  1510. organization: OrganizationFixture(),
  1511. });
  1512. expect(request).toHaveBeenCalled();
  1513. // Zoom in
  1514. await waitFor(() => {
  1515. expect(tree.list.length).toBe(5);
  1516. });
  1517. // Zoom out
  1518. tree.zoomIn(tree.list[2], false, {
  1519. api: new MockApiClient(),
  1520. organization: OrganizationFixture(),
  1521. });
  1522. await waitFor(() => {
  1523. expect(tree.list.length).toBe(4);
  1524. });
  1525. });
  1526. it('preserves expanded state when zooming in and out', async () => {
  1527. const tree = TraceTree.FromTrace(
  1528. makeTrace({
  1529. transactions: [
  1530. makeTransaction({
  1531. project_slug: 'project',
  1532. event_id: 'event_id',
  1533. children: [
  1534. makeTransaction({project_slug: 'other_project', event_id: 'event_id'}),
  1535. ],
  1536. }),
  1537. ],
  1538. })
  1539. );
  1540. MockApiClient.addMockResponse({
  1541. url: '/organizations/org-slug/events/project:event_id/',
  1542. method: 'GET',
  1543. body: makeEvent({}, [
  1544. makeSpan({description: 'span1'}),
  1545. makeSpan({description: 'span2'}),
  1546. ]),
  1547. });
  1548. tree.expand(tree.list[1], true);
  1549. expect(tree.list.length).toBe(3);
  1550. tree.zoomIn(tree.list[1], true, {
  1551. api: new MockApiClient(),
  1552. organization: OrganizationFixture(),
  1553. });
  1554. await waitFor(() => {
  1555. expect(tree.list.length).toBe(4);
  1556. });
  1557. tree.zoomIn(tree.list[1], false, {
  1558. api: new MockApiClient(),
  1559. organization: OrganizationFixture(),
  1560. });
  1561. await waitFor(() => {
  1562. expect(tree.list.length).toBe(3);
  1563. });
  1564. expect(tree.list[1].expanded).toBe(true);
  1565. });
  1566. });
  1567. describe('autogrouping', () => {
  1568. it('auto groups sibling spans and preserves tail spans', () => {
  1569. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  1570. project_slug: '',
  1571. event_id: '',
  1572. });
  1573. for (let i = 0; i < 5; i++) {
  1574. root.children.push(
  1575. new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
  1576. project_slug: '',
  1577. event_id: '',
  1578. })
  1579. );
  1580. }
  1581. root.children.push(
  1582. new TraceTreeNode(root, makeSpan({description: 'span', op: 'http'}), {
  1583. project_slug: '',
  1584. event_id: '',
  1585. })
  1586. );
  1587. expect(root.children.length).toBe(6);
  1588. TraceTree.AutogroupSiblingSpanNodes(root);
  1589. expect(root.children.length).toBe(2);
  1590. });
  1591. it('autogroups when number of children is exactly 5', () => {
  1592. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  1593. project_slug: '',
  1594. event_id: '',
  1595. });
  1596. for (let i = 0; i < 5; i++) {
  1597. root.children.push(
  1598. new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
  1599. project_slug: '',
  1600. event_id: '',
  1601. })
  1602. );
  1603. }
  1604. expect(root.children.length).toBe(5);
  1605. TraceTree.AutogroupSiblingSpanNodes(root);
  1606. expect(root.children.length).toBe(1);
  1607. });
  1608. it('collects errors and performance issues for sibling autogrouped node', () => {
  1609. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  1610. project_slug: '',
  1611. event_id: '',
  1612. });
  1613. for (let i = 0; i < 5; i++) {
  1614. const node = new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
  1615. project_slug: '',
  1616. event_id: '',
  1617. });
  1618. node.errors = [makeTraceError()];
  1619. node.performance_issues = [makeTracePerformanceIssue()];
  1620. root.children.push(node);
  1621. }
  1622. expect(root.children.length).toBe(5);
  1623. TraceTree.AutogroupSiblingSpanNodes(root);
  1624. expect(root.children.length).toBe(1);
  1625. const autogroupedNode = root.children[0];
  1626. assertSiblingAutogroupedNode(autogroupedNode);
  1627. expect(autogroupedNode.has_errors).toBe(true);
  1628. expect(autogroupedNode.errors).toHaveLength(5);
  1629. expect(autogroupedNode.performance_issues).toHaveLength(5);
  1630. });
  1631. it('adds autogrouped siblings as children under autogrouped node', () => {
  1632. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  1633. project_slug: '',
  1634. event_id: '',
  1635. });
  1636. for (let i = 0; i < 5; i++) {
  1637. root.children.push(
  1638. new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
  1639. project_slug: '',
  1640. event_id: '',
  1641. })
  1642. );
  1643. }
  1644. expect(root.children.length).toBe(5);
  1645. TraceTree.AutogroupSiblingSpanNodes(root);
  1646. expect(root.children.length).toBe(1);
  1647. const autoGroupedNode = root.children[0];
  1648. assertAutogroupedNode(autoGroupedNode);
  1649. expect(autoGroupedNode.groupCount).toBe(5);
  1650. expect(autoGroupedNode.children.length).toBe(5);
  1651. });
  1652. it('autogroups when number of children is > 5', () => {
  1653. const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
  1654. project_slug: '',
  1655. event_id: '',
  1656. });
  1657. for (let i = 0; i < 7; i++) {
  1658. root.children.push(
  1659. new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
  1660. project_slug: '',
  1661. event_id: '',
  1662. })
  1663. );
  1664. }
  1665. expect(root.children.length).toBe(7);
  1666. TraceTree.AutogroupSiblingSpanNodes(root);
  1667. expect(root.children.length).toBe(1);
  1668. });
  1669. it('autogroups direct children case', () => {
  1670. // db db db
  1671. // http -> parent autogroup (3) -> parent autogroup (3)
  1672. // http http
  1673. // http http
  1674. // http
  1675. const root: TraceTreeNode<TraceTree.Span> = new TraceTreeNode(
  1676. null,
  1677. makeSpan({
  1678. description: `span1`,
  1679. span_id: `1`,
  1680. op: 'db',
  1681. }),
  1682. {project_slug: '', event_id: ''}
  1683. );
  1684. let last: TraceTreeNode<any> = root;
  1685. for (let i = 0; i < 3; i++) {
  1686. const node = new TraceTreeNode(
  1687. last,
  1688. makeSpan({
  1689. description: `span${i}`,
  1690. span_id: `${i}`,
  1691. op: 'http',
  1692. }),
  1693. {
  1694. project_slug: '',
  1695. event_id: '',
  1696. }
  1697. );
  1698. last.children.push(node);
  1699. last = node;
  1700. }
  1701. if (!root) {
  1702. throw new Error('root is null');
  1703. }
  1704. expect(root.children.length).toBe(1);
  1705. expect(root.children[0].children.length).toBe(1);
  1706. TraceTree.AutogroupDirectChildrenSpanNodes(root);
  1707. expect(root.children.length).toBe(1);
  1708. assertAutogroupedNode(root.children[0]);
  1709. expect(root.children[0].children.length).toBe(0);
  1710. root.children[0].expanded = true;
  1711. expect((root.children[0].children[0].value as RawSpanType).description).toBe(
  1712. 'span0'
  1713. );
  1714. });
  1715. it('collects errors and performance issues for parent autogrouped node', () => {
  1716. // db db db
  1717. // http -> parent autogroup (3) -> parent autogroup (3)
  1718. // http http
  1719. // http http
  1720. // http
  1721. const root: TraceTreeNode<TraceTree.Span> = new TraceTreeNode(
  1722. null,
  1723. makeSpan({
  1724. description: `span1`,
  1725. span_id: `1`,
  1726. op: 'db',
  1727. }),
  1728. {project_slug: '', event_id: ''}
  1729. );
  1730. let last: TraceTreeNode<any> = root;
  1731. for (let i = 0; i < 3; i++) {
  1732. const node = new TraceTreeNode(
  1733. last,
  1734. makeSpan({
  1735. description: `span${i}`,
  1736. span_id: `${i}`,
  1737. op: 'http',
  1738. }),
  1739. {
  1740. project_slug: '',
  1741. event_id: '',
  1742. }
  1743. );
  1744. node.errors = [makeTraceError()];
  1745. node.performance_issues = [makeTracePerformanceIssue()];
  1746. last.children.push(node);
  1747. last = node;
  1748. }
  1749. if (!root) {
  1750. throw new Error('root is null');
  1751. }
  1752. expect(root.children.length).toBe(1);
  1753. expect(root.children[0].children.length).toBe(1);
  1754. TraceTree.AutogroupDirectChildrenSpanNodes(root);
  1755. expect(root.children.length).toBe(1);
  1756. assertAutogroupedNode(root.children[0]);
  1757. expect(root.children[0].has_errors).toBe(true);
  1758. expect(root.children[0].errors).toHaveLength(3);
  1759. expect(root.children[0].performance_issues).toHaveLength(3);
  1760. });
  1761. it('autogrouping direct children skips rendering intermediary nodes', () => {
  1762. // db db db
  1763. // http autogrouped (3) autogrouped (3)
  1764. // http -> db -> http
  1765. // http http
  1766. // db http
  1767. // db
  1768. const root = new TraceTreeNode(
  1769. null,
  1770. makeSpan({span_id: 'span1', description: 'span1', op: 'db'}),
  1771. {
  1772. project_slug: '',
  1773. event_id: '',
  1774. }
  1775. );
  1776. let last = root;
  1777. for (let i = 0; i < 4; i++) {
  1778. const node = new TraceTreeNode(
  1779. last,
  1780. makeSpan({
  1781. span_id: `span`,
  1782. description: `span`,
  1783. op: i === 3 ? 'db' : 'http',
  1784. }),
  1785. {
  1786. project_slug: '',
  1787. event_id: '',
  1788. }
  1789. );
  1790. last.children.push(node);
  1791. last = node;
  1792. }
  1793. TraceTree.AutogroupDirectChildrenSpanNodes(root);
  1794. const autoGroupedNode = root.children[0];
  1795. assertAutogroupedNode(autoGroupedNode);
  1796. expect(autoGroupedNode.children.length).toBe(1);
  1797. expect((autoGroupedNode.children[0].value as RawSpanType).op).toBe('db');
  1798. autoGroupedNode.expanded = true;
  1799. expect(autoGroupedNode.children.length).toBe(1);
  1800. expect((autoGroupedNode.children[0].value as RawSpanType).op).toBe('http');
  1801. });
  1802. it('nested direct autogrouping', () => {
  1803. // db db db
  1804. // http -> parent autogroup (3) -> parent autogroup (3)
  1805. // http db http
  1806. // http parent autogroup (3) http
  1807. // db http
  1808. // http db
  1809. // http parent autogrouped (3)
  1810. // http http
  1811. // http
  1812. // http
  1813. const root = new TraceTreeNode(
  1814. null,
  1815. makeSpan({span_id: 'span', description: 'span', op: 'db'}),
  1816. {
  1817. project_slug: '',
  1818. event_id: '',
  1819. }
  1820. );
  1821. let last = root;
  1822. for (let i = 0; i < 3; i++) {
  1823. if (i === 1) {
  1824. const autogroupBreakingSpan = new TraceTreeNode(
  1825. last,
  1826. makeSpan({span_id: 'span', description: 'span', op: 'db'}),
  1827. {
  1828. project_slug: '',
  1829. event_id: '',
  1830. }
  1831. );
  1832. last.children.push(autogroupBreakingSpan);
  1833. last = autogroupBreakingSpan;
  1834. } else {
  1835. for (let j = 0; j < 3; j++) {
  1836. const node = new TraceTreeNode(
  1837. last,
  1838. makeSpan({span_id: `span${j}`, description: `span${j}`, op: 'http'}),
  1839. {
  1840. project_slug: '',
  1841. event_id: '',
  1842. }
  1843. );
  1844. last.children.push(node);
  1845. last = node;
  1846. }
  1847. }
  1848. }
  1849. TraceTree.AutogroupDirectChildrenSpanNodes(root);
  1850. assertAutogroupedNode(root.children[0]);
  1851. assertAutogroupedNode(root.children[0].children[0].children[0]);
  1852. });
  1853. it('sibling autogrouping', () => {
  1854. // db db
  1855. // http sibling autogrouped (5)
  1856. // http
  1857. // http ->
  1858. // http
  1859. // http
  1860. const root = new TraceTreeNode(
  1861. null,
  1862. makeTransaction({start_timestamp: 0, timestamp: 10}),
  1863. {
  1864. project_slug: '',
  1865. event_id: '',
  1866. }
  1867. );
  1868. for (let i = 0; i < 5; i++) {
  1869. root.children.push(
  1870. new TraceTreeNode(root, makeSpan({start_timestamp: i, timestamp: i + 1}), {
  1871. project_slug: '',
  1872. event_id: '',
  1873. })
  1874. );
  1875. }
  1876. TraceTree.AutogroupSiblingSpanNodes(root);
  1877. expect(root.children.length).toBe(1);
  1878. assertAutogroupedNode(root.children[0]);
  1879. });
  1880. it('multiple sibling autogrouping', () => {
  1881. // db db
  1882. // http sibling autogrouped (5)
  1883. // http db
  1884. // http -> sibling autogrouped (5)
  1885. // http
  1886. // http
  1887. // db
  1888. // http
  1889. // http
  1890. // http
  1891. // http
  1892. // http
  1893. const root = new TraceTreeNode(
  1894. null,
  1895. makeTransaction({start_timestamp: 0, timestamp: 10}),
  1896. {
  1897. project_slug: '',
  1898. event_id: '',
  1899. }
  1900. );
  1901. for (let i = 0; i < 10; i++) {
  1902. if (i === 5) {
  1903. root.children.push(
  1904. new TraceTreeNode(
  1905. root,
  1906. makeSpan({start_timestamp: i, timestamp: i + 1, op: 'db'}),
  1907. {
  1908. project_slug: '',
  1909. event_id: '',
  1910. }
  1911. )
  1912. );
  1913. }
  1914. root.children.push(
  1915. new TraceTreeNode(
  1916. root,
  1917. makeSpan({start_timestamp: i, timestamp: i + 1, op: 'http'}),
  1918. {
  1919. project_slug: '',
  1920. event_id: '',
  1921. }
  1922. )
  1923. );
  1924. }
  1925. TraceTree.AutogroupSiblingSpanNodes(root);
  1926. assertAutogroupedNode(root.children[0]);
  1927. expect(root.children).toHaveLength(3);
  1928. assertAutogroupedNode(root.children[2]);
  1929. });
  1930. it('renders children of autogrouped direct children nodes', async () => {
  1931. const tree = TraceTree.FromTrace(
  1932. makeTrace({
  1933. transactions: [
  1934. makeTransaction({
  1935. transaction: '/',
  1936. project_slug: 'project',
  1937. event_id: 'event_id',
  1938. }),
  1939. ],
  1940. })
  1941. );
  1942. MockApiClient.addMockResponse({
  1943. url: '/organizations/org-slug/events/project:event_id/',
  1944. method: 'GET',
  1945. body: makeEvent({}, [
  1946. makeSpan({description: 'parent span', op: 'http', span_id: '1'}),
  1947. makeSpan({description: 'span', op: 'db', span_id: '2', parent_span_id: '1'}),
  1948. makeSpan({description: 'span', op: 'db', span_id: '3', parent_span_id: '2'}),
  1949. makeSpan({description: 'span', op: 'db', span_id: '4', parent_span_id: '3'}),
  1950. makeSpan({description: 'span', op: 'db', span_id: '5', parent_span_id: '4'}),
  1951. makeSpan({
  1952. description: 'span',
  1953. op: 'redis',
  1954. span_id: '6',
  1955. parent_span_id: '5',
  1956. }),
  1957. makeSpan({description: 'span', op: 'https', parent_span_id: '1'}),
  1958. ]),
  1959. });
  1960. expect(tree.list.length).toBe(2);
  1961. tree.zoomIn(tree.list[1], true, {
  1962. api: new MockApiClient(),
  1963. organization: OrganizationFixture(),
  1964. });
  1965. await waitFor(() => {
  1966. expect(tree.list.length).toBe(6);
  1967. });
  1968. const autogroupedNode = tree.list[tree.list.length - 3];
  1969. assertParentAutogroupedNode(autogroupedNode);
  1970. expect('autogrouped_by' in autogroupedNode?.value).toBeTruthy();
  1971. expect(autogroupedNode.groupCount).toBe(4);
  1972. expect(autogroupedNode.head.value.span_id).toBe('2');
  1973. expect(autogroupedNode.tail.value.span_id).toBe('5');
  1974. // Expand autogrouped node
  1975. expect(tree.expand(autogroupedNode, true)).toBe(true);
  1976. expect(tree.list.length).toBe(10);
  1977. // Collapse autogrouped node
  1978. expect(tree.expand(autogroupedNode, false)).toBe(true);
  1979. expect(tree.list.length).toBe(6);
  1980. expect(autogroupedNode.children[0].depth).toBe(4);
  1981. });
  1982. });
  1983. });