spanTreeModel.spec.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  1. import {Client} from 'sentry/api';
  2. import SpanTreeModel from 'sentry/components/events/interfaces/spans/spanTreeModel';
  3. import {EnhancedProcessedSpanType} from 'sentry/components/events/interfaces/spans/types';
  4. import {
  5. boundsGenerator,
  6. generateRootSpan,
  7. parseTrace,
  8. } from 'sentry/components/events/interfaces/spans/utils';
  9. import {EntryType, EventTransaction} from 'sentry/types/event';
  10. import {assert} from 'sentry/types/utils';
  11. describe('SpanTreeModel', () => {
  12. const api: Client = new Client();
  13. const event = {
  14. id: '2b658a829a21496b87fd1f14a61abf65',
  15. eventID: '2b658a829a21496b87fd1f14a61abf65',
  16. title: '/organizations/:orgId/discover/results/',
  17. type: 'transaction',
  18. startTimestamp: 1622079935.86141,
  19. endTimestamp: 1622079940.032905,
  20. contexts: {
  21. trace: {
  22. trace_id: '8cbbc19c0f54447ab702f00263262726',
  23. span_id: 'a934857184bdf5a6',
  24. op: 'pageload',
  25. status: 'unknown',
  26. type: 'trace',
  27. },
  28. },
  29. entries: [
  30. {
  31. data: [
  32. {
  33. timestamp: 1622079937.227645,
  34. start_timestamp: 1622079936.90689,
  35. description: 'GET /api/0/organizations/?member=1',
  36. op: 'http',
  37. span_id: 'b23703998ae619e7',
  38. parent_span_id: 'a934857184bdf5a6',
  39. trace_id: '8cbbc19c0f54447ab702f00263262726',
  40. status: 'ok',
  41. tags: {
  42. 'http.status_code': '200',
  43. },
  44. data: {
  45. method: 'GET',
  46. type: 'fetch',
  47. url: '/api/0/organizations/?member=1',
  48. },
  49. },
  50. {
  51. timestamp: 1622079937.20331,
  52. start_timestamp: 1622079936.907515,
  53. description: 'GET /api/0/internal/health/',
  54. op: 'http',
  55. span_id: 'a453cc713e5baf9c',
  56. parent_span_id: 'a934857184bdf5a6',
  57. trace_id: '8cbbc19c0f54447ab702f00263262726',
  58. status: 'ok',
  59. tags: {
  60. 'http.status_code': '200',
  61. },
  62. data: {
  63. method: 'GET',
  64. type: 'fetch',
  65. url: '/api/0/internal/health/',
  66. },
  67. },
  68. {
  69. timestamp: 1622079936.05839,
  70. start_timestamp: 1622079936.048125,
  71. description: '/_static/dist/sentry/sentry.541f5b.css',
  72. op: 'resource.link',
  73. span_id: 'a23f26b939d1a735',
  74. parent_span_id: 'a453cc713e5baf9c',
  75. trace_id: '8cbbc19c0f54447ab702f00263262726',
  76. data: {
  77. 'Decoded Body Size': 159248,
  78. 'Encoded Body Size': 159248,
  79. 'Transfer Size': 275,
  80. },
  81. },
  82. ],
  83. type: EntryType.SPANS,
  84. },
  85. ],
  86. } as EventTransaction;
  87. MockApiClient.addMockResponse({
  88. url: '/organizations/sentry/events/project:19c403a10af34db2b7d93ad669bb51ed/',
  89. body: {
  90. ...event,
  91. contexts: {
  92. trace: {
  93. trace_id: '61d2d7c5acf448ffa8e2f8f973e2cd36',
  94. span_id: 'a5702f287954a9ef',
  95. parent_span_id: 'b23703998ae619e7',
  96. op: 'something',
  97. status: 'unknown',
  98. type: 'trace',
  99. },
  100. },
  101. entries: [
  102. {
  103. data: [
  104. {
  105. timestamp: 1622079937.227645,
  106. start_timestamp: 1622079936.90689,
  107. description: 'something child',
  108. op: 'child',
  109. span_id: 'bcbea9f18a11e161',
  110. parent_span_id: 'a5702f287954a9ef',
  111. trace_id: '61d2d7c5acf448ffa8e2f8f973e2cd36',
  112. status: 'ok',
  113. data: {},
  114. },
  115. ],
  116. type: EntryType.SPANS,
  117. },
  118. ],
  119. },
  120. });
  121. MockApiClient.addMockResponse({
  122. url: '/organizations/sentry/events/project:broken/',
  123. body: {
  124. ...event,
  125. },
  126. statusCode: 500,
  127. });
  128. it('makes children', () => {
  129. const parsedTrace = parseTrace(event);
  130. const rootSpan = generateRootSpan(parsedTrace);
  131. const spanTreeModel = new SpanTreeModel(rootSpan, parsedTrace.childSpans, api);
  132. expect(spanTreeModel.children).toHaveLength(2);
  133. });
  134. it('handles recursive children', () => {
  135. const event2 = {
  136. ...event,
  137. entries: [
  138. {
  139. data: [
  140. {
  141. timestamp: 1622079937.227645,
  142. start_timestamp: 1622079936.90689,
  143. description: 'GET /api/0/organizations/?member=1',
  144. op: 'http',
  145. span_id: 'a934857184bdf5a6',
  146. parent_span_id: 'a934857184bdf5a6',
  147. trace_id: '8cbbc19c0f54447ab702f00263262726',
  148. status: 'ok',
  149. tags: {
  150. 'http.status_code': '200',
  151. },
  152. data: {
  153. method: 'GET',
  154. type: 'fetch',
  155. url: '/api/0/organizations/?member=1',
  156. },
  157. },
  158. ],
  159. type: EntryType.SPANS,
  160. },
  161. ],
  162. } as EventTransaction;
  163. const parsedTrace = parseTrace(event2);
  164. const rootSpan = generateRootSpan(parsedTrace);
  165. const spanTreeModel = new SpanTreeModel(rootSpan, parsedTrace.childSpans, api);
  166. expect(spanTreeModel.children).toHaveLength(1);
  167. });
  168. it('operationNameCounts', () => {
  169. const parsedTrace = parseTrace(event);
  170. const rootSpan = generateRootSpan(parsedTrace);
  171. const spanTreeModel = new SpanTreeModel(rootSpan, parsedTrace.childSpans, api);
  172. expect(Object.fromEntries(spanTreeModel.operationNameCounts)).toMatchObject({
  173. http: 2,
  174. pageload: 1,
  175. 'resource.link': 1,
  176. });
  177. });
  178. it('toggleEmbeddedChildren - happy path', async () => {
  179. const parsedTrace = parseTrace(event);
  180. const rootSpan = generateRootSpan(parsedTrace);
  181. const spanTreeModel = new SpanTreeModel(rootSpan, parsedTrace.childSpans, api);
  182. expect(spanTreeModel.fetchEmbeddedChildrenState).toBe('idle');
  183. const fullWaterfall: EnhancedProcessedSpanType[] = [
  184. {
  185. type: 'span',
  186. span: {
  187. trace_id: '8cbbc19c0f54447ab702f00263262726',
  188. span_id: 'a934857184bdf5a6',
  189. parent_span_id: undefined,
  190. start_timestamp: 1622079935.86141,
  191. timestamp: 1622079940.032905,
  192. op: 'pageload',
  193. description: undefined,
  194. data: {},
  195. status: 'unknown',
  196. },
  197. numOfSpanChildren: 2,
  198. treeDepth: 0,
  199. isLastSibling: true,
  200. continuingTreeDepths: [],
  201. showEmbeddedChildren: false,
  202. toggleEmbeddedChildren: expect.any(Function),
  203. fetchEmbeddedChildrenState: 'idle',
  204. toggleNestedSpanGroup: undefined,
  205. toggleSiblingSpanGroup: undefined,
  206. isEmbeddedTransactionTimeAdjusted: false,
  207. },
  208. {
  209. type: 'span',
  210. span: {
  211. timestamp: 1622079937.227645,
  212. start_timestamp: 1622079936.90689,
  213. description: 'GET /api/0/organizations/?member=1',
  214. op: 'http',
  215. span_id: 'b23703998ae619e7',
  216. parent_span_id: 'a934857184bdf5a6',
  217. trace_id: '8cbbc19c0f54447ab702f00263262726',
  218. status: 'ok',
  219. tags: {
  220. 'http.status_code': '200',
  221. },
  222. data: {
  223. method: 'GET',
  224. type: 'fetch',
  225. url: '/api/0/organizations/?member=1',
  226. },
  227. },
  228. numOfSpanChildren: 0,
  229. treeDepth: 1,
  230. isLastSibling: false,
  231. continuingTreeDepths: [],
  232. showEmbeddedChildren: false,
  233. toggleEmbeddedChildren: expect.any(Function),
  234. fetchEmbeddedChildrenState: 'idle',
  235. toggleNestedSpanGroup: undefined,
  236. toggleSiblingSpanGroup: undefined,
  237. isEmbeddedTransactionTimeAdjusted: false,
  238. },
  239. {
  240. type: 'span',
  241. span: {
  242. timestamp: 1622079937.20331,
  243. start_timestamp: 1622079936.907515,
  244. description: 'GET /api/0/internal/health/',
  245. op: 'http',
  246. span_id: 'a453cc713e5baf9c',
  247. parent_span_id: 'a934857184bdf5a6',
  248. trace_id: '8cbbc19c0f54447ab702f00263262726',
  249. status: 'ok',
  250. tags: {
  251. 'http.status_code': '200',
  252. },
  253. data: {
  254. method: 'GET',
  255. type: 'fetch',
  256. url: '/api/0/internal/health/',
  257. },
  258. },
  259. numOfSpanChildren: 1,
  260. treeDepth: 1,
  261. isLastSibling: true,
  262. continuingTreeDepths: [],
  263. showEmbeddedChildren: false,
  264. toggleEmbeddedChildren: expect.any(Function),
  265. fetchEmbeddedChildrenState: 'idle',
  266. toggleNestedSpanGroup: undefined,
  267. toggleSiblingSpanGroup: undefined,
  268. isEmbeddedTransactionTimeAdjusted: false,
  269. },
  270. {
  271. type: 'span',
  272. span: {
  273. timestamp: 1622079936.05839,
  274. start_timestamp: 1622079936.048125,
  275. description: '/_static/dist/sentry/sentry.541f5b.css',
  276. op: 'resource.link',
  277. span_id: 'a23f26b939d1a735',
  278. parent_span_id: 'a453cc713e5baf9c',
  279. trace_id: '8cbbc19c0f54447ab702f00263262726',
  280. data: {
  281. 'Decoded Body Size': 159248,
  282. 'Encoded Body Size': 159248,
  283. 'Transfer Size': 275,
  284. },
  285. },
  286. numOfSpanChildren: 0,
  287. treeDepth: 2,
  288. isLastSibling: true,
  289. continuingTreeDepths: [],
  290. showEmbeddedChildren: false,
  291. toggleEmbeddedChildren: expect.any(Function),
  292. fetchEmbeddedChildrenState: 'idle',
  293. toggleNestedSpanGroup: undefined,
  294. toggleSiblingSpanGroup: undefined,
  295. isEmbeddedTransactionTimeAdjusted: false,
  296. },
  297. ];
  298. const generateBounds = boundsGenerator({
  299. traceStartTimestamp: parsedTrace.traceStartTimestamp,
  300. traceEndTimestamp: parsedTrace.traceEndTimestamp,
  301. viewStart: 0,
  302. viewEnd: 1,
  303. });
  304. let spans = spanTreeModel.getSpansList({
  305. operationNameFilters: {
  306. type: 'no_filter',
  307. },
  308. generateBounds,
  309. treeDepth: 0,
  310. isLastSibling: true,
  311. continuingTreeDepths: [],
  312. hiddenSpanSubTrees: new Set(),
  313. spanAncestors: new Set(),
  314. filterSpans: undefined,
  315. previousSiblingEndTimestamp: undefined,
  316. event,
  317. isOnlySibling: true,
  318. spanNestedGrouping: undefined,
  319. toggleNestedSpanGroup: undefined,
  320. isNestedSpanGroupExpanded: false,
  321. addTraceBounds: () => {},
  322. removeTraceBounds: () => {},
  323. directParent: null,
  324. });
  325. expect(spans).toEqual(fullWaterfall);
  326. let mockAddTraceBounds = jest.fn();
  327. let mockRemoveTraceBounds = jest.fn();
  328. // embed a child transaction
  329. let promise = spanTreeModel.toggleEmbeddedChildren({
  330. addTraceBounds: mockAddTraceBounds,
  331. removeTraceBounds: mockRemoveTraceBounds,
  332. })({
  333. orgSlug: 'sentry',
  334. eventSlug: 'project:19c403a10af34db2b7d93ad669bb51ed',
  335. });
  336. expect(spanTreeModel.fetchEmbeddedChildrenState).toBe(
  337. 'loading_embedded_transactions'
  338. );
  339. await promise;
  340. expect(mockAddTraceBounds).toHaveBeenCalled();
  341. expect(mockRemoveTraceBounds).not.toHaveBeenCalled();
  342. expect(spanTreeModel.fetchEmbeddedChildrenState).toBe('idle');
  343. spans = spanTreeModel.getSpansList({
  344. operationNameFilters: {
  345. type: 'no_filter',
  346. },
  347. generateBounds,
  348. treeDepth: 0,
  349. isLastSibling: true,
  350. continuingTreeDepths: [],
  351. hiddenSpanSubTrees: new Set(),
  352. spanAncestors: new Set(),
  353. filterSpans: undefined,
  354. previousSiblingEndTimestamp: undefined,
  355. event,
  356. isOnlySibling: true,
  357. spanNestedGrouping: undefined,
  358. toggleNestedSpanGroup: undefined,
  359. isNestedSpanGroupExpanded: false,
  360. addTraceBounds: () => {},
  361. removeTraceBounds: () => {},
  362. directParent: null,
  363. });
  364. const fullWaterfallExpected: EnhancedProcessedSpanType[] = [...fullWaterfall];
  365. fullWaterfallExpected.splice(
  366. 1,
  367. 0,
  368. // Expect these spans to be embedded
  369. {
  370. type: 'span',
  371. span: {
  372. trace_id: '61d2d7c5acf448ffa8e2f8f973e2cd36',
  373. span_id: 'a5702f287954a9ef',
  374. parent_span_id: 'b23703998ae619e7',
  375. start_timestamp: 1622079935.86141,
  376. timestamp: 1622079940.032905,
  377. op: 'something',
  378. description: undefined,
  379. data: {},
  380. status: 'unknown',
  381. },
  382. numOfSpanChildren: 1,
  383. treeDepth: 1,
  384. isLastSibling: false,
  385. continuingTreeDepths: [],
  386. showEmbeddedChildren: false,
  387. toggleEmbeddedChildren: expect.any(Function),
  388. fetchEmbeddedChildrenState: 'idle',
  389. toggleNestedSpanGroup: undefined,
  390. toggleSiblingSpanGroup: undefined,
  391. isEmbeddedTransactionTimeAdjusted: false,
  392. },
  393. {
  394. type: 'span',
  395. span: {
  396. trace_id: '61d2d7c5acf448ffa8e2f8f973e2cd36',
  397. span_id: 'bcbea9f18a11e161',
  398. parent_span_id: 'a5702f287954a9ef',
  399. start_timestamp: 1622079936.90689,
  400. timestamp: 1622079937.227645,
  401. op: 'child',
  402. description: 'something child',
  403. data: {},
  404. status: 'ok',
  405. },
  406. numOfSpanChildren: 0,
  407. treeDepth: 2,
  408. isLastSibling: true,
  409. continuingTreeDepths: [1],
  410. showEmbeddedChildren: false,
  411. toggleEmbeddedChildren: expect.any(Function),
  412. fetchEmbeddedChildrenState: 'idle',
  413. toggleNestedSpanGroup: undefined,
  414. toggleSiblingSpanGroup: undefined,
  415. isEmbeddedTransactionTimeAdjusted: false,
  416. }
  417. );
  418. fullWaterfallExpected[0] = {
  419. ...fullWaterfallExpected[0],
  420. };
  421. assert(fullWaterfallExpected[0].type === 'span');
  422. fullWaterfallExpected[0].numOfSpanChildren += 1;
  423. fullWaterfallExpected[0].showEmbeddedChildren = true;
  424. expect(spans).toEqual(fullWaterfallExpected);
  425. mockAddTraceBounds = jest.fn();
  426. mockRemoveTraceBounds = jest.fn();
  427. // un-embed a child transaction
  428. promise = spanTreeModel.toggleEmbeddedChildren({
  429. addTraceBounds: mockAddTraceBounds,
  430. removeTraceBounds: mockRemoveTraceBounds,
  431. })({
  432. orgSlug: 'sentry',
  433. eventSlug: 'project:19c403a10af34db2b7d93ad669bb51ed',
  434. });
  435. expect(spanTreeModel.fetchEmbeddedChildrenState).toBe('idle');
  436. await promise;
  437. expect(mockAddTraceBounds).not.toHaveBeenCalled();
  438. expect(mockRemoveTraceBounds).toHaveBeenCalled();
  439. expect(spanTreeModel.fetchEmbeddedChildrenState).toBe('idle');
  440. spans = spanTreeModel.getSpansList({
  441. operationNameFilters: {
  442. type: 'no_filter',
  443. },
  444. generateBounds,
  445. treeDepth: 0,
  446. isLastSibling: true,
  447. continuingTreeDepths: [],
  448. hiddenSpanSubTrees: new Set(),
  449. spanAncestors: new Set(),
  450. filterSpans: undefined,
  451. previousSiblingEndTimestamp: undefined,
  452. event,
  453. isOnlySibling: true,
  454. spanNestedGrouping: undefined,
  455. toggleNestedSpanGroup: undefined,
  456. isNestedSpanGroupExpanded: false,
  457. addTraceBounds: () => {},
  458. removeTraceBounds: () => {},
  459. directParent: null,
  460. });
  461. expect(spans).toEqual(fullWaterfall);
  462. });
  463. it('toggleEmbeddedChildren - error state', async () => {
  464. const parsedTrace = parseTrace(event);
  465. const rootSpan = generateRootSpan(parsedTrace);
  466. const spanTreeModel = new SpanTreeModel(rootSpan, parsedTrace.childSpans, api);
  467. const promise = spanTreeModel.toggleEmbeddedChildren({
  468. addTraceBounds: () => {},
  469. removeTraceBounds: () => {},
  470. })({
  471. orgSlug: 'sentry',
  472. eventSlug: 'project:broken',
  473. });
  474. expect(spanTreeModel.fetchEmbeddedChildrenState).toBe(
  475. 'loading_embedded_transactions'
  476. );
  477. await promise;
  478. expect(spanTreeModel.fetchEmbeddedChildrenState).toBe(
  479. 'error_fetching_embedded_transactions'
  480. );
  481. });
  482. it('automatically groups siblings with the same operation and description', () => {
  483. const event2 = {
  484. ...event,
  485. entries: [
  486. {
  487. data: [],
  488. type: EntryType.SPANS,
  489. },
  490. ],
  491. } as EventTransaction;
  492. const spanTemplate = {
  493. timestamp: 1622079937.20331,
  494. start_timestamp: 1622079936.907515,
  495. description: 'test_description',
  496. op: 'test',
  497. span_id: 'a453cc713e5baf9c',
  498. parent_span_id: 'a934857184bdf5a6',
  499. trace_id: '8cbbc19c0f54447ab702f00263262726',
  500. status: 'ok',
  501. tags: {
  502. 'http.status_code': '200',
  503. },
  504. data: {
  505. method: 'GET',
  506. type: 'fetch',
  507. url: '/api/0/internal/health/',
  508. },
  509. };
  510. for (let i = 0; i < 5; i++) {
  511. event2.entries[0].data.push(spanTemplate);
  512. }
  513. const parsedTrace = parseTrace(event2);
  514. const rootSpan = generateRootSpan(parsedTrace);
  515. const spanTreeModel = new SpanTreeModel(rootSpan, parsedTrace.childSpans, api);
  516. const generateBounds = boundsGenerator({
  517. traceStartTimestamp: parsedTrace.traceStartTimestamp,
  518. traceEndTimestamp: parsedTrace.traceEndTimestamp,
  519. viewStart: 0,
  520. viewEnd: 1,
  521. });
  522. const spans = spanTreeModel.getSpansList({
  523. operationNameFilters: {
  524. type: 'no_filter',
  525. },
  526. generateBounds,
  527. treeDepth: 0,
  528. isLastSibling: true,
  529. continuingTreeDepths: [],
  530. hiddenSpanSubTrees: new Set(),
  531. spanAncestors: new Set(),
  532. filterSpans: undefined,
  533. previousSiblingEndTimestamp: undefined,
  534. event,
  535. isOnlySibling: true,
  536. spanNestedGrouping: undefined,
  537. toggleNestedSpanGroup: undefined,
  538. isNestedSpanGroupExpanded: false,
  539. addTraceBounds: () => {},
  540. removeTraceBounds: () => {},
  541. directParent: null,
  542. });
  543. expect(spans.length).toEqual(2);
  544. expect(spans[1].type).toEqual('span_group_siblings');
  545. // If statement here is required to avoid TS linting issues
  546. if (spans[1].type === 'span_group_siblings') {
  547. expect(spans[1].spanSiblingGrouping!.length).toEqual(5);
  548. }
  549. });
  550. it('does not autogroup similar siblings if there are less than 5 in a row', () => {
  551. const event2 = {
  552. ...event,
  553. entries: [
  554. {
  555. data: [],
  556. type: EntryType.SPANS,
  557. },
  558. ],
  559. } as EventTransaction;
  560. const spanTemplate = {
  561. timestamp: 1622079937.20331,
  562. start_timestamp: 1622079936.907515,
  563. description: 'test_description',
  564. op: 'test',
  565. span_id: 'a453cc713e5baf9c',
  566. parent_span_id: 'a934857184bdf5a6',
  567. trace_id: '8cbbc19c0f54447ab702f00263262726',
  568. status: 'ok',
  569. tags: {
  570. 'http.status_code': '200',
  571. },
  572. data: {
  573. method: 'GET',
  574. type: 'fetch',
  575. url: '/api/0/internal/health/',
  576. },
  577. };
  578. for (let i = 0; i < 4; i++) {
  579. event2.entries[0].data.push(spanTemplate);
  580. }
  581. const parsedTrace = parseTrace(event2);
  582. const rootSpan = generateRootSpan(parsedTrace);
  583. const spanTreeModel = new SpanTreeModel(rootSpan, parsedTrace.childSpans, api);
  584. const generateBounds = boundsGenerator({
  585. traceStartTimestamp: parsedTrace.traceStartTimestamp,
  586. traceEndTimestamp: parsedTrace.traceEndTimestamp,
  587. viewStart: 0,
  588. viewEnd: 1,
  589. });
  590. const spans = spanTreeModel.getSpansList({
  591. operationNameFilters: {
  592. type: 'no_filter',
  593. },
  594. generateBounds,
  595. treeDepth: 0,
  596. isLastSibling: true,
  597. continuingTreeDepths: [],
  598. hiddenSpanSubTrees: new Set(),
  599. spanAncestors: new Set(),
  600. filterSpans: undefined,
  601. previousSiblingEndTimestamp: undefined,
  602. event,
  603. isOnlySibling: true,
  604. spanNestedGrouping: undefined,
  605. toggleNestedSpanGroup: undefined,
  606. isNestedSpanGroupExpanded: false,
  607. addTraceBounds: () => {},
  608. removeTraceBounds: () => {},
  609. directParent: null,
  610. });
  611. expect(spans.length).toEqual(5);
  612. spans.forEach(span => expect(span.type).toEqual('span'));
  613. });
  614. it('properly autogroups similar siblings and leaves other siblings ungrouped', () => {
  615. const event2 = {
  616. ...event,
  617. entries: [
  618. {
  619. data: [],
  620. type: EntryType.SPANS,
  621. },
  622. ],
  623. } as EventTransaction;
  624. const groupableSpanTemplate = {
  625. timestamp: 1622079937.20331,
  626. start_timestamp: 1622079936.907515,
  627. description: 'test_description',
  628. op: 'test',
  629. span_id: 'a453cc713e5baf9c',
  630. parent_span_id: 'a934857184bdf5a6',
  631. trace_id: '8cbbc19c0f54447ab702f00263262726',
  632. status: 'ok',
  633. tags: {
  634. 'http.status_code': '200',
  635. },
  636. data: {
  637. method: 'GET',
  638. type: 'fetch',
  639. url: '/api/0/internal/health/',
  640. },
  641. };
  642. const normalSpanTemplate = {
  643. timestamp: 1622079937.20331,
  644. start_timestamp: 1622079936.907515,
  645. description: 'dont_group_me',
  646. op: 'http',
  647. span_id: 'a453cc713e5baf9c',
  648. parent_span_id: 'a934857184bdf5a6',
  649. trace_id: '8cbbc19c0f54447ab702f00263262726',
  650. status: 'ok',
  651. tags: {
  652. 'http.status_code': '200',
  653. },
  654. data: {
  655. method: 'GET',
  656. type: 'fetch',
  657. url: '/api/0/internal/health/',
  658. },
  659. };
  660. for (let i = 0; i < 7; i++) {
  661. event2.entries[0].data.push(groupableSpanTemplate);
  662. }
  663. // This span should not get grouped with the others
  664. event2.entries[0].data.push(normalSpanTemplate);
  665. for (let i = 0; i < 5; i++) {
  666. event2.entries[0].data.push(groupableSpanTemplate);
  667. }
  668. const parsedTrace = parseTrace(event2);
  669. const rootSpan = generateRootSpan(parsedTrace);
  670. const spanTreeModel = new SpanTreeModel(rootSpan, parsedTrace.childSpans, api);
  671. const generateBounds = boundsGenerator({
  672. traceStartTimestamp: parsedTrace.traceStartTimestamp,
  673. traceEndTimestamp: parsedTrace.traceEndTimestamp,
  674. viewStart: 0,
  675. viewEnd: 1,
  676. });
  677. const spans = spanTreeModel.getSpansList({
  678. operationNameFilters: {
  679. type: 'no_filter',
  680. },
  681. generateBounds,
  682. treeDepth: 0,
  683. isLastSibling: true,
  684. continuingTreeDepths: [],
  685. hiddenSpanSubTrees: new Set(),
  686. spanAncestors: new Set(),
  687. filterSpans: undefined,
  688. previousSiblingEndTimestamp: undefined,
  689. event,
  690. isOnlySibling: true,
  691. spanNestedGrouping: undefined,
  692. toggleNestedSpanGroup: undefined,
  693. isNestedSpanGroupExpanded: false,
  694. addTraceBounds: () => {},
  695. removeTraceBounds: () => {},
  696. directParent: null,
  697. });
  698. expect(spans.length).toEqual(4);
  699. expect(spans[1].type).toEqual('span_group_siblings');
  700. expect(spans[2].type).toEqual('span');
  701. expect(spans[3].type).toEqual('span_group_siblings');
  702. });
  703. });