virtualizedViewManager.spec.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. import type {List} from 'react-virtualized';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
  4. import {EntryType, type Event} from 'sentry/types';
  5. import type {
  6. TraceFullDetailed,
  7. TraceSplitResults,
  8. } from 'sentry/utils/performance/quickTrace/types';
  9. import {VirtualizedViewManager} from 'sentry/views/performance/newTraceDetails/virtualizedViewManager';
  10. import {TraceTree} from './traceTree';
  11. function makeEvent(overrides: Partial<Event> = {}, spans: RawSpanType[] = []): Event {
  12. return {
  13. entries: [{type: EntryType.SPANS, data: spans}],
  14. ...overrides,
  15. } as Event;
  16. }
  17. function makeTrace(
  18. overrides: Partial<TraceSplitResults<TraceFullDetailed>>
  19. ): TraceSplitResults<TraceFullDetailed> {
  20. return {
  21. transactions: [],
  22. orphan_errors: [],
  23. ...overrides,
  24. } as TraceSplitResults<TraceFullDetailed>;
  25. }
  26. function makeTransaction(overrides: Partial<TraceFullDetailed> = {}): TraceFullDetailed {
  27. return {
  28. children: [],
  29. start_timestamp: 0,
  30. timestamp: 1,
  31. transaction: 'transaction',
  32. 'transaction.op': '',
  33. 'transaction.status': '',
  34. ...overrides,
  35. } as TraceFullDetailed;
  36. }
  37. function makeSpan(overrides: Partial<RawSpanType> = {}): RawSpanType {
  38. return {
  39. op: '',
  40. description: '',
  41. span_id: '',
  42. start_timestamp: 0,
  43. timestamp: 10,
  44. ...overrides,
  45. } as RawSpanType;
  46. }
  47. function makeParentAutogroupSpans(): RawSpanType[] {
  48. return [
  49. makeSpan({description: 'span', op: 'db', span_id: 'head_span'}),
  50. makeSpan({
  51. description: 'span',
  52. op: 'db',
  53. span_id: 'middle_span',
  54. parent_span_id: 'head_span',
  55. }),
  56. makeSpan({
  57. description: 'span',
  58. op: 'db',
  59. span_id: 'tail_span',
  60. parent_span_id: 'middle_span',
  61. }),
  62. ];
  63. }
  64. function makeSiblingAutogroupedSpans(): RawSpanType[] {
  65. return [
  66. makeSpan({description: 'span', op: 'db', span_id: 'first_span'}),
  67. makeSpan({description: 'span', op: 'db', span_id: 'middle_span'}),
  68. makeSpan({description: 'span', op: 'db', span_id: 'other_middle_span'}),
  69. makeSpan({description: 'span', op: 'db', span_id: 'another_middle_span'}),
  70. makeSpan({description: 'span', op: 'db', span_id: 'last_span'}),
  71. ];
  72. }
  73. function makeSingleTransactionTree(): TraceTree {
  74. return TraceTree.FromTrace(
  75. makeTrace({
  76. transactions: [
  77. makeTransaction({
  78. transaction: 'transaction',
  79. project_slug: 'project',
  80. event_id: 'event_id',
  81. }),
  82. ],
  83. })
  84. );
  85. }
  86. function makeList(): List {
  87. return {
  88. scrollToRow: jest.fn(),
  89. } as unknown as List;
  90. }
  91. describe('VirtualizedViewManger', () => {
  92. it('initializes space', () => {
  93. const manager = new VirtualizedViewManager({
  94. list: {width: 0.5},
  95. span_list: {width: 0.5},
  96. });
  97. manager.initializeTraceSpace([10_000, 0, 1000, 1]);
  98. expect(manager.trace_space.serialize()).toEqual([0, 0, 1000, 1]);
  99. expect(manager.trace_view.serialize()).toEqual([0, 0, 1000, 1]);
  100. });
  101. it('initializes physical space', () => {
  102. const manager = new VirtualizedViewManager({
  103. list: {width: 0.5},
  104. span_list: {width: 0.5},
  105. });
  106. manager.initializePhysicalSpace(1000, 1);
  107. expect(manager.container_physical_space.serialize()).toEqual([0, 0, 1000, 1]);
  108. expect(manager.trace_physical_space.serialize()).toEqual([0, 0, 500, 1]);
  109. });
  110. describe('computeSpanCSSMatrixTransform', () => {
  111. it('enforces min scaling', () => {
  112. const manager = new VirtualizedViewManager({
  113. list: {width: 0},
  114. span_list: {width: 1},
  115. });
  116. manager.initializeTraceSpace([0, 0, 1000, 1]);
  117. manager.initializePhysicalSpace(1000, 1);
  118. expect(manager.computeSpanCSSMatrixTransform([0, 0.1])).toEqual([
  119. 0.001, 0, 0, 1, 0, 0,
  120. ]);
  121. });
  122. it('computes width scaling correctly', () => {
  123. const manager = new VirtualizedViewManager({
  124. list: {width: 0},
  125. span_list: {width: 1},
  126. });
  127. manager.initializeTraceSpace([0, 0, 100, 1]);
  128. manager.initializePhysicalSpace(1000, 1);
  129. expect(manager.computeSpanCSSMatrixTransform([0, 100])).toEqual([1, 0, 0, 1, 0, 0]);
  130. });
  131. it('computes x position correctly', () => {
  132. const manager = new VirtualizedViewManager({
  133. list: {width: 0},
  134. span_list: {width: 1},
  135. });
  136. manager.initializeTraceSpace([0, 0, 1000, 1]);
  137. manager.initializePhysicalSpace(1000, 1);
  138. expect(manager.computeSpanCSSMatrixTransform([50, 1000])).toEqual([
  139. 1, 0, 0, 1, 50, 0,
  140. ]);
  141. });
  142. it('computes span x position correctly', () => {
  143. const manager = new VirtualizedViewManager({
  144. list: {width: 0},
  145. span_list: {width: 1},
  146. });
  147. manager.initializeTraceSpace([0, 0, 1000, 1]);
  148. manager.initializePhysicalSpace(1000, 1);
  149. expect(manager.computeSpanCSSMatrixTransform([50, 1000])).toEqual([
  150. 1, 0, 0, 1, 50, 0,
  151. ]);
  152. });
  153. describe('when start is not 0', () => {
  154. it('computes width scaling correctly', () => {
  155. const manager = new VirtualizedViewManager({
  156. list: {width: 0},
  157. span_list: {width: 1},
  158. });
  159. manager.initializeTraceSpace([100, 0, 100, 1]);
  160. manager.initializePhysicalSpace(1000, 1);
  161. expect(manager.computeSpanCSSMatrixTransform([100, 100])).toEqual([
  162. 1, 0, 0, 1, 0, 0,
  163. ]);
  164. });
  165. it('computes x position correctly when view is offset', () => {
  166. const manager = new VirtualizedViewManager({
  167. list: {width: 0},
  168. span_list: {width: 1},
  169. });
  170. manager.initializeTraceSpace([100, 0, 100, 1]);
  171. manager.initializePhysicalSpace(1000, 1);
  172. expect(manager.computeSpanCSSMatrixTransform([100, 100])).toEqual([
  173. 1, 0, 0, 1, 0, 0,
  174. ]);
  175. });
  176. });
  177. });
  178. describe('computeTransformXFromTimestamp', () => {
  179. it('computes x position correctly', () => {
  180. const manager = new VirtualizedViewManager({
  181. list: {width: 0},
  182. span_list: {width: 1},
  183. });
  184. manager.initializeTraceSpace([0, 0, 1000, 1]);
  185. manager.initializePhysicalSpace(1000, 1);
  186. expect(manager.computeTransformXFromTimestamp(50)).toEqual(50);
  187. });
  188. it('computes x position correctly when view is offset', () => {
  189. const manager = new VirtualizedViewManager({
  190. list: {width: 0},
  191. span_list: {width: 1},
  192. });
  193. manager.initializeTraceSpace([50, 0, 1000, 1]);
  194. manager.initializePhysicalSpace(1000, 1);
  195. manager.trace_view.x = 50;
  196. expect(manager.computeTransformXFromTimestamp(50)).toEqual(0);
  197. });
  198. it('when view is offset and scaled', () => {
  199. const manager = new VirtualizedViewManager({
  200. list: {width: 0},
  201. span_list: {width: 1},
  202. });
  203. manager.initializeTraceSpace([50, 0, 100, 1]);
  204. manager.initializePhysicalSpace(1000, 1);
  205. manager.trace_view.width = 50;
  206. manager.trace_view.x = 50;
  207. expect(Math.round(manager.computeTransformXFromTimestamp(75))).toEqual(500);
  208. });
  209. });
  210. describe('getConfigSpaceCursor', () => {
  211. it('returns the correct x position', () => {
  212. const manager = new VirtualizedViewManager({
  213. list: {width: 0},
  214. span_list: {width: 1},
  215. });
  216. manager.initializeTraceSpace([0, 0, 100, 1]);
  217. manager.initializePhysicalSpace(1000, 1);
  218. expect(manager.getConfigSpaceCursor({x: 500, y: 0})).toEqual([50, 0]);
  219. });
  220. it('returns the correct x position when view scaled', () => {
  221. const manager = new VirtualizedViewManager({
  222. list: {width: 0},
  223. span_list: {width: 1},
  224. });
  225. manager.initializeTraceSpace([0, 0, 100, 1]);
  226. manager.initializePhysicalSpace(1000, 1);
  227. manager.trace_view.x = 50;
  228. manager.trace_view.width = 50;
  229. expect(manager.getConfigSpaceCursor({x: 500, y: 0})).toEqual([75, 0]);
  230. });
  231. it('returns the correct x position when view is offset', () => {
  232. const manager = new VirtualizedViewManager({
  233. list: {width: 0},
  234. span_list: {width: 1},
  235. });
  236. manager.initializeTraceSpace([0, 0, 100, 1]);
  237. manager.initializePhysicalSpace(1000, 1);
  238. manager.trace_view.x = 50;
  239. expect(manager.getConfigSpaceCursor({x: 500, y: 0})).toEqual([100, 0]);
  240. });
  241. });
  242. describe('text positioning', () => {
  243. describe('non offset view', () => {
  244. it.todo('span is left');
  245. it.todo('span is right');
  246. it.todo('span left and over center');
  247. });
  248. describe('offset view', () => {
  249. it.todo('span is left');
  250. it.todo('span is right');
  251. it.todo('span left and over center');
  252. });
  253. describe('non offset zoomed in view', () => {
  254. it.todo('span is left');
  255. it.todo('span is right');
  256. it.todo('span left and over center');
  257. });
  258. describe('offset zoomed in view', () => {
  259. it.todo('span is left');
  260. it.todo('span is right');
  261. it.todo('span left and over center');
  262. });
  263. });
  264. describe('scrollToPath', () => {
  265. const organization = OrganizationFixture();
  266. const api = new MockApiClient();
  267. const manager = new VirtualizedViewManager({
  268. list: {width: 0.5},
  269. span_list: {width: 0.5},
  270. });
  271. it('scrolls to transaction', async () => {
  272. const tree = TraceTree.FromTrace(
  273. makeTrace({
  274. transactions: [
  275. makeTransaction(),
  276. makeTransaction({
  277. event_id: 'event_id',
  278. children: [],
  279. }),
  280. ],
  281. })
  282. );
  283. manager.virtualizedList = makeList();
  284. const result = await manager.scrollToPath(tree, ['txn:event_id'], () => void 0, {
  285. api: api,
  286. organization,
  287. });
  288. expect(result).toBe(tree.list[2]);
  289. expect(manager.virtualizedList.scrollToRow).toHaveBeenCalledWith(2);
  290. });
  291. it('scrolls to nested transaction', async () => {
  292. const tree = TraceTree.FromTrace(
  293. makeTrace({
  294. transactions: [
  295. makeTransaction({
  296. event_id: 'root',
  297. children: [
  298. makeTransaction({
  299. event_id: 'child',
  300. children: [
  301. makeTransaction({
  302. event_id: 'event_id',
  303. children: [],
  304. }),
  305. ],
  306. }),
  307. ],
  308. }),
  309. ],
  310. })
  311. );
  312. manager.virtualizedList = makeList();
  313. expect(tree.list[tree.list.length - 1].path).toEqual([
  314. 'txn:event_id',
  315. 'txn:child',
  316. 'txn:root',
  317. ]);
  318. const result = await manager.scrollToPath(
  319. tree,
  320. ['txn:event_id', 'txn:child', 'txn:root'],
  321. () => void 0,
  322. {
  323. api: api,
  324. organization,
  325. }
  326. );
  327. expect(result).toBe(tree.list[tree.list.length - 1]);
  328. expect(manager.virtualizedList.scrollToRow).toHaveBeenCalledWith(3);
  329. });
  330. it('scrolls to spans of expanded transaction', async () => {
  331. manager.virtualizedList = makeList();
  332. const tree = TraceTree.FromTrace(
  333. makeTrace({
  334. transactions: [
  335. makeTransaction({
  336. event_id: 'event_id',
  337. project_slug: 'project_slug',
  338. children: [],
  339. }),
  340. ],
  341. })
  342. );
  343. MockApiClient.addMockResponse({
  344. url: '/organizations/org-slug/events/project_slug:event_id/',
  345. method: 'GET',
  346. body: makeEvent(undefined, [makeSpan({span_id: 'span_id'})]),
  347. });
  348. const result = await manager.scrollToPath(
  349. tree,
  350. ['span:span_id', 'txn:event_id'],
  351. () => void 0,
  352. {
  353. api: api,
  354. organization,
  355. }
  356. );
  357. expect(tree.list[1].zoomedIn).toBe(true);
  358. expect(result).toBeTruthy();
  359. expect(result).toBe(tree.list[2]);
  360. expect(manager.virtualizedList.scrollToRow).toHaveBeenCalledWith(2);
  361. });
  362. it('scrolls to span -> transaction -> span -> transaction', async () => {
  363. manager.virtualizedList = makeList();
  364. const tree = TraceTree.FromTrace(
  365. makeTrace({
  366. transactions: [
  367. makeTransaction({
  368. event_id: 'event_id',
  369. project_slug: 'project_slug',
  370. children: [
  371. makeTransaction({
  372. parent_span_id: 'child_span',
  373. event_id: 'child_event_id',
  374. project_slug: 'project_slug',
  375. }),
  376. ],
  377. }),
  378. ],
  379. })
  380. );
  381. MockApiClient.addMockResponse({
  382. url: '/organizations/org-slug/events/project_slug:event_id/',
  383. method: 'GET',
  384. body: makeEvent(undefined, [
  385. makeSpan({span_id: 'other_child_span'}),
  386. makeSpan({span_id: 'child_span'}),
  387. ]),
  388. });
  389. MockApiClient.addMockResponse({
  390. url: '/organizations/org-slug/events/project_slug:child_event_id/',
  391. method: 'GET',
  392. body: makeEvent(undefined, [makeSpan({span_id: 'other_child_span'})]),
  393. });
  394. const result = await manager.scrollToPath(
  395. tree,
  396. ['span:other_child_span', 'txn:child_event_id', 'txn:event_id'],
  397. () => void 0,
  398. {
  399. api: api,
  400. organization,
  401. }
  402. );
  403. expect(result).toBeTruthy();
  404. expect(manager.virtualizedList.scrollToRow).toHaveBeenCalledWith(3);
  405. });
  406. describe('scrolls to directly autogrouped node', () => {
  407. for (const headOrTailId of ['head_span', 'tail_span']) {
  408. it('scrolls to directly autogrouped node head', async () => {
  409. manager.virtualizedList = makeList();
  410. const tree = makeSingleTransactionTree();
  411. MockApiClient.addMockResponse({
  412. url: '/organizations/org-slug/events/project:event_id/',
  413. method: 'GET',
  414. body: makeEvent({}, makeParentAutogroupSpans()),
  415. });
  416. const result = await manager.scrollToPath(
  417. tree,
  418. [`ag:${headOrTailId}`, 'txn:event_id'],
  419. () => void 0,
  420. {
  421. api: api,
  422. organization,
  423. }
  424. );
  425. expect(result).toBeTruthy();
  426. expect(manager.virtualizedList.scrollToRow).toHaveBeenCalledWith(2);
  427. });
  428. }
  429. for (const headOrTailId of ['head_span', 'tail_span']) {
  430. it('scrolls to child of autogrouped node head or tail', async () => {
  431. manager.virtualizedList = makeList();
  432. const tree = makeSingleTransactionTree();
  433. MockApiClient.addMockResponse({
  434. url: '/organizations/org-slug/events/project:event_id/',
  435. method: 'GET',
  436. body: makeEvent({}, makeParentAutogroupSpans()),
  437. });
  438. const result = await manager.scrollToPath(
  439. tree,
  440. ['span:middle_span', `ag:${headOrTailId}`, 'txn:event_id'],
  441. () => void 0,
  442. {
  443. api: api,
  444. organization,
  445. }
  446. );
  447. expect(result).toBeTruthy();
  448. expect(manager.virtualizedList.scrollToRow).toHaveBeenCalledWith(4);
  449. });
  450. }
  451. });
  452. describe('sibling autogrouping', () => {
  453. it('scrolls to sibling autogrouped node', async () => {
  454. manager.virtualizedList = makeList();
  455. const tree = makeSingleTransactionTree();
  456. MockApiClient.addMockResponse({
  457. url: '/organizations/org-slug/events/project:event_id/',
  458. method: 'GET',
  459. body: makeEvent({}, makeSiblingAutogroupedSpans()),
  460. });
  461. const result = await manager.scrollToPath(
  462. tree,
  463. [`ag:first_span`, 'txn:event_id'],
  464. () => void 0,
  465. {
  466. api: api,
  467. organization,
  468. }
  469. );
  470. expect(result).toBeTruthy();
  471. expect(manager.virtualizedList.scrollToRow).toHaveBeenCalledWith(2);
  472. });
  473. it('scrolls to child span of sibling autogrouped node', async () => {
  474. manager.virtualizedList = makeList();
  475. const tree = makeSingleTransactionTree();
  476. MockApiClient.addMockResponse({
  477. url: '/organizations/org-slug/events/project:event_id/',
  478. method: 'GET',
  479. body: makeEvent({}, makeSiblingAutogroupedSpans()),
  480. });
  481. const result = await manager.scrollToPath(
  482. tree,
  483. ['span:middle_span', `ag:first_span`, 'txn:event_id'],
  484. () => void 0,
  485. {
  486. api: api,
  487. organization,
  488. }
  489. );
  490. expect(result).toBeTruthy();
  491. expect(manager.virtualizedList.scrollToRow).toHaveBeenCalledWith(4);
  492. });
  493. it.todo('scrolls to orphan transactions');
  494. it.todo('scrolls to orphan transactions child span');
  495. });
  496. });
  497. });