index.spec.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  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.addMockResponse({
  117. url: `/organizations/${organization.slug}/projects/`,
  118. body: [],
  119. });
  120. render(
  121. <QuickTrace
  122. event={makeTransactionEvent(4) as Event}
  123. quickTrace={{
  124. type: 'partial',
  125. trace: makeQuickTraceEvents(4),
  126. }}
  127. anchor="left"
  128. errorDest="issue"
  129. transactionDest="performance"
  130. location={location}
  131. organization={organization}
  132. />
  133. );
  134. const nodes = await screen.findAllByTestId('event-node');
  135. expect(nodes.length).toEqual(1);
  136. expect(nodes[0]).toHaveTextContent('This Event');
  137. });
  138. it('renders partial trace with single child', async function () {
  139. render(
  140. <QuickTrace
  141. event={makeTransactionEvent(4) as Event}
  142. quickTrace={{
  143. type: 'partial',
  144. trace: [...makeQuickTraceEvents(4), ...makeQuickTraceEvents(5)],
  145. }}
  146. anchor="left"
  147. errorDest="issue"
  148. transactionDest="performance"
  149. location={location}
  150. organization={organization}
  151. />
  152. );
  153. const nodes = await screen.findAllByTestId('event-node');
  154. expect(nodes.length).toEqual(2);
  155. ['This Event', '1 Child'].forEach((text, i) =>
  156. expect(nodes[i]).toHaveTextContent(text)
  157. );
  158. });
  159. it('renders partial trace with multiple children', async function () {
  160. MockApiClient.addMockResponse({
  161. url: `/organizations/${organization.slug}/projects/`,
  162. body: [],
  163. });
  164. render(
  165. <QuickTrace
  166. event={makeTransactionEvent(4) as Event}
  167. quickTrace={{
  168. type: 'partial',
  169. trace: [...makeQuickTraceEvents(4), ...makeQuickTraceEvents(5, {n: 3})],
  170. }}
  171. anchor="left"
  172. errorDest="issue"
  173. transactionDest="performance"
  174. location={location}
  175. organization={organization}
  176. />
  177. );
  178. const nodes = await screen.findAllByTestId('event-node');
  179. expect(nodes.length).toEqual(2);
  180. ['This Event', '3 Children'].forEach((text, i) =>
  181. expect(nodes[i]).toHaveTextContent(text)
  182. );
  183. });
  184. it('renders full trace with root as parent', async function () {
  185. render(
  186. <QuickTrace
  187. event={makeTransactionEvent(1) as Event}
  188. quickTrace={{
  189. type: 'partial',
  190. trace: [...makeQuickTraceEvents(0), ...makeQuickTraceEvents(1)],
  191. }}
  192. anchor="left"
  193. errorDest="issue"
  194. transactionDest="performance"
  195. location={location}
  196. organization={organization}
  197. />
  198. );
  199. const nodes = await screen.findAllByTestId('event-node');
  200. expect(nodes.length).toEqual(2);
  201. ['Parent', 'This Event'].forEach((text, i) =>
  202. expect(nodes[i]).toHaveTextContent(text)
  203. );
  204. });
  205. });
  206. describe('Full Trace', function () {
  207. it('renders full trace with single ancestor', async function () {
  208. render(
  209. <QuickTrace
  210. event={makeTransactionEvent(3) as Event}
  211. quickTrace={{
  212. type: 'full',
  213. trace: [
  214. ...makeQuickTraceEvents(0),
  215. ...makeQuickTraceEvents(1),
  216. ...makeQuickTraceEvents(2),
  217. ...makeQuickTraceEvents(3),
  218. ],
  219. }}
  220. anchor="left"
  221. errorDest="issue"
  222. transactionDest="performance"
  223. location={location}
  224. organization={organization}
  225. />
  226. );
  227. const nodes = await screen.findAllByTestId('event-node');
  228. expect(nodes.length).toEqual(4);
  229. ['Root', '1 Ancestor', 'Parent', 'This Event'].forEach((text, i) =>
  230. expect(nodes[i]).toHaveTextContent(text)
  231. );
  232. });
  233. it('renders full trace with multiple ancestors', async function () {
  234. MockApiClient.addMockResponse({
  235. url: `/organizations/${organization.slug}/projects/`,
  236. body: [],
  237. });
  238. render(
  239. <QuickTrace
  240. event={makeTransactionEvent(5) as Event}
  241. quickTrace={{
  242. type: 'full',
  243. trace: [
  244. ...makeQuickTraceEvents(0),
  245. ...makeQuickTraceEvents(1),
  246. ...makeQuickTraceEvents(2),
  247. ...makeQuickTraceEvents(3),
  248. ...makeQuickTraceEvents(4),
  249. ...makeQuickTraceEvents(5),
  250. ],
  251. }}
  252. anchor="left"
  253. errorDest="issue"
  254. transactionDest="performance"
  255. location={location}
  256. organization={organization}
  257. />
  258. );
  259. const nodes = await screen.findAllByTestId('event-node');
  260. expect(nodes.length).toEqual(4);
  261. ['Root', '3 Ancestors', 'Parent', 'This Event'].forEach((text, i) =>
  262. expect(nodes[i]).toHaveTextContent(text)
  263. );
  264. });
  265. it('renders full trace with single descendant', async function () {
  266. render(
  267. <QuickTrace
  268. event={makeTransactionEvent(0) as Event}
  269. quickTrace={{
  270. type: 'full',
  271. trace: [
  272. ...makeQuickTraceEvents(0),
  273. ...makeQuickTraceEvents(1),
  274. ...makeQuickTraceEvents(2),
  275. ],
  276. }}
  277. anchor="left"
  278. errorDest="issue"
  279. transactionDest="performance"
  280. location={location}
  281. organization={organization}
  282. />
  283. );
  284. const nodes = await screen.findAllByTestId('event-node');
  285. expect(nodes.length).toEqual(3);
  286. ['This Event', '1 Child', '1 Descendant'].forEach((text, i) =>
  287. expect(nodes[i]).toHaveTextContent(text)
  288. );
  289. });
  290. it('renders full trace with multiple descendants', async function () {
  291. MockApiClient.addMockResponse({
  292. url: `/organizations/${organization.slug}/projects/`,
  293. body: [],
  294. });
  295. render(
  296. <QuickTrace
  297. event={makeTransactionEvent(0) as Event}
  298. quickTrace={{
  299. type: 'full',
  300. trace: [
  301. ...makeQuickTraceEvents(0),
  302. ...makeQuickTraceEvents(1),
  303. ...makeQuickTraceEvents(2),
  304. ...makeQuickTraceEvents(3),
  305. ...makeQuickTraceEvents(4),
  306. ],
  307. }}
  308. anchor="left"
  309. errorDest="issue"
  310. transactionDest="performance"
  311. location={location}
  312. organization={organization}
  313. />
  314. );
  315. const nodes = await screen.findAllByTestId('event-node');
  316. expect(nodes.length).toEqual(3);
  317. ['This Event', '1 Child', '3 Descendants'].forEach((text, i) =>
  318. expect(nodes[i]).toHaveTextContent(text)
  319. );
  320. });
  321. it('renders full trace', async function () {
  322. MockApiClient.addMockResponse({
  323. url: `/organizations/${organization.slug}/projects/`,
  324. body: [],
  325. });
  326. render(
  327. <QuickTrace
  328. event={makeTransactionEvent(5) as Event}
  329. quickTrace={{
  330. type: 'full',
  331. trace: [
  332. ...makeQuickTraceEvents(0),
  333. ...makeQuickTraceEvents(1),
  334. ...makeQuickTraceEvents(2),
  335. ...makeQuickTraceEvents(3),
  336. ...makeQuickTraceEvents(4),
  337. ...makeQuickTraceEvents(5),
  338. ...makeQuickTraceEvents(6),
  339. ...makeQuickTraceEvents(7),
  340. ...makeQuickTraceEvents(8),
  341. ...makeQuickTraceEvents(9),
  342. ],
  343. }}
  344. anchor="left"
  345. errorDest="issue"
  346. transactionDest="performance"
  347. location={location}
  348. organization={organization}
  349. />
  350. );
  351. const nodes = await screen.findAllByTestId('event-node');
  352. expect(nodes.length).toEqual(6);
  353. ['Root', '3 Ancestors', 'Parent', 'This Event', '1 Child', '3 Descendants'].forEach(
  354. (text, i) => expect(nodes[i]).toHaveTextContent(text)
  355. );
  356. });
  357. });
  358. describe('Event Node Clicks', function () {
  359. it('renders single event targets', async function () {
  360. const routerContext = TestStubs.routerContext();
  361. render(
  362. <QuickTrace
  363. event={makeTransactionEvent(3) as Event}
  364. quickTrace={{
  365. type: 'full',
  366. trace: [
  367. ...makeQuickTraceEvents(0),
  368. ...makeQuickTraceEvents(1),
  369. ...makeQuickTraceEvents(2),
  370. ...makeQuickTraceEvents(3),
  371. ...makeQuickTraceEvents(4),
  372. ...makeQuickTraceEvents(5),
  373. ],
  374. }}
  375. anchor="left"
  376. errorDest="issue"
  377. transactionDest="performance"
  378. location={location}
  379. organization={organization}
  380. />,
  381. {context: routerContext}
  382. );
  383. const nodes = await screen.findAllByTestId('event-node');
  384. expect(nodes.length).toEqual(6);
  385. [
  386. makeTransactionHref('p0', 'e0', 't0', '0'),
  387. makeTransactionHref('p1', 'e1', 't1', '1'),
  388. makeTransactionHref('p2', 'e2', 't2', '2'),
  389. undefined, // the "This Event" node has no target
  390. makeTransactionHref('p4', 'e4', 't4', '4'),
  391. makeTransactionHref('p5', 'e5', 't5', '5'),
  392. ].forEach((target, i) => {
  393. const linkNode = nodes[i].children[0];
  394. if (target) {
  395. expect(linkNode).toHaveAttribute('href', target);
  396. } else {
  397. expect(linkNode).not.toHaveAttribute('href');
  398. }
  399. });
  400. });
  401. it('renders multiple event targets', async function () {
  402. MockApiClient.addMockResponse({
  403. url: `/organizations/${organization.slug}/projects/`,
  404. body: [],
  405. });
  406. render(
  407. <QuickTrace
  408. event={makeTransactionEvent(0) as Event}
  409. quickTrace={{
  410. type: 'full',
  411. trace: [...makeQuickTraceEvents(0), ...makeQuickTraceEvents(1, {n: 3})],
  412. }}
  413. anchor="left"
  414. errorDest="issue"
  415. transactionDest="performance"
  416. location={location}
  417. organization={organization}
  418. />
  419. );
  420. const items = await screen.findAllByTestId('dropdown-item');
  421. expect(items.length).toEqual(3);
  422. // can't easily assert the target is correct since it uses an onClick handler
  423. });
  424. });
  425. });