index.spec.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen} from 'sentry-test/reactTestingLibrary';
  3. import QuickTrace from 'sentry/components/quickTrace';
  4. import {Event} from 'sentry/types/event';
  5. import {QuickTraceEvent} from 'sentry/utils/performance/quickTrace/types';
  6. describe('Quick Trace', function () {
  7. let location;
  8. let organization;
  9. const initialize = () => {
  10. const context = initializeOrg();
  11. organization = context.organization;
  12. };
  13. function makeQuickTraceEvents(generation, {n = 1, parentId = null} = {}) {
  14. const events: QuickTraceEvent[] = [];
  15. for (let i = 0; i < n; i++) {
  16. const suffix = n > 1 ? `-${i}` : '';
  17. events.push({
  18. event_id: `e${generation}${suffix}`,
  19. generation,
  20. span_id: `s${generation}${suffix}`,
  21. transaction: `t${generation}${suffix}`,
  22. 'transaction.duration': 1234,
  23. project_id: generation,
  24. project_slug: `p${generation}`,
  25. parent_event_id:
  26. generation === 0 ? null : parentId === null ? `e${generation - 1}` : parentId,
  27. parent_span_id:
  28. generation === 0
  29. ? null
  30. : parentId === null
  31. ? `s${generation - 1}${parentId}`
  32. : `s${parentId}`,
  33. });
  34. }
  35. return events;
  36. }
  37. function makeTransactionEvent(id) {
  38. return {
  39. id: `e${id}`,
  40. type: 'transaction',
  41. startTimestamp: 1615921516.132774,
  42. endTimestamp: 1615921517.924861,
  43. };
  44. }
  45. function makeTransactionHref(
  46. pid: string,
  47. eid: string,
  48. transaction: string,
  49. project: string
  50. ) {
  51. return `/organizations/${organization.slug}/performance/${pid}:${eid}/?project=${project}&transaction=${transaction}`;
  52. }
  53. beforeEach(function () {
  54. initialize();
  55. location = {
  56. pathname: '/',
  57. query: {},
  58. };
  59. });
  60. describe('Empty Trace', function () {
  61. it('renders nothing for empty trace', function () {
  62. const {container} = render(
  63. <QuickTrace
  64. event={makeTransactionEvent(1) as Event}
  65. quickTrace={{
  66. type: 'empty',
  67. trace: [],
  68. }}
  69. anchor="left"
  70. errorDest="issue"
  71. transactionDest="performance"
  72. location={location}
  73. organization={organization}
  74. />
  75. );
  76. expect(container).toHaveTextContent('\u2014');
  77. });
  78. });
  79. describe('Partial Trace', function () {
  80. it('renders nothing when partial trace is empty', function () {
  81. const {container} = render(
  82. <QuickTrace
  83. event={makeTransactionEvent(1) as Event}
  84. quickTrace={{
  85. type: 'partial',
  86. trace: null,
  87. }}
  88. anchor="left"
  89. errorDest="issue"
  90. transactionDest="performance"
  91. location={location}
  92. organization={organization}
  93. />
  94. );
  95. expect(container).toHaveTextContent('\u2014');
  96. });
  97. it('renders nothing when partial trace missing current event', function () {
  98. const {container} = render(
  99. <QuickTrace
  100. event={makeTransactionEvent('not-1') as Event}
  101. quickTrace={{
  102. type: 'partial',
  103. trace: makeQuickTraceEvents(1),
  104. }}
  105. anchor="left"
  106. errorDest="issue"
  107. transactionDest="performance"
  108. location={location}
  109. organization={organization}
  110. />
  111. );
  112. expect(container).toHaveTextContent('\u2014');
  113. });
  114. // TODO
  115. it('renders partial trace with no children', async function () {
  116. MockApiClient.warnOnMissingMocks();
  117. render(
  118. <QuickTrace
  119. event={makeTransactionEvent(4) as Event}
  120. quickTrace={{
  121. type: 'partial',
  122. trace: makeQuickTraceEvents(4),
  123. }}
  124. anchor="left"
  125. errorDest="issue"
  126. transactionDest="performance"
  127. location={location}
  128. organization={organization}
  129. />
  130. );
  131. const nodes = await screen.findAllByTestId('event-node');
  132. expect(nodes.length).toEqual(1);
  133. expect(nodes[0]).toHaveTextContent('This Event');
  134. });
  135. it('renders partial trace with single child', async function () {
  136. render(
  137. <QuickTrace
  138. event={makeTransactionEvent(4) as Event}
  139. quickTrace={{
  140. type: 'partial',
  141. trace: [...makeQuickTraceEvents(4), ...makeQuickTraceEvents(5)],
  142. }}
  143. anchor="left"
  144. errorDest="issue"
  145. transactionDest="performance"
  146. location={location}
  147. organization={organization}
  148. />
  149. );
  150. const nodes = await screen.findAllByTestId('event-node');
  151. expect(nodes.length).toEqual(2);
  152. ['This Event', '1 Child'].forEach((text, i) =>
  153. expect(nodes[i]).toHaveTextContent(text)
  154. );
  155. });
  156. it('renders partial trace with multiple children', async function () {
  157. MockApiClient.warnOnMissingMocks();
  158. render(
  159. <QuickTrace
  160. event={makeTransactionEvent(4) as Event}
  161. quickTrace={{
  162. type: 'partial',
  163. trace: [...makeQuickTraceEvents(4), ...makeQuickTraceEvents(5, {n: 3})],
  164. }}
  165. anchor="left"
  166. errorDest="issue"
  167. transactionDest="performance"
  168. location={location}
  169. organization={organization}
  170. />
  171. );
  172. const nodes = await screen.findAllByTestId('event-node');
  173. expect(nodes.length).toEqual(2);
  174. ['This Event', '3 Children'].forEach((text, i) =>
  175. expect(nodes[i]).toHaveTextContent(text)
  176. );
  177. });
  178. it('renders full trace with root as parent', async function () {
  179. render(
  180. <QuickTrace
  181. event={makeTransactionEvent(1) as Event}
  182. quickTrace={{
  183. type: 'partial',
  184. trace: [...makeQuickTraceEvents(0), ...makeQuickTraceEvents(1)],
  185. }}
  186. anchor="left"
  187. errorDest="issue"
  188. transactionDest="performance"
  189. location={location}
  190. organization={organization}
  191. />
  192. );
  193. const nodes = await screen.findAllByTestId('event-node');
  194. expect(nodes.length).toEqual(2);
  195. ['Parent', 'This Event'].forEach((text, i) =>
  196. expect(nodes[i]).toHaveTextContent(text)
  197. );
  198. });
  199. });
  200. describe('Full Trace', function () {
  201. it('renders full trace with single ancestor', async function () {
  202. render(
  203. <QuickTrace
  204. event={makeTransactionEvent(3) as Event}
  205. quickTrace={{
  206. type: 'full',
  207. trace: [
  208. ...makeQuickTraceEvents(0),
  209. ...makeQuickTraceEvents(1),
  210. ...makeQuickTraceEvents(2),
  211. ...makeQuickTraceEvents(3),
  212. ],
  213. }}
  214. anchor="left"
  215. errorDest="issue"
  216. transactionDest="performance"
  217. location={location}
  218. organization={organization}
  219. />
  220. );
  221. const nodes = await screen.findAllByTestId('event-node');
  222. expect(nodes.length).toEqual(4);
  223. ['Root', '1 Ancestor', 'Parent', 'This Event'].forEach((text, i) =>
  224. expect(nodes[i]).toHaveTextContent(text)
  225. );
  226. });
  227. it('renders full trace with multiple ancestors', async function () {
  228. MockApiClient.warnOnMissingMocks();
  229. render(
  230. <QuickTrace
  231. event={makeTransactionEvent(5) as Event}
  232. quickTrace={{
  233. type: 'full',
  234. trace: [
  235. ...makeQuickTraceEvents(0),
  236. ...makeQuickTraceEvents(1),
  237. ...makeQuickTraceEvents(2),
  238. ...makeQuickTraceEvents(3),
  239. ...makeQuickTraceEvents(4),
  240. ...makeQuickTraceEvents(5),
  241. ],
  242. }}
  243. anchor="left"
  244. errorDest="issue"
  245. transactionDest="performance"
  246. location={location}
  247. organization={organization}
  248. />
  249. );
  250. const nodes = await screen.findAllByTestId('event-node');
  251. expect(nodes.length).toEqual(4);
  252. ['Root', '3 Ancestors', 'Parent', 'This Event'].forEach((text, i) =>
  253. expect(nodes[i]).toHaveTextContent(text)
  254. );
  255. });
  256. it('renders full trace with single descendant', async function () {
  257. render(
  258. <QuickTrace
  259. event={makeTransactionEvent(0) as Event}
  260. quickTrace={{
  261. type: 'full',
  262. trace: [
  263. ...makeQuickTraceEvents(0),
  264. ...makeQuickTraceEvents(1),
  265. ...makeQuickTraceEvents(2),
  266. ],
  267. }}
  268. anchor="left"
  269. errorDest="issue"
  270. transactionDest="performance"
  271. location={location}
  272. organization={organization}
  273. />
  274. );
  275. const nodes = await screen.findAllByTestId('event-node');
  276. expect(nodes.length).toEqual(3);
  277. ['This Event', '1 Child', '1 Descendant'].forEach((text, i) =>
  278. expect(nodes[i]).toHaveTextContent(text)
  279. );
  280. });
  281. it('renders full trace with multiple descendants', async function () {
  282. MockApiClient.warnOnMissingMocks();
  283. render(
  284. <QuickTrace
  285. event={makeTransactionEvent(0) as Event}
  286. quickTrace={{
  287. type: 'full',
  288. trace: [
  289. ...makeQuickTraceEvents(0),
  290. ...makeQuickTraceEvents(1),
  291. ...makeQuickTraceEvents(2),
  292. ...makeQuickTraceEvents(3),
  293. ...makeQuickTraceEvents(4),
  294. ],
  295. }}
  296. anchor="left"
  297. errorDest="issue"
  298. transactionDest="performance"
  299. location={location}
  300. organization={organization}
  301. />
  302. );
  303. const nodes = await screen.findAllByTestId('event-node');
  304. expect(nodes.length).toEqual(3);
  305. ['This Event', '1 Child', '3 Descendants'].forEach((text, i) =>
  306. expect(nodes[i]).toHaveTextContent(text)
  307. );
  308. });
  309. it('renders full trace', async function () {
  310. MockApiClient.warnOnMissingMocks();
  311. render(
  312. <QuickTrace
  313. event={makeTransactionEvent(5) as Event}
  314. quickTrace={{
  315. type: 'full',
  316. trace: [
  317. ...makeQuickTraceEvents(0),
  318. ...makeQuickTraceEvents(1),
  319. ...makeQuickTraceEvents(2),
  320. ...makeQuickTraceEvents(3),
  321. ...makeQuickTraceEvents(4),
  322. ...makeQuickTraceEvents(5),
  323. ...makeQuickTraceEvents(6),
  324. ...makeQuickTraceEvents(7),
  325. ...makeQuickTraceEvents(8),
  326. ...makeQuickTraceEvents(9),
  327. ],
  328. }}
  329. anchor="left"
  330. errorDest="issue"
  331. transactionDest="performance"
  332. location={location}
  333. organization={organization}
  334. />
  335. );
  336. const nodes = await screen.findAllByTestId('event-node');
  337. expect(nodes.length).toEqual(6);
  338. ['Root', '3 Ancestors', 'Parent', 'This Event', '1 Child', '3 Descendants'].forEach(
  339. (text, i) => expect(nodes[i]).toHaveTextContent(text)
  340. );
  341. });
  342. });
  343. describe('Event Node Clicks', function () {
  344. it('renders single event targets', async function () {
  345. const routerContext = TestStubs.routerContext();
  346. render(
  347. <QuickTrace
  348. event={makeTransactionEvent(3) as Event}
  349. quickTrace={{
  350. type: 'full',
  351. trace: [
  352. ...makeQuickTraceEvents(0),
  353. ...makeQuickTraceEvents(1),
  354. ...makeQuickTraceEvents(2),
  355. ...makeQuickTraceEvents(3),
  356. ...makeQuickTraceEvents(4),
  357. ...makeQuickTraceEvents(5),
  358. ],
  359. }}
  360. anchor="left"
  361. errorDest="issue"
  362. transactionDest="performance"
  363. location={location}
  364. organization={organization}
  365. />,
  366. {context: routerContext}
  367. );
  368. const nodes = await screen.findAllByTestId('event-node');
  369. expect(nodes.length).toEqual(6);
  370. [
  371. makeTransactionHref('p0', 'e0', 't0', '0'),
  372. makeTransactionHref('p1', 'e1', 't1', '1'),
  373. makeTransactionHref('p2', 'e2', 't2', '2'),
  374. undefined, // the "This Event" node has no target
  375. makeTransactionHref('p4', 'e4', 't4', '4'),
  376. makeTransactionHref('p5', 'e5', 't5', '5'),
  377. ].forEach((target, i) => {
  378. const linkNode = nodes[i].children[0];
  379. if (target) {
  380. expect(linkNode).toHaveAttribute('href', target);
  381. } else {
  382. expect(linkNode).not.toHaveAttribute('href');
  383. }
  384. });
  385. });
  386. it('renders multiple event targets', async function () {
  387. MockApiClient.warnOnMissingMocks();
  388. render(
  389. <QuickTrace
  390. event={makeTransactionEvent(0) as Event}
  391. quickTrace={{
  392. type: 'full',
  393. trace: [...makeQuickTraceEvents(0), ...makeQuickTraceEvents(1, {n: 3})],
  394. }}
  395. anchor="left"
  396. errorDest="issue"
  397. transactionDest="performance"
  398. location={location}
  399. organization={organization}
  400. />
  401. );
  402. const items = await screen.findAllByTestId('dropdown-item');
  403. expect(items.length).toEqual(3);
  404. // can't easily assert the target is correct since it uses an onClick handler
  405. });
  406. });
  407. });