spanEvidenceKeyValueList.spec.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877
  1. import {EntryRequestFixture} from 'sentry-fixture/eventEntry';
  2. import {
  3. MockSpan,
  4. ProblemSpan,
  5. TransactionEventBuilder,
  6. } from 'sentry-test/performance/utils';
  7. import {render, screen} from 'sentry-test/reactTestingLibrary';
  8. import {IssueType} from 'sentry/types/group';
  9. import {
  10. extractQueryParameters,
  11. extractSpanURLString,
  12. SpanEvidenceKeyValueList,
  13. } from './spanEvidenceKeyValueList';
  14. describe('SpanEvidenceKeyValueList', () => {
  15. const projectSlug = 'project';
  16. describe('N+1 Database Queries', () => {
  17. const builder = new TransactionEventBuilder('a1', '/');
  18. builder.getEventFixture().projectID = '123';
  19. const parentSpan = new MockSpan({
  20. startTimestamp: 0,
  21. endTimestamp: 0.2,
  22. op: 'http.server',
  23. problemSpan: ProblemSpan.PARENT,
  24. });
  25. parentSpan.addChild({
  26. startTimestamp: 0.01,
  27. endTimestamp: 2.1,
  28. op: 'db',
  29. description: 'SELECT * FROM books',
  30. hash: 'aaa',
  31. problemSpan: ProblemSpan.OFFENDER,
  32. });
  33. parentSpan.addChild({
  34. startTimestamp: 2.1,
  35. endTimestamp: 4.0,
  36. op: 'db',
  37. description: 'SELECT * FROM books',
  38. hash: 'aaa',
  39. problemSpan: ProblemSpan.OFFENDER,
  40. });
  41. builder.addSpan(parentSpan);
  42. it('Renders relevant fields', () => {
  43. render(
  44. <SpanEvidenceKeyValueList
  45. event={builder.getEventFixture()}
  46. projectSlug={projectSlug}
  47. />
  48. );
  49. expect(screen.getByRole('cell', {name: 'Transaction'})).toBeInTheDocument();
  50. expect(
  51. screen.getByTestId('span-evidence-key-value-list.transaction')
  52. ).toHaveTextContent('/');
  53. expect(
  54. screen.getByTestId('span-evidence-key-value-list.transaction').querySelector('a')
  55. ).toHaveAttribute(
  56. 'href',
  57. `/organizations/org-slug/performance/summary/?project=123&referrer=performance-transaction-summary&transaction=%2F&unselectedSeries=p100%28%29&unselectedSeries=avg%28%29`
  58. );
  59. expect(
  60. screen.getByRole('button', {
  61. name: /view full event/i,
  62. })
  63. ).toHaveAttribute('href', '/organizations/org-slug/performance/project:a1/');
  64. expect(screen.getByRole('cell', {name: 'Parent Span'})).toBeInTheDocument();
  65. expect(
  66. screen.getByTestId('span-evidence-key-value-list.parent-span')
  67. ).toHaveTextContent('http.server');
  68. expect(screen.getByRole('cell', {name: 'Repeating Spans (2)'})).toBeInTheDocument();
  69. expect(
  70. screen.getByTestId(/span-evidence-key-value-list.repeating-spans/)
  71. ).toHaveTextContent('SELECT * FROM books');
  72. expect(
  73. screen.queryByTestId('span-evidence-key-value-list.')
  74. ).not.toBeInTheDocument();
  75. expect(screen.queryByRole('cell', {name: 'Parameter'})).not.toBeInTheDocument();
  76. expect(
  77. screen.queryByTestId('span-evidence-key-value-list.problem-parameters')
  78. ).not.toBeInTheDocument();
  79. });
  80. });
  81. describe('N+1 Database Queries with occurrences', () => {
  82. const builder = new TransactionEventBuilder('a1', '/', undefined, undefined, true);
  83. builder.getEventFixture().projectID = '123';
  84. const parentSpan = new MockSpan({
  85. startTimestamp: 0,
  86. endTimestamp: 0.2,
  87. op: 'http.server',
  88. problemSpan: ProblemSpan.PARENT,
  89. });
  90. parentSpan.addChild({
  91. startTimestamp: 0.01,
  92. endTimestamp: 2.1,
  93. op: 'db',
  94. description: 'SELECT * FROM books',
  95. hash: 'aaa',
  96. problemSpan: ProblemSpan.OFFENDER,
  97. });
  98. parentSpan.addChild({
  99. startTimestamp: 2.1,
  100. endTimestamp: 4.0,
  101. op: 'db',
  102. description: 'SELECT * FROM books',
  103. hash: 'aaa',
  104. problemSpan: ProblemSpan.OFFENDER,
  105. });
  106. builder.addSpan(parentSpan);
  107. it('Renders relevant fields', () => {
  108. render(
  109. <SpanEvidenceKeyValueList
  110. event={builder.getEventFixture()}
  111. projectSlug={projectSlug}
  112. />
  113. );
  114. expect(screen.getByRole('cell', {name: 'Transaction'})).toBeInTheDocument();
  115. expect(
  116. screen.getByTestId('span-evidence-key-value-list.transaction')
  117. ).toHaveTextContent('/');
  118. expect(
  119. screen.getByTestId('span-evidence-key-value-list.transaction').querySelector('a')
  120. ).toHaveAttribute(
  121. 'href',
  122. `/organizations/org-slug/performance/summary/?project=123&referrer=performance-transaction-summary&transaction=%2F&unselectedSeries=p100%28%29&unselectedSeries=avg%28%29`
  123. );
  124. expect(
  125. screen.getByRole('button', {
  126. name: /view full event/i,
  127. })
  128. ).toHaveAttribute('href', '/organizations/org-slug/performance/project:a1/');
  129. expect(screen.getByRole('cell', {name: 'Parent Span'})).toBeInTheDocument();
  130. expect(
  131. screen.getByTestId('span-evidence-key-value-list.parent-span')
  132. ).toHaveTextContent('http.server');
  133. expect(screen.getByRole('cell', {name: 'Repeating Spans (2)'})).toBeInTheDocument();
  134. expect(
  135. screen.getByTestId(/span-evidence-key-value-list.repeating-spans/)
  136. ).toHaveTextContent('SELECT * FROM books');
  137. expect(
  138. screen.queryByTestId('span-evidence-key-value-list.')
  139. ).not.toBeInTheDocument();
  140. expect(screen.queryByRole('cell', {name: 'Parameter'})).not.toBeInTheDocument();
  141. expect(
  142. screen.queryByTestId('span-evidence-key-value-list.problem-parameters')
  143. ).not.toBeInTheDocument();
  144. });
  145. });
  146. describe('MN+1 Database Queries', () => {
  147. const builder = new TransactionEventBuilder('a1', '/');
  148. builder.getEventFixture().projectID = '123';
  149. const parentSpan = new MockSpan({
  150. startTimestamp: 0,
  151. endTimestamp: 0.2,
  152. op: 'http.server',
  153. problemSpan: ProblemSpan.PARENT,
  154. });
  155. parentSpan.addChild({
  156. startTimestamp: 0.01,
  157. endTimestamp: 2.1,
  158. op: 'db',
  159. description: 'SELECT * FROM books',
  160. hash: 'aaa',
  161. problemSpan: ProblemSpan.OFFENDER,
  162. });
  163. parentSpan.addChild({
  164. startTimestamp: 2.1,
  165. endTimestamp: 4.0,
  166. op: 'db.sql.active_record',
  167. description: 'SELECT * FROM books WHERE id = %s',
  168. hash: 'bbb',
  169. problemSpan: ProblemSpan.OFFENDER,
  170. });
  171. builder.addSpan(parentSpan);
  172. it('Renders relevant fields', () => {
  173. render(
  174. <SpanEvidenceKeyValueList
  175. event={builder.getEventFixture()}
  176. projectSlug={projectSlug}
  177. />
  178. );
  179. expect(screen.getByRole('cell', {name: 'Transaction'})).toBeInTheDocument();
  180. expect(
  181. screen.getByTestId('span-evidence-key-value-list.transaction')
  182. ).toHaveTextContent('/');
  183. expect(
  184. screen.getByTestId('span-evidence-key-value-list.transaction').querySelector('a')
  185. ).toHaveAttribute(
  186. 'href',
  187. `/organizations/org-slug/performance/summary/?project=123&referrer=performance-transaction-summary&transaction=%2F&unselectedSeries=p100%28%29&unselectedSeries=avg%28%29`
  188. );
  189. expect(
  190. screen.getByRole('button', {
  191. name: /view full event/i,
  192. })
  193. ).toHaveAttribute('href', '/organizations/org-slug/performance/project:a1/');
  194. expect(screen.getByRole('cell', {name: 'Parent Span'})).toBeInTheDocument();
  195. expect(
  196. screen.getByTestId('span-evidence-key-value-list.parent-span')
  197. ).toHaveTextContent('http.server');
  198. expect(screen.getByRole('cell', {name: 'Repeating Spans (2)'})).toBeInTheDocument();
  199. expect(
  200. screen.getByTestId('span-evidence-key-value-list.repeating-spans-2')
  201. ).toHaveTextContent('SELECT * FROM books');
  202. expect(screen.getByTestId('span-evidence-key-value-list.')).toHaveTextContent(
  203. 'db.sql.active_record - SELECT * FROM books WHERE id = %s'
  204. );
  205. expect(screen.queryByRole('cell', {name: 'Parameter'})).not.toBeInTheDocument();
  206. expect(
  207. screen.queryByTestId('span-evidence-key-value-list.problem-parameters')
  208. ).not.toBeInTheDocument();
  209. });
  210. });
  211. describe('Consecutive DB Queries', () => {
  212. const builder = new TransactionEventBuilder(
  213. 'a1',
  214. '/',
  215. IssueType.PERFORMANCE_CONSECUTIVE_DB_QUERIES
  216. );
  217. builder.getEventFixture().projectID = '123';
  218. const parentSpan = new MockSpan({
  219. startTimestamp: 0,
  220. endTimestamp: 0.65,
  221. op: 'http.server',
  222. problemSpan: ProblemSpan.PARENT,
  223. });
  224. parentSpan.addChild({
  225. startTimestamp: 0.1,
  226. endTimestamp: 0.2,
  227. op: 'db',
  228. description: 'SELECT * FROM USERS LIMIT 100',
  229. problemSpan: ProblemSpan.CAUSE,
  230. });
  231. parentSpan.addChild({
  232. startTimestamp: 0.2,
  233. endTimestamp: 0.4,
  234. op: 'db',
  235. description: 'SELECT COUNT(*) FROM USERS',
  236. problemSpan: [ProblemSpan.CAUSE, ProblemSpan.OFFENDER],
  237. });
  238. parentSpan.addChild({
  239. startTimestamp: 0.4,
  240. endTimestamp: 0.6,
  241. op: 'db',
  242. description: 'SELECT COUNT(*) FROM ITEMS',
  243. problemSpan: [ProblemSpan.CAUSE, ProblemSpan.OFFENDER],
  244. });
  245. builder.addSpan(parentSpan);
  246. it('Renders relevant fields', () => {
  247. render(
  248. <SpanEvidenceKeyValueList
  249. event={builder.getEventFixture()}
  250. projectSlug={projectSlug}
  251. />
  252. );
  253. expect(screen.getByRole('cell', {name: 'Transaction'})).toBeInTheDocument();
  254. expect(
  255. screen.getByTestId('span-evidence-key-value-list.transaction')
  256. ).toHaveTextContent('/');
  257. expect(
  258. screen.getByTestId('span-evidence-key-value-list.transaction').querySelector('a')
  259. ).toHaveAttribute(
  260. 'href',
  261. `/organizations/org-slug/performance/summary/?project=123&referrer=performance-transaction-summary&transaction=%2F&unselectedSeries=p100%28%29&unselectedSeries=avg%28%29`
  262. );
  263. expect(
  264. screen.getByRole('button', {
  265. name: /view full event/i,
  266. })
  267. ).toHaveAttribute('href', '/organizations/org-slug/performance/project:a1/');
  268. expect(screen.getByRole('cell', {name: 'Starting Span'})).toBeInTheDocument();
  269. expect(
  270. screen.getByTestId('span-evidence-key-value-list.starting-span')
  271. ).toHaveTextContent('SELECT * FROM USERS LIMIT 100');
  272. expect(screen.queryAllByRole('cell', {name: 'Parallelizable Spans'}).length).toBe(
  273. 1
  274. );
  275. const parallelizableSpanKeyValue = screen.getByTestId(
  276. 'span-evidence-key-value-list.parallelizable-spans'
  277. );
  278. expect(parallelizableSpanKeyValue).toHaveTextContent('SELECT COUNT(*) FROM USERS');
  279. expect(parallelizableSpanKeyValue).toHaveTextContent('SELECT COUNT(*) FROM ITEMS');
  280. expect(
  281. screen.getByTestId('span-evidence-key-value-list.duration-impact')
  282. ).toHaveTextContent('46% (300ms/650ms)');
  283. });
  284. });
  285. describe('Consecutive HTTP', () => {
  286. const builder = new TransactionEventBuilder(
  287. 'a1',
  288. '/',
  289. IssueType.PERFORMANCE_CONSECUTIVE_HTTP
  290. );
  291. builder.getEventFixture().projectID = '123';
  292. const parentSpan = new MockSpan({
  293. startTimestamp: 0,
  294. endTimestamp: 0.65,
  295. op: 'http.client',
  296. problemSpan: ProblemSpan.PARENT,
  297. });
  298. parentSpan.addChild({
  299. startTimestamp: 0.1,
  300. endTimestamp: 0.2,
  301. op: 'http.client',
  302. description: 'GET /endpoint1',
  303. problemSpan: ProblemSpan.OFFENDER,
  304. });
  305. parentSpan.addChild({
  306. startTimestamp: 0.2,
  307. endTimestamp: 0.3,
  308. op: 'http.client',
  309. description: 'GET /endpoint2',
  310. problemSpan: ProblemSpan.OFFENDER,
  311. });
  312. parentSpan.addChild({
  313. startTimestamp: 0.3,
  314. endTimestamp: 0.4,
  315. op: 'http.client',
  316. description: 'GET /endpoint3',
  317. problemSpan: ProblemSpan.OFFENDER,
  318. });
  319. builder.addSpan(parentSpan);
  320. it('Renders relevant fields', () => {
  321. render(
  322. <SpanEvidenceKeyValueList
  323. event={builder.getEventFixture()}
  324. projectSlug={projectSlug}
  325. />
  326. );
  327. const parallelizableSpanKeyValue = screen.getByTestId(
  328. 'span-evidence-key-value-list.offending-spans'
  329. );
  330. expect(parallelizableSpanKeyValue).toHaveTextContent('GET /endpoint1');
  331. expect(parallelizableSpanKeyValue).toHaveTextContent('GET /endpoint2');
  332. expect(parallelizableSpanKeyValue).toHaveTextContent('GET /endpoint3');
  333. });
  334. });
  335. describe('N+1 API Calls', () => {
  336. const builder = new TransactionEventBuilder(
  337. 'a1',
  338. '/',
  339. IssueType.PERFORMANCE_N_PLUS_ONE_API_CALLS
  340. );
  341. builder.getEventFixture().projectID = '123';
  342. const parentSpan = new MockSpan({
  343. startTimestamp: 0,
  344. endTimestamp: 200,
  345. op: 'pageload',
  346. problemSpan: ProblemSpan.PARENT,
  347. });
  348. parentSpan.addChild({
  349. startTimestamp: 10,
  350. endTimestamp: 2100,
  351. op: 'http.client',
  352. description: 'GET /book/?book_id=7&sort=up',
  353. problemSpan: ProblemSpan.OFFENDER,
  354. });
  355. parentSpan.addChild({
  356. startTimestamp: 10,
  357. endTimestamp: 2100,
  358. op: 'http.client',
  359. description: 'GET /book/?book_id=8&sort=down',
  360. problemSpan: ProblemSpan.OFFENDER,
  361. });
  362. builder.addSpan(parentSpan);
  363. builder.addEntry(
  364. EntryRequestFixture({
  365. data: {
  366. ...EntryRequestFixture().data,
  367. url: 'http://some.service.io',
  368. },
  369. })
  370. );
  371. it('Renders relevant fields', () => {
  372. render(
  373. <SpanEvidenceKeyValueList
  374. event={builder.getEventFixture()}
  375. projectSlug={projectSlug}
  376. />
  377. );
  378. expect(screen.getByRole('cell', {name: 'Transaction'})).toBeInTheDocument();
  379. expect(
  380. screen.getByTestId('span-evidence-key-value-list.transaction')
  381. ).toHaveTextContent('/');
  382. expect(
  383. screen.getByTestId('span-evidence-key-value-list.transaction').querySelector('a')
  384. ).toHaveAttribute(
  385. 'href',
  386. `/organizations/org-slug/performance/summary/?project=123&referrer=performance-transaction-summary&transaction=%2F&unselectedSeries=p100%28%29&unselectedSeries=avg%28%29`
  387. );
  388. expect(
  389. screen.getByRole('button', {
  390. name: /view full event/i,
  391. })
  392. ).toHaveAttribute('href', '/organizations/org-slug/performance/project:a1/');
  393. expect(screen.getByRole('cell', {name: 'Repeating Spans (2)'})).toBeInTheDocument();
  394. expect(
  395. screen.getByTestId(/span-evidence-key-value-list.repeating-spans/)
  396. ).toHaveTextContent('/book/[Parameters]');
  397. expect(screen.getByRole('cell', {name: 'Parameters'})).toBeInTheDocument();
  398. const parametersKeyValue = screen.getByTestId(
  399. 'span-evidence-key-value-list.parameters'
  400. );
  401. expect(parametersKeyValue).toHaveTextContent('book_id:{7,8}');
  402. expect(parametersKeyValue).toHaveTextContent('sort:{up,down}');
  403. });
  404. describe('extractSpanURLString', () => {
  405. it('Tries to pull a URL from the span data', () => {
  406. expect(
  407. extractSpanURLString({
  408. span_id: 'a',
  409. data: {
  410. url: 'http://service.io?id=2543',
  411. },
  412. })?.toString()
  413. ).toEqual('http://service.io/?id=2543');
  414. });
  415. it('Pulls out a relative URL if a base is provided', () => {
  416. expect(
  417. extractSpanURLString(
  418. {
  419. span_id: 'a',
  420. data: {
  421. url: '/item',
  422. },
  423. },
  424. 'http://service.io'
  425. )?.toString()
  426. ).toEqual('http://service.io/item');
  427. });
  428. it('Fetches the query string from the span data if available', () => {
  429. expect(
  430. extractSpanURLString({
  431. span_id: 'a',
  432. description: 'GET http://service.io/item',
  433. data: {
  434. url: 'http://service.io/item',
  435. 'http.query': 'id=153',
  436. },
  437. })?.toString()
  438. ).toEqual('http://service.io/item?id=153');
  439. });
  440. it('Falls back to span description if URL is faulty', () => {
  441. expect(
  442. extractSpanURLString({
  443. span_id: 'a',
  444. description: 'GET http://service.io/item',
  445. data: {
  446. url: '/item',
  447. },
  448. })?.toString()
  449. ).toEqual('http://service.io/item');
  450. });
  451. });
  452. describe('extractQueryParameters', () => {
  453. it('If the URLs have no parameters or are malformed, returns nothing', () => {
  454. const URLs = [
  455. new URL('http://service.io/items'),
  456. new URL('http://service.io/values'),
  457. ];
  458. expect(extractQueryParameters(URLs)).toEqual({});
  459. });
  460. it('If the URLs have one changing parameter, returns it and its values', () => {
  461. const URLs = [
  462. new URL('http://service.io/items?id=4'),
  463. new URL('http://service.io/items?id=5'),
  464. new URL('http://service.io/items?id=6'),
  465. ];
  466. expect(extractQueryParameters(URLs)).toEqual({
  467. id: ['4', '5', '6'],
  468. });
  469. });
  470. it('If the URLs have multiple changing parameters, returns them and their values', () => {
  471. const URLs = [
  472. new URL('http://service.io/items?id=4&sort=down&filter=none'),
  473. new URL('http://service.io/items?id=5&sort=up&filter=none'),
  474. new URL('http://service.io/items?id=6&sort=up&filter=none'),
  475. ];
  476. expect(extractQueryParameters(URLs)).toEqual({
  477. id: ['4', '5', '6'],
  478. sort: ['down', 'up'],
  479. filter: ['none'],
  480. });
  481. });
  482. });
  483. });
  484. describe('Slow DB Span', () => {
  485. const builder = new TransactionEventBuilder(
  486. 'a1',
  487. '/',
  488. IssueType.PERFORMANCE_SLOW_DB_QUERY
  489. );
  490. builder.getEventFixture().projectID = '123';
  491. const parentSpan = new MockSpan({
  492. startTimestamp: 0,
  493. endTimestamp: 200,
  494. op: 'pageload',
  495. problemSpan: ProblemSpan.PARENT,
  496. });
  497. parentSpan.addChild({
  498. startTimestamp: 10,
  499. endTimestamp: 10100,
  500. op: 'db',
  501. description: 'SELECT pokemon FROM pokedex',
  502. problemSpan: ProblemSpan.OFFENDER,
  503. });
  504. builder.addSpan(parentSpan);
  505. it('Renders relevant fields', () => {
  506. render(
  507. <SpanEvidenceKeyValueList
  508. event={builder.getEventFixture()}
  509. projectSlug={projectSlug}
  510. />
  511. );
  512. expect(screen.getByRole('cell', {name: 'Transaction'})).toBeInTheDocument();
  513. expect(
  514. screen.getByTestId('span-evidence-key-value-list.transaction')
  515. ).toHaveTextContent('/');
  516. expect(
  517. screen.getByTestId('span-evidence-key-value-list.transaction').querySelector('a')
  518. ).toHaveAttribute(
  519. 'href',
  520. `/organizations/org-slug/performance/summary/?project=123&referrer=performance-transaction-summary&transaction=%2F&unselectedSeries=p100%28%29&unselectedSeries=avg%28%29`
  521. );
  522. expect(
  523. screen.getByRole('button', {
  524. name: /view full event/i,
  525. })
  526. ).toHaveAttribute('href', '/organizations/org-slug/performance/project:a1/');
  527. expect(screen.getByRole('cell', {name: 'Slow DB Query'})).toBeInTheDocument();
  528. expect(
  529. screen.getByTestId('span-evidence-key-value-list.slow-db-query')
  530. ).toHaveTextContent('SELECT pokemon FROM pokedex');
  531. expect(screen.getByRole('cell', {name: 'Duration Impact'})).toBeInTheDocument();
  532. });
  533. });
  534. describe('Render Blocking Asset', () => {
  535. const builder = new TransactionEventBuilder(
  536. 'a1',
  537. '/',
  538. IssueType.PERFORMANCE_RENDER_BLOCKING_ASSET,
  539. {
  540. duration: 3,
  541. fcp: 2500,
  542. }
  543. );
  544. builder.getEventFixture().projectID = '123';
  545. const offenderSpan = new MockSpan({
  546. startTimestamp: 0,
  547. endTimestamp: 1.0,
  548. op: 'resource.script',
  549. description: 'https://example.com/resource.js',
  550. problemSpan: ProblemSpan.OFFENDER,
  551. });
  552. builder.addSpan(offenderSpan);
  553. it('Renders relevant fields', () => {
  554. render(
  555. <SpanEvidenceKeyValueList
  556. event={builder.getEventFixture()}
  557. projectSlug={projectSlug}
  558. />
  559. );
  560. expect(screen.getByRole('cell', {name: 'Transaction'})).toBeInTheDocument();
  561. expect(
  562. screen.getByTestId('span-evidence-key-value-list.transaction')
  563. ).toHaveTextContent('/');
  564. expect(
  565. screen.getByTestId('span-evidence-key-value-list.transaction').querySelector('a')
  566. ).toHaveAttribute(
  567. 'href',
  568. `/organizations/org-slug/performance/summary/?project=123&referrer=performance-transaction-summary&transaction=%2F&unselectedSeries=p100%28%29&unselectedSeries=avg%28%29`
  569. );
  570. expect(
  571. screen.getByRole('button', {
  572. name: /view full event/i,
  573. })
  574. ).toHaveAttribute('href', '/organizations/org-slug/performance/project:a1/');
  575. expect(screen.getByRole('cell', {name: 'Slow Resource Span'})).toBeInTheDocument();
  576. expect(
  577. screen.getByTestId('span-evidence-key-value-list.slow-resource-span')
  578. ).toHaveTextContent('resource.script - https://example.com/resource.js');
  579. expect(screen.getByRole('cell', {name: 'FCP Delay'})).toBeInTheDocument();
  580. expect(
  581. screen.getByTestId('span-evidence-key-value-list.fcp-delay')
  582. ).toHaveTextContent('1s (40% of 2.50s)');
  583. expect(screen.getByRole('cell', {name: 'Duration Impact'})).toBeInTheDocument();
  584. expect(
  585. screen.getByTestId('span-evidence-key-value-list.duration-impact')
  586. ).toHaveTextContent('33% (1s/3.00s');
  587. });
  588. });
  589. describe('Uncompressed Asset', () => {
  590. const builder = new TransactionEventBuilder(
  591. 'a1',
  592. '/',
  593. IssueType.PERFORMANCE_UNCOMPRESSED_ASSET,
  594. {
  595. duration: 0.931, // in seconds
  596. }
  597. );
  598. builder.getEventFixture().projectID = '123';
  599. const offenderSpan = new MockSpan({
  600. startTimestamp: 0,
  601. endTimestamp: 0.487, // in seconds
  602. op: 'resource.script',
  603. description: 'https://example.com/resource.js',
  604. problemSpan: ProblemSpan.OFFENDER,
  605. data: {
  606. 'http.response_content_length': 31041901,
  607. },
  608. });
  609. builder.addSpan(offenderSpan);
  610. it('Renders relevant fields', () => {
  611. render(
  612. <SpanEvidenceKeyValueList
  613. event={builder.getEventFixture()}
  614. projectSlug={projectSlug}
  615. />
  616. );
  617. expect(screen.getByRole('cell', {name: 'Transaction'})).toBeInTheDocument();
  618. expect(
  619. screen.getByTestId('span-evidence-key-value-list.transaction')
  620. ).toHaveTextContent('/');
  621. expect(
  622. screen.getByTestId('span-evidence-key-value-list.transaction').querySelector('a')
  623. ).toHaveAttribute(
  624. 'href',
  625. `/organizations/org-slug/performance/summary/?project=123&referrer=performance-transaction-summary&transaction=%2F&unselectedSeries=p100%28%29&unselectedSeries=avg%28%29`
  626. );
  627. expect(
  628. screen.getByRole('button', {
  629. name: /view full event/i,
  630. })
  631. ).toHaveAttribute('href', '/organizations/org-slug/performance/project:a1/');
  632. expect(screen.getByRole('cell', {name: 'Slow Resource Span'})).toBeInTheDocument();
  633. expect(
  634. screen.getByTestId('span-evidence-key-value-list.slow-resource-span')
  635. ).toHaveTextContent('resource.script - https://example.com/resource.js');
  636. expect(screen.getByRole('cell', {name: 'Asset Size'})).toBeInTheDocument();
  637. expect(
  638. screen.getByTestId('span-evidence-key-value-list.asset-size')
  639. ).toHaveTextContent('29.6 MiB (31041901 B)');
  640. expect(screen.getByRole('cell', {name: 'Duration Impact'})).toBeInTheDocument();
  641. expect(
  642. screen.getByTestId('span-evidence-key-value-list.duration-impact')
  643. ).toHaveTextContent('52% (487ms/931ms)');
  644. });
  645. describe('With backwards compatible legacy keys', () => {
  646. const legacyKeyBuilder = new TransactionEventBuilder(
  647. 'a1',
  648. '/',
  649. IssueType.PERFORMANCE_UNCOMPRESSED_ASSET,
  650. {
  651. duration: 0.931, // in seconds
  652. }
  653. );
  654. legacyKeyBuilder.getEventFixture().projectID = '123';
  655. const offenderSpanWithLegacyKey = new MockSpan({
  656. startTimestamp: 0,
  657. endTimestamp: 0.487, // in seconds
  658. op: 'resource.script',
  659. description: 'https://example.com/resource.js',
  660. problemSpan: ProblemSpan.OFFENDER,
  661. data: {
  662. 'Encoded Body Size': 31041901,
  663. },
  664. });
  665. legacyKeyBuilder.addSpan(offenderSpanWithLegacyKey);
  666. it('Renders relevant fields', () => {
  667. render(
  668. <SpanEvidenceKeyValueList
  669. event={legacyKeyBuilder.getEventFixture()}
  670. projectSlug={projectSlug}
  671. />
  672. );
  673. expect(screen.getByRole('cell', {name: 'Asset Size'})).toBeInTheDocument();
  674. expect(
  675. screen.getByTestId('span-evidence-key-value-list.asset-size')
  676. ).toHaveTextContent('29.6 MiB (31041901 B)');
  677. });
  678. });
  679. });
  680. describe('Large HTTP Payload', () => {
  681. const builder = new TransactionEventBuilder(
  682. 'a1',
  683. '/',
  684. IssueType.PERFORMANCE_LARGE_HTTP_PAYLOAD
  685. );
  686. builder.getEventFixture().projectID = '123';
  687. const offenderSpan = new MockSpan({
  688. startTimestamp: 0,
  689. endTimestamp: 0.487, // in seconds
  690. op: 'http.client',
  691. description: 'https://example.com/api/users',
  692. problemSpan: ProblemSpan.OFFENDER,
  693. data: {
  694. 'http.response_content_length': 31041901,
  695. },
  696. });
  697. builder.addSpan(offenderSpan);
  698. it('Renders relevant fields', () => {
  699. render(
  700. <SpanEvidenceKeyValueList
  701. event={builder.getEventFixture()}
  702. projectSlug={projectSlug}
  703. />
  704. );
  705. expect(screen.getByRole('cell', {name: 'Transaction'})).toBeInTheDocument();
  706. expect(
  707. screen.getByTestId('span-evidence-key-value-list.transaction')
  708. ).toHaveTextContent('/');
  709. expect(
  710. screen.getByTestId('span-evidence-key-value-list.transaction').querySelector('a')
  711. ).toHaveAttribute(
  712. 'href',
  713. `/organizations/org-slug/performance/summary/?project=123&referrer=performance-transaction-summary&transaction=%2F&unselectedSeries=p100%28%29&unselectedSeries=avg%28%29`
  714. );
  715. expect(
  716. screen.getByRole('button', {
  717. name: /view full event/i,
  718. })
  719. ).toHaveAttribute('href', '/organizations/org-slug/performance/project:a1/');
  720. expect(
  721. screen.getByRole('cell', {name: 'Large HTTP Payload Span'})
  722. ).toBeInTheDocument();
  723. expect(
  724. screen.getByTestId('span-evidence-key-value-list.large-http-payload-span')
  725. ).toHaveTextContent('http.client - https://example.com/api/users');
  726. expect(screen.getByRole('cell', {name: 'Payload Size'})).toBeInTheDocument();
  727. expect(
  728. screen.getByTestId('span-evidence-key-value-list.payload-size')
  729. ).toHaveTextContent('29.6 MiB (31041901 B)');
  730. });
  731. describe('With backwards compatible legacy keys', () => {
  732. const legacyKeyBuilder = new TransactionEventBuilder(
  733. 'a1',
  734. '/',
  735. IssueType.PERFORMANCE_LARGE_HTTP_PAYLOAD
  736. );
  737. legacyKeyBuilder.getEventFixture().projectID = '123';
  738. const offenderSpanWithLegacyKey = new MockSpan({
  739. startTimestamp: 0,
  740. endTimestamp: 0.487, // in seconds
  741. op: 'http.client',
  742. description: 'https://example.com/api/users',
  743. problemSpan: ProblemSpan.OFFENDER,
  744. data: {
  745. 'Encoded Body Size': 31041901,
  746. },
  747. });
  748. legacyKeyBuilder.addSpan(offenderSpanWithLegacyKey);
  749. it('Renders relevant fields', () => {
  750. render(
  751. <SpanEvidenceKeyValueList
  752. event={legacyKeyBuilder.getEventFixture()}
  753. projectSlug={projectSlug}
  754. />
  755. );
  756. expect(screen.getByRole('cell', {name: 'Payload Size'})).toBeInTheDocument();
  757. expect(
  758. screen.getByTestId('span-evidence-key-value-list.payload-size')
  759. ).toHaveTextContent('29.6 MiB (31041901 B)');
  760. });
  761. });
  762. });
  763. });