widgetContainer.spec.tsx 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213
  1. import type {InitializeDataSettings} from 'sentry-test/performance/initializePerformanceData';
  2. import {initializeData as _initializeData} from 'sentry-test/performance/initializePerformanceData';
  3. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  4. import {MetricsCardinalityProvider} from 'sentry/utils/performance/contexts/metricsCardinality';
  5. import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  6. import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
  7. import {PerformanceDisplayProvider} from 'sentry/utils/performance/contexts/performanceDisplayContext';
  8. import {OrganizationContext} from 'sentry/views/organizationContext';
  9. import WidgetContainer from 'sentry/views/performance/landing/widgets/components/widgetContainer';
  10. import {PerformanceWidgetSetting} from 'sentry/views/performance/landing/widgets/widgetDefinitions';
  11. import {ProjectPerformanceType} from 'sentry/views/performance/utils';
  12. import {QUERY_LIMIT_PARAM} from '../utils';
  13. const initializeData = (query = {}, rest: InitializeDataSettings = {}) => {
  14. const data = _initializeData({
  15. query: {statsPeriod: '7d', environment: ['prod'], project: [-42], ...query},
  16. ...rest,
  17. });
  18. data.eventView.additionalConditions.addFilterValues('transaction.op', ['pageload']);
  19. return data;
  20. };
  21. function WrappedComponent({data, withStaticFilters = false, ...rest}) {
  22. return (
  23. <OrganizationContext.Provider value={data.organization}>
  24. <MetricsCardinalityProvider
  25. location={data.router.location}
  26. organization={data.organization}
  27. >
  28. <MEPSettingProvider forceTransactions>
  29. <PerformanceDisplayProvider
  30. value={{performanceType: ProjectPerformanceType.ANY}}
  31. >
  32. <WidgetContainer
  33. chartHeight={100}
  34. allowedCharts={[
  35. PerformanceWidgetSetting.TPM_AREA,
  36. PerformanceWidgetSetting.FAILURE_RATE_AREA,
  37. PerformanceWidgetSetting.USER_MISERY_AREA,
  38. PerformanceWidgetSetting.DURATION_HISTOGRAM,
  39. ]}
  40. rowChartSettings={[]}
  41. withStaticFilters={withStaticFilters}
  42. forceDefaultChartSetting
  43. {...data}
  44. {...rest}
  45. />
  46. </PerformanceDisplayProvider>
  47. </MEPSettingProvider>
  48. </MetricsCardinalityProvider>
  49. </OrganizationContext.Provider>
  50. );
  51. }
  52. const issuesPredicate = (url, options) =>
  53. url.includes('events') && options.query?.query.includes('error');
  54. describe('Performance > Widgets > WidgetContainer', function () {
  55. let wrapper;
  56. let eventStatsMock;
  57. let eventsTrendsStats;
  58. let eventsMock;
  59. let issuesListMock;
  60. beforeEach(function () {
  61. eventStatsMock = MockApiClient.addMockResponse({
  62. method: 'GET',
  63. url: `/organizations/org-slug/events-stats/`,
  64. body: [],
  65. });
  66. eventsMock = MockApiClient.addMockResponse({
  67. method: 'GET',
  68. url: `/organizations/org-slug/events/`,
  69. body: {
  70. data: [{}],
  71. meta: {},
  72. },
  73. match: [(...args) => !issuesPredicate(...args)],
  74. });
  75. issuesListMock = MockApiClient.addMockResponse({
  76. method: 'GET',
  77. url: `/organizations/org-slug/events/`,
  78. body: {
  79. data: [
  80. {
  81. 'issue.id': 123,
  82. transaction: '/issue/:id/',
  83. title: 'Error: Something is broken.',
  84. 'project.id': 1,
  85. count: 3100,
  86. issue: 'JAVASCRIPT-ABCD',
  87. },
  88. ],
  89. },
  90. match: [(...args) => issuesPredicate(...args)],
  91. });
  92. eventsTrendsStats = MockApiClient.addMockResponse({
  93. method: 'GET',
  94. url: '/organizations/org-slug/events-trends-stats/',
  95. body: [],
  96. });
  97. MockApiClient.addMockResponse({
  98. method: 'GET',
  99. url: `/organizations/org-slug/metrics-compatibility/`,
  100. body: [],
  101. });
  102. MockApiClient.addMockResponse({
  103. method: 'GET',
  104. url: `/organizations/org-slug/metrics-compatibility-sums/`,
  105. body: [],
  106. });
  107. });
  108. afterEach(function () {
  109. if (wrapper) {
  110. wrapper.unmount();
  111. wrapper = undefined;
  112. }
  113. });
  114. it('Check requests when changing widget props', function () {
  115. const data = initializeData();
  116. wrapper = render(
  117. <WrappedComponent
  118. data={data}
  119. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  120. />
  121. );
  122. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  123. // Change eventView reference
  124. data.eventView = data.eventView.clone();
  125. wrapper.rerender(
  126. <WrappedComponent
  127. data={data}
  128. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  129. />
  130. );
  131. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  132. // Change eventView statsperiod
  133. const modifiedData = initializeData({
  134. statsPeriod: '14d',
  135. });
  136. wrapper.rerender(
  137. <WrappedComponent
  138. data={modifiedData}
  139. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  140. />
  141. );
  142. expect(eventStatsMock).toHaveBeenCalledTimes(2);
  143. expect(eventStatsMock).toHaveBeenNthCalledWith(
  144. 2,
  145. expect.anything(),
  146. expect.objectContaining({
  147. query: expect.objectContaining({
  148. interval: '1h',
  149. partial: '1',
  150. query: 'transaction.op:pageload',
  151. statsPeriod: '28d',
  152. yAxis: 'tpm()',
  153. }),
  154. })
  155. );
  156. });
  157. it('Check requests when changing widget props for GenericDiscoverQuery based widget', function () {
  158. const data = initializeData();
  159. wrapper = render(
  160. <MEPSettingProvider forceTransactions>
  161. <WrappedComponent
  162. data={data}
  163. defaultChartSetting={PerformanceWidgetSetting.MOST_IMPROVED}
  164. />
  165. </MEPSettingProvider>
  166. );
  167. expect(eventsTrendsStats).toHaveBeenCalledTimes(1);
  168. // Change eventView reference
  169. data.eventView = data.eventView.clone();
  170. wrapper.rerender(
  171. <MEPSettingProvider forceTransactions>
  172. <WrappedComponent
  173. data={data}
  174. defaultChartSetting={PerformanceWidgetSetting.MOST_IMPROVED}
  175. />
  176. </MEPSettingProvider>
  177. );
  178. expect(eventsTrendsStats).toHaveBeenCalledTimes(1);
  179. // Change eventView statsperiod
  180. const modifiedData = initializeData({
  181. statsPeriod: '14d',
  182. });
  183. wrapper.rerender(
  184. <MEPSettingProvider forceTransactions>
  185. <WrappedComponent
  186. data={modifiedData}
  187. defaultChartSetting={PerformanceWidgetSetting.MOST_IMPROVED}
  188. />
  189. </MEPSettingProvider>
  190. );
  191. expect(eventsTrendsStats).toHaveBeenCalledTimes(2);
  192. expect(eventsTrendsStats).toHaveBeenNthCalledWith(
  193. 2,
  194. expect.anything(),
  195. expect.objectContaining({
  196. query: expect.objectContaining({
  197. cursor: '0:0:1',
  198. environment: ['prod'],
  199. field: ['transaction', 'project'],
  200. interval: undefined,
  201. middle: undefined,
  202. noPagination: true,
  203. per_page: QUERY_LIMIT_PARAM,
  204. project: ['-42'],
  205. query:
  206. 'transaction.op:pageload tpm():>0.01 count_percentage():>0.25 count_percentage():<4 trend_percentage():>0% confidence():>6',
  207. sort: 'trend_percentage()',
  208. statsPeriod: '14d',
  209. trendFunction: 'p50(transaction.duration)',
  210. trendType: 'improved',
  211. }),
  212. })
  213. );
  214. });
  215. it('should call PageError Provider when errors are present', async function () {
  216. const data = initializeData();
  217. eventStatsMock = MockApiClient.addMockResponse({
  218. method: 'GET',
  219. url: `/organizations/org-slug/events-stats/`,
  220. statusCode: 400,
  221. body: {
  222. detail: 'Request did not work :(',
  223. },
  224. });
  225. wrapper = render(
  226. <PageAlertProvider>
  227. <PageAlert />
  228. <WrappedComponent
  229. data={data}
  230. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  231. />
  232. </PageAlertProvider>
  233. );
  234. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  235. 'Transactions Per Minute'
  236. );
  237. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  238. expect(await screen.findByTestId('page-error-alert')).toHaveTextContent(
  239. 'Request did not work :('
  240. );
  241. });
  242. it('TPM Widget', async function () {
  243. const data = initializeData();
  244. wrapper = render(
  245. <WrappedComponent
  246. data={data}
  247. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  248. />
  249. );
  250. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  251. 'Transactions Per Minute'
  252. );
  253. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  254. expect(eventStatsMock).toHaveBeenNthCalledWith(
  255. 1,
  256. expect.anything(),
  257. expect.objectContaining({
  258. query: expect.objectContaining({
  259. interval: '1h',
  260. partial: '1',
  261. query: 'transaction.op:pageload',
  262. statsPeriod: '14d',
  263. yAxis: 'tpm()',
  264. }),
  265. })
  266. );
  267. });
  268. it('Failure Rate Widget', async function () {
  269. const data = initializeData();
  270. wrapper = render(
  271. <WrappedComponent
  272. data={data}
  273. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  274. />
  275. );
  276. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  277. 'Failure Rate'
  278. );
  279. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  280. expect(eventStatsMock).toHaveBeenNthCalledWith(
  281. 1,
  282. expect.anything(),
  283. expect.objectContaining({
  284. query: expect.objectContaining({
  285. interval: '1h',
  286. partial: '1',
  287. query: 'transaction.op:pageload',
  288. statsPeriod: '14d',
  289. yAxis: 'failure_rate()',
  290. }),
  291. })
  292. );
  293. });
  294. it('Widget with MEP enabled and metric meta set to true', async function () {
  295. const data = initializeData(
  296. {},
  297. {
  298. features: ['performance-use-metrics'],
  299. }
  300. );
  301. eventStatsMock = MockApiClient.addMockResponse({
  302. method: 'GET',
  303. url: `/organizations/org-slug/events-stats/`,
  304. body: {
  305. data: [],
  306. isMetricsData: true,
  307. },
  308. });
  309. eventsMock = MockApiClient.addMockResponse({
  310. method: 'GET',
  311. url: `/organizations/org-slug/events/`,
  312. body: {
  313. data: [{}],
  314. meta: {isMetricsData: true},
  315. },
  316. });
  317. wrapper = render(
  318. <WrappedComponent
  319. data={data}
  320. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  321. />
  322. );
  323. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  324. expect(eventStatsMock).toHaveBeenNthCalledWith(
  325. 1,
  326. expect.anything(),
  327. expect.objectContaining({
  328. query: expect.objectContaining({dataset: 'metrics'}),
  329. })
  330. );
  331. expect(eventsMock).toHaveBeenCalledTimes(1);
  332. expect(eventsMock).toHaveBeenCalledWith(
  333. expect.anything(),
  334. expect.objectContaining({
  335. query: expect.objectContaining({dataset: 'metrics'}),
  336. })
  337. );
  338. expect(await screen.findByTestId('has-metrics-data-tag')).toHaveTextContent(
  339. 'processed'
  340. );
  341. });
  342. it('Widget with MEP enabled and metric meta set to undefined', async function () {
  343. const data = initializeData(
  344. {},
  345. {
  346. features: ['performance-use-metrics'],
  347. }
  348. );
  349. eventStatsMock = MockApiClient.addMockResponse({
  350. method: 'GET',
  351. url: `/organizations/org-slug/events-stats/`,
  352. body: {
  353. data: [],
  354. isMetricsData: undefined,
  355. },
  356. });
  357. wrapper = render(
  358. <WrappedComponent
  359. data={data}
  360. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  361. />
  362. );
  363. expect(await screen.findByTestId('no-metrics-data-tag')).toBeInTheDocument();
  364. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  365. expect(eventStatsMock).toHaveBeenNthCalledWith(
  366. 1,
  367. expect.anything(),
  368. expect.objectContaining({
  369. query: expect.objectContaining({dataset: 'metrics'}),
  370. })
  371. );
  372. });
  373. it('Widget with MEP enabled and metric meta set to false', async function () {
  374. const data = initializeData(
  375. {},
  376. {
  377. features: ['performance-use-metrics'],
  378. }
  379. );
  380. eventStatsMock = MockApiClient.addMockResponse({
  381. method: 'GET',
  382. url: `/organizations/org-slug/events-stats/`,
  383. body: {
  384. data: [],
  385. isMetricsData: false,
  386. },
  387. });
  388. eventsMock = MockApiClient.addMockResponse({
  389. method: 'GET',
  390. url: `/organizations/org-slug/events/`,
  391. body: {
  392. data: [{}],
  393. meta: {isMetricsData: false},
  394. },
  395. });
  396. wrapper = render(
  397. <WrappedComponent
  398. data={data}
  399. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  400. />
  401. );
  402. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  403. expect(eventStatsMock).toHaveBeenNthCalledWith(
  404. 1,
  405. expect.anything(),
  406. expect.objectContaining({
  407. query: expect.objectContaining({dataset: 'metrics'}),
  408. })
  409. );
  410. expect(eventsMock).toHaveBeenCalledTimes(1);
  411. expect(eventsMock).toHaveBeenCalledWith(
  412. expect.anything(),
  413. expect.objectContaining({
  414. query: expect.objectContaining({dataset: 'metrics'}),
  415. })
  416. );
  417. expect(await screen.findByTestId('has-metrics-data-tag')).toHaveTextContent(
  418. 'indexed'
  419. );
  420. });
  421. it('User misery Widget', async function () {
  422. const data = initializeData();
  423. wrapper = render(
  424. <WrappedComponent
  425. data={data}
  426. defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA}
  427. />
  428. );
  429. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  430. 'User Misery'
  431. );
  432. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  433. expect(eventStatsMock).toHaveBeenNthCalledWith(
  434. 1,
  435. expect.anything(),
  436. expect.objectContaining({
  437. query: expect.objectContaining({
  438. interval: '1h',
  439. partial: '1',
  440. query: 'transaction.op:pageload',
  441. statsPeriod: '14d',
  442. yAxis: 'user_misery()',
  443. }),
  444. })
  445. );
  446. });
  447. it('Worst LCP widget', async function () {
  448. const data = initializeData();
  449. wrapper = render(
  450. <WrappedComponent
  451. data={data}
  452. defaultChartSetting={PerformanceWidgetSetting.WORST_LCP_VITALS}
  453. />
  454. );
  455. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  456. 'Worst LCP Web Vitals'
  457. );
  458. expect(await screen.findByTestId('view-all-button')).toHaveTextContent('View All');
  459. expect(eventsMock).toHaveBeenCalledTimes(1);
  460. expect(eventsMock).toHaveBeenNthCalledWith(
  461. 1,
  462. expect.anything(),
  463. expect.objectContaining({
  464. query: expect.objectContaining({
  465. environment: ['prod'],
  466. field: [
  467. 'transaction',
  468. 'title',
  469. 'project.id',
  470. 'count_web_vitals(measurements.lcp, poor)',
  471. 'count_web_vitals(measurements.lcp, meh)',
  472. 'count_web_vitals(measurements.lcp, good)',
  473. ],
  474. per_page: 4,
  475. project: ['-42'],
  476. query: 'transaction.op:pageload',
  477. sort: '-count_web_vitals(measurements.lcp, poor)',
  478. statsPeriod: '7d',
  479. }),
  480. })
  481. );
  482. });
  483. it('Worst LCP widget - MEP', async function () {
  484. const data = initializeData(
  485. {},
  486. {
  487. features: ['performance-use-metrics'],
  488. }
  489. );
  490. wrapper = render(
  491. <WrappedComponent
  492. data={data}
  493. defaultChartSetting={PerformanceWidgetSetting.WORST_LCP_VITALS}
  494. />
  495. );
  496. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  497. 'Worst LCP Web Vitals'
  498. );
  499. expect(await screen.findByTestId('view-all-button')).toHaveTextContent('View All');
  500. expect(eventsMock).toHaveBeenCalledTimes(1);
  501. expect(eventsMock).toHaveBeenNthCalledWith(
  502. 1,
  503. expect.anything(),
  504. expect.objectContaining({
  505. query: expect.objectContaining({
  506. environment: ['prod'],
  507. field: [
  508. 'transaction',
  509. 'title',
  510. 'project.id',
  511. 'count_web_vitals(measurements.lcp, poor)',
  512. 'count_web_vitals(measurements.lcp, meh)',
  513. 'count_web_vitals(measurements.lcp, good)',
  514. ],
  515. per_page: 4,
  516. project: ['-42'],
  517. query: 'transaction.op:pageload !transaction:"<< unparameterized >>"',
  518. sort: '-count_web_vitals(measurements.lcp, poor)',
  519. statsPeriod: '7d',
  520. }),
  521. })
  522. );
  523. });
  524. it('Worst FCP widget', async function () {
  525. const data = initializeData();
  526. wrapper = render(
  527. <WrappedComponent
  528. data={data}
  529. defaultChartSetting={PerformanceWidgetSetting.WORST_FCP_VITALS}
  530. />
  531. );
  532. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  533. 'Worst FCP Web Vitals'
  534. );
  535. expect(await screen.findByTestId('view-all-button')).toHaveTextContent('View All');
  536. expect(eventsMock).toHaveBeenCalledTimes(1);
  537. expect(eventsMock).toHaveBeenNthCalledWith(
  538. 1,
  539. expect.anything(),
  540. expect.objectContaining({
  541. query: expect.objectContaining({
  542. environment: ['prod'],
  543. field: [
  544. 'transaction',
  545. 'title',
  546. 'project.id',
  547. 'count_web_vitals(measurements.fcp, poor)',
  548. 'count_web_vitals(measurements.fcp, meh)',
  549. 'count_web_vitals(measurements.fcp, good)',
  550. ],
  551. per_page: 4,
  552. project: ['-42'],
  553. query: 'transaction.op:pageload',
  554. sort: '-count_web_vitals(measurements.fcp, poor)',
  555. statsPeriod: '7d',
  556. }),
  557. })
  558. );
  559. });
  560. it('Worst FID widget', async function () {
  561. const data = initializeData();
  562. wrapper = render(
  563. <WrappedComponent
  564. data={data}
  565. defaultChartSetting={PerformanceWidgetSetting.WORST_FID_VITALS}
  566. />
  567. );
  568. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  569. 'Worst FID Web Vitals'
  570. );
  571. expect(await screen.findByTestId('view-all-button')).toHaveTextContent('View All');
  572. expect(eventsMock).toHaveBeenCalledTimes(1);
  573. expect(eventsMock).toHaveBeenNthCalledWith(
  574. 1,
  575. expect.anything(),
  576. expect.objectContaining({
  577. query: expect.objectContaining({
  578. environment: ['prod'],
  579. field: [
  580. 'transaction',
  581. 'title',
  582. 'project.id',
  583. 'count_web_vitals(measurements.fid, poor)',
  584. 'count_web_vitals(measurements.fid, meh)',
  585. 'count_web_vitals(measurements.fid, good)',
  586. ],
  587. per_page: 4,
  588. project: ['-42'],
  589. query: 'transaction.op:pageload',
  590. sort: '-count_web_vitals(measurements.fid, poor)',
  591. statsPeriod: '7d',
  592. }),
  593. })
  594. );
  595. });
  596. it('LCP Histogram Widget', async function () {
  597. const data = initializeData();
  598. wrapper = render(
  599. <WrappedComponent
  600. data={data}
  601. defaultChartSetting={PerformanceWidgetSetting.LCP_HISTOGRAM}
  602. />
  603. );
  604. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  605. 'LCP Distribution'
  606. );
  607. // TODO(k-fish): Add histogram mock
  608. });
  609. it('FCP Histogram Widget', async function () {
  610. const data = initializeData();
  611. wrapper = render(
  612. <WrappedComponent
  613. data={data}
  614. defaultChartSetting={PerformanceWidgetSetting.FCP_HISTOGRAM}
  615. />
  616. );
  617. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  618. 'FCP Distribution'
  619. );
  620. // TODO(k-fish): Add histogram mock
  621. });
  622. it('Most errors widget', async function () {
  623. const data = initializeData();
  624. wrapper = render(
  625. <WrappedComponent
  626. data={data}
  627. defaultChartSetting={PerformanceWidgetSetting.MOST_RELATED_ERRORS}
  628. />
  629. );
  630. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  631. 'Most Related Errors'
  632. );
  633. expect(eventsMock).toHaveBeenCalledTimes(1);
  634. expect(eventsMock).toHaveBeenNthCalledWith(
  635. 1,
  636. expect.anything(),
  637. expect.objectContaining({
  638. query: expect.objectContaining({
  639. environment: ['prod'],
  640. field: ['transaction', 'project.id', 'failure_count()'],
  641. per_page: QUERY_LIMIT_PARAM,
  642. project: ['-42'],
  643. query: 'transaction.op:pageload failure_count():>0',
  644. sort: '-failure_count()',
  645. statsPeriod: '7d',
  646. }),
  647. })
  648. );
  649. });
  650. it('Most related issues widget', async function () {
  651. const data = initializeData();
  652. wrapper = render(
  653. <WrappedComponent
  654. data={data}
  655. defaultChartSetting={PerformanceWidgetSetting.MOST_RELATED_ISSUES}
  656. />
  657. );
  658. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  659. 'Most Related Issues'
  660. );
  661. expect(issuesListMock).toHaveBeenCalledTimes(1);
  662. expect(issuesListMock).toHaveBeenNthCalledWith(
  663. 1,
  664. expect.anything(),
  665. expect.objectContaining({
  666. query: expect.objectContaining({
  667. environment: ['prod'],
  668. field: ['issue', 'transaction', 'title', 'project.id', 'count()'],
  669. per_page: QUERY_LIMIT_PARAM,
  670. project: ['-42'],
  671. query: 'event.type:error !tags[transaction]:"" count():>0',
  672. sort: '-count()',
  673. statsPeriod: '7d',
  674. }),
  675. })
  676. );
  677. });
  678. it('Switching from issues to errors widget', async function () {
  679. const data = initializeData();
  680. wrapper = render(
  681. <WrappedComponent
  682. data={data}
  683. defaultChartSetting={PerformanceWidgetSetting.MOST_RELATED_ISSUES}
  684. />
  685. );
  686. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  687. 'Most Related Issues'
  688. );
  689. expect(issuesListMock).toHaveBeenCalledTimes(1);
  690. wrapper.rerender(
  691. <WrappedComponent
  692. data={data}
  693. defaultChartSetting={PerformanceWidgetSetting.MOST_RELATED_ERRORS}
  694. />
  695. );
  696. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  697. 'Most Related Errors'
  698. );
  699. expect(eventsMock).toHaveBeenCalledTimes(1);
  700. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  701. });
  702. it('Most improved trends widget', async function () {
  703. const data = initializeData();
  704. wrapper = render(
  705. <MEPSettingProvider forceTransactions>
  706. <WrappedComponent
  707. data={data}
  708. defaultChartSetting={PerformanceWidgetSetting.MOST_IMPROVED}
  709. />
  710. </MEPSettingProvider>
  711. );
  712. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  713. 'Most Improved'
  714. );
  715. expect(eventsTrendsStats).toHaveBeenCalledTimes(1);
  716. expect(eventsTrendsStats).toHaveBeenNthCalledWith(
  717. 1,
  718. expect.anything(),
  719. expect.objectContaining({
  720. query: expect.objectContaining({
  721. environment: ['prod'],
  722. field: ['transaction', 'project'],
  723. interval: undefined,
  724. middle: undefined,
  725. per_page: QUERY_LIMIT_PARAM,
  726. project: ['-42'],
  727. query:
  728. 'transaction.op:pageload tpm():>0.01 count_percentage():>0.25 count_percentage():<4 trend_percentage():>0% confidence():>6',
  729. sort: 'trend_percentage()',
  730. statsPeriod: '7d',
  731. trendFunction: 'p50(transaction.duration)',
  732. trendType: 'improved',
  733. }),
  734. })
  735. );
  736. });
  737. it('Most time spent in db queries widget', async function () {
  738. const data = initializeData();
  739. wrapper = render(
  740. <MEPSettingProvider forceTransactions>
  741. <WrappedComponent
  742. data={data}
  743. defaultChartSetting={PerformanceWidgetSetting.MOST_TIME_SPENT_DB_QUERIES}
  744. />
  745. </MEPSettingProvider>
  746. );
  747. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  748. 'Most Time-Consuming Queries'
  749. );
  750. expect(eventsMock).toHaveBeenCalledTimes(1);
  751. expect(eventsMock).toHaveBeenNthCalledWith(
  752. 1,
  753. expect.anything(),
  754. expect.objectContaining({
  755. query: expect.objectContaining({
  756. dataset: 'spansMetrics',
  757. environment: ['prod'],
  758. field: [
  759. 'span.op',
  760. 'span.group',
  761. 'project.id',
  762. 'span.description',
  763. 'sum(span.self_time)',
  764. 'avg(span.self_time)',
  765. 'time_spent_percentage()',
  766. ],
  767. per_page: QUERY_LIMIT_PARAM,
  768. project: ['-42'],
  769. query: 'has:span.description span.module:db transaction.op:pageload',
  770. sort: '-time_spent_percentage()',
  771. statsPeriod: '7d',
  772. }),
  773. })
  774. );
  775. });
  776. it('Most time consuming resources widget', async function () {
  777. const data = initializeData();
  778. wrapper = render(
  779. <MEPSettingProvider forceTransactions>
  780. <WrappedComponent
  781. data={data}
  782. defaultChartSetting={PerformanceWidgetSetting.MOST_TIME_CONSUMING_RESOURCES}
  783. />
  784. </MEPSettingProvider>
  785. );
  786. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  787. 'Most Time Consuming Resources'
  788. );
  789. expect(eventsMock).toHaveBeenCalledTimes(1);
  790. expect(eventsMock).toHaveBeenNthCalledWith(
  791. 1,
  792. expect.anything(),
  793. expect.objectContaining({
  794. query: expect.objectContaining({
  795. dataset: 'spansMetrics',
  796. environment: ['prod'],
  797. field: [
  798. 'span.description',
  799. 'span.op',
  800. 'project.id',
  801. 'span.group',
  802. 'sum(span.self_time)',
  803. 'avg(span.self_time)',
  804. 'time_spent_percentage()',
  805. ],
  806. per_page: QUERY_LIMIT_PARAM,
  807. project: ['-42'],
  808. query:
  809. '!span.description:browser-extension://* resource.render_blocking_status:blocking ( span.op:resource.script OR file_extension:css OR file_extension:[woff,woff2,ttf,otf,eot] OR file_extension:[jpg,jpeg,png,gif,svg,webp,apng,avif] OR span.op:resource.img ) transaction.op:pageload',
  810. sort: '-time_spent_percentage()',
  811. statsPeriod: '7d',
  812. }),
  813. })
  814. );
  815. });
  816. it('Best Page Opportunities widget', async function () {
  817. const data = initializeData();
  818. wrapper = render(
  819. <MEPSettingProvider forceTransactions>
  820. <WrappedComponent
  821. data={data}
  822. defaultChartSetting={PerformanceWidgetSetting.HIGHEST_OPPORTUNITY_PAGES}
  823. />
  824. </MEPSettingProvider>
  825. );
  826. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  827. 'Best Page Opportunities'
  828. );
  829. expect(eventsMock).toHaveBeenCalledTimes(2);
  830. expect(eventsMock).toHaveBeenNthCalledWith(
  831. 1,
  832. expect.anything(),
  833. expect.objectContaining({
  834. query: expect.objectContaining({
  835. dataset: 'metrics',
  836. field: [
  837. 'p75(measurements.lcp)',
  838. 'p75(measurements.fcp)',
  839. 'p75(measurements.cls)',
  840. 'p75(measurements.ttfb)',
  841. 'p75(measurements.fid)',
  842. 'p75(transaction.duration)',
  843. 'count_web_vitals(measurements.lcp, any)',
  844. 'count_web_vitals(measurements.fcp, any)',
  845. 'count_web_vitals(measurements.cls, any)',
  846. 'count_web_vitals(measurements.fid, any)',
  847. 'count_web_vitals(measurements.ttfb, any)',
  848. 'count()',
  849. ],
  850. query: 'transaction.op:pageload',
  851. }),
  852. })
  853. );
  854. expect(eventsMock).toHaveBeenNthCalledWith(
  855. 2,
  856. expect.anything(),
  857. expect.objectContaining({
  858. query: expect.objectContaining({
  859. dataset: 'metrics',
  860. field: [
  861. 'transaction',
  862. 'transaction.op',
  863. 'p75(measurements.lcp)',
  864. 'p75(measurements.fcp)',
  865. 'p75(measurements.cls)',
  866. 'p75(measurements.ttfb)',
  867. 'p75(measurements.fid)',
  868. 'count_web_vitals(measurements.lcp, any)',
  869. 'count_web_vitals(measurements.fcp, any)',
  870. 'count_web_vitals(measurements.cls, any)',
  871. 'count_web_vitals(measurements.ttfb, any)',
  872. 'count_web_vitals(measurements.fid, any)',
  873. 'count()',
  874. ],
  875. per_page: 4,
  876. query: 'transaction.op:pageload',
  877. sort: '-count()',
  878. }),
  879. })
  880. );
  881. });
  882. it('Most regressed trends widget', async function () {
  883. const data = initializeData();
  884. wrapper = render(
  885. <WrappedComponent
  886. data={data}
  887. defaultChartSetting={PerformanceWidgetSetting.MOST_REGRESSED}
  888. />
  889. );
  890. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  891. 'Most Regressed'
  892. );
  893. expect(eventsTrendsStats).toHaveBeenCalledTimes(1);
  894. expect(eventsTrendsStats).toHaveBeenNthCalledWith(
  895. 1,
  896. expect.anything(),
  897. expect.objectContaining({
  898. query: expect.objectContaining({
  899. environment: ['prod'],
  900. field: ['transaction', 'project'],
  901. interval: undefined,
  902. middle: undefined,
  903. per_page: QUERY_LIMIT_PARAM,
  904. project: ['-42'],
  905. query:
  906. 'transaction.op:pageload tpm():>0.01 count_percentage():>0.25 count_percentage():<4 trend_percentage():>0% confidence():>6',
  907. sort: '-trend_percentage()',
  908. statsPeriod: '7d',
  909. trendFunction: 'p50(transaction.duration)',
  910. trendType: 'regression',
  911. }),
  912. })
  913. );
  914. });
  915. it('Most slow frames widget', async function () {
  916. const data = initializeData();
  917. wrapper = render(
  918. <WrappedComponent
  919. data={data}
  920. defaultChartSetting={PerformanceWidgetSetting.MOST_SLOW_FRAMES}
  921. />
  922. );
  923. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  924. 'Most Slow Frames'
  925. );
  926. expect(eventsMock).toHaveBeenCalledTimes(1);
  927. expect(eventsMock).toHaveBeenNthCalledWith(
  928. 1,
  929. expect.anything(),
  930. expect.objectContaining({
  931. query: expect.objectContaining({
  932. cursor: '0:0:1',
  933. environment: ['prod'],
  934. field: ['transaction', 'project.id', 'epm()', 'avg(measurements.frames_slow)'],
  935. noPagination: true,
  936. per_page: QUERY_LIMIT_PARAM,
  937. project: ['-42'],
  938. query: 'transaction.op:pageload epm():>0.01 avg(measurements.frames_slow):>0',
  939. sort: '-avg(measurements.frames_slow)',
  940. statsPeriod: '7d',
  941. }),
  942. })
  943. );
  944. expect(await screen.findByTestId('empty-state')).toBeInTheDocument();
  945. });
  946. it('Most slow frames widget - MEP', async function () {
  947. const data = initializeData(
  948. {},
  949. {
  950. features: ['performance-use-metrics'],
  951. }
  952. );
  953. wrapper = render(
  954. <WrappedComponent
  955. data={data}
  956. defaultChartSetting={PerformanceWidgetSetting.MOST_SLOW_FRAMES}
  957. />
  958. );
  959. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  960. 'Most Slow Frames'
  961. );
  962. expect(eventsMock).toHaveBeenCalledTimes(1);
  963. expect(eventsMock).toHaveBeenNthCalledWith(
  964. 1,
  965. expect.anything(),
  966. expect.objectContaining({
  967. query: expect.objectContaining({
  968. cursor: '0:0:1',
  969. environment: ['prod'],
  970. field: ['transaction', 'project.id', 'epm()', 'avg(measurements.frames_slow)'],
  971. noPagination: true,
  972. per_page: QUERY_LIMIT_PARAM,
  973. project: ['-42'],
  974. query: 'transaction.op:pageload epm():>0.01 avg(measurements.frames_slow):>0',
  975. sort: '-avg(measurements.frames_slow)',
  976. statsPeriod: '7d',
  977. }),
  978. })
  979. );
  980. expect(await screen.findByTestId('empty-state')).toBeInTheDocument();
  981. });
  982. it('Most frozen frames widget', async function () {
  983. const data = initializeData();
  984. wrapper = render(
  985. <WrappedComponent
  986. data={data}
  987. defaultChartSetting={PerformanceWidgetSetting.MOST_FROZEN_FRAMES}
  988. />
  989. );
  990. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  991. 'Most Frozen Frames'
  992. );
  993. expect(eventsMock).toHaveBeenCalledTimes(1);
  994. expect(eventsMock).toHaveBeenNthCalledWith(
  995. 1,
  996. expect.anything(),
  997. expect.objectContaining({
  998. query: expect.objectContaining({
  999. cursor: '0:0:1',
  1000. environment: ['prod'],
  1001. field: [
  1002. 'transaction',
  1003. 'project.id',
  1004. 'epm()',
  1005. 'avg(measurements.frames_frozen)',
  1006. ],
  1007. noPagination: true,
  1008. per_page: QUERY_LIMIT_PARAM,
  1009. project: ['-42'],
  1010. query: 'transaction.op:pageload epm():>0.01 avg(measurements.frames_frozen):>0',
  1011. sort: '-avg(measurements.frames_frozen)',
  1012. statsPeriod: '7d',
  1013. }),
  1014. })
  1015. );
  1016. expect(await screen.findByTestId('empty-state')).toBeInTheDocument();
  1017. });
  1018. it('Able to change widget type from menu', async function () {
  1019. const data = initializeData();
  1020. const setRowChartSettings = jest.fn(() => {});
  1021. wrapper = render(
  1022. <WrappedComponent
  1023. data={data}
  1024. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  1025. setRowChartSettings={setRowChartSettings}
  1026. />
  1027. );
  1028. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  1029. 'Failure Rate'
  1030. );
  1031. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  1032. expect(setRowChartSettings).toHaveBeenCalledTimes(0);
  1033. await userEvent.click(await screen.findByLabelText('More'));
  1034. await userEvent.click(await screen.findByText('User Misery'));
  1035. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  1036. 'User Misery'
  1037. );
  1038. expect(eventStatsMock).toHaveBeenCalledTimes(2);
  1039. expect(setRowChartSettings).toHaveBeenCalledTimes(1);
  1040. });
  1041. it('Chart settings passed from the row are disabled in the menu', async function () {
  1042. const data = initializeData();
  1043. const setRowChartSettings = jest.fn(() => {});
  1044. wrapper = render(
  1045. <WrappedComponent
  1046. data={data}
  1047. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  1048. setRowChartSettings={setRowChartSettings}
  1049. rowChartSettings={[
  1050. PerformanceWidgetSetting.FAILURE_RATE_AREA,
  1051. PerformanceWidgetSetting.USER_MISERY_AREA,
  1052. ]}
  1053. />
  1054. );
  1055. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  1056. 'Failure Rate'
  1057. );
  1058. // Open context menu
  1059. await userEvent.click(await screen.findByLabelText('More'));
  1060. // Check that the the "User Misery" option is disabled by clicking on it,
  1061. // expecting that the selected option doesn't change
  1062. const userMiseryOption = await screen.findByRole('option', {name: 'User Misery'});
  1063. await userEvent.click(userMiseryOption);
  1064. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  1065. 'Failure Rate'
  1066. );
  1067. });
  1068. });