index.spec.tsx 14 KB

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