widgetContainer.spec.tsx 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  1. import {
  2. initializeData as _initializeData,
  3. initializeDataSettings,
  4. } from 'sentry-test/performance/initializePerformanceData';
  5. import {act, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  6. import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  7. import {
  8. PageErrorAlert,
  9. PageErrorProvider,
  10. } from 'sentry/utils/performance/contexts/pageError';
  11. import {PerformanceDisplayProvider} from 'sentry/utils/performance/contexts/performanceDisplayContext';
  12. import {OrganizationContext} from 'sentry/views/organizationContext';
  13. import WidgetContainer from 'sentry/views/performance/landing/widgets/components/widgetContainer';
  14. import {PerformanceWidgetSetting} from 'sentry/views/performance/landing/widgets/widgetDefinitions';
  15. import {PROJECT_PERFORMANCE_TYPE} from 'sentry/views/performance/utils';
  16. const initializeData = (query = {}, rest: initializeDataSettings = {}) => {
  17. const data = _initializeData({
  18. query: {statsPeriod: '7d', environment: ['prod'], project: [-42], ...query},
  19. ...rest,
  20. });
  21. data.eventView.additionalConditions.addFilterValues('transaction.op', ['pageload']);
  22. return data;
  23. };
  24. const WrappedComponent = ({data, withStaticFilters = false, ...rest}) => {
  25. return (
  26. <OrganizationContext.Provider value={data.organization}>
  27. <MEPSettingProvider>
  28. <PerformanceDisplayProvider
  29. value={{performanceType: PROJECT_PERFORMANCE_TYPE.ANY}}
  30. >
  31. <WidgetContainer
  32. allowedCharts={[
  33. PerformanceWidgetSetting.TPM_AREA,
  34. PerformanceWidgetSetting.FAILURE_RATE_AREA,
  35. PerformanceWidgetSetting.USER_MISERY_AREA,
  36. PerformanceWidgetSetting.DURATION_HISTOGRAM,
  37. ]}
  38. rowChartSettings={[]}
  39. withStaticFilters={withStaticFilters}
  40. forceDefaultChartSetting
  41. {...data}
  42. {...rest}
  43. />
  44. </PerformanceDisplayProvider>
  45. </MEPSettingProvider>
  46. </OrganizationContext.Provider>
  47. );
  48. };
  49. const issuesPredicate = (url, options) =>
  50. url.includes('eventsv2') && options.query?.query.includes('error');
  51. describe('Performance > Widgets > WidgetContainer', function () {
  52. let wrapper;
  53. let eventStatsMock;
  54. let eventsV2Mock;
  55. let eventsTrendsStats;
  56. let issuesListMock;
  57. beforeEach(function () {
  58. eventStatsMock = MockApiClient.addMockResponse({
  59. method: 'GET',
  60. url: `/organizations/org-slug/events-stats/`,
  61. body: [],
  62. });
  63. eventsV2Mock = MockApiClient.addMockResponse({
  64. method: 'GET',
  65. url: `/organizations/org-slug/eventsv2/`,
  66. body: [],
  67. match: [(...args) => !issuesPredicate(...args)],
  68. });
  69. issuesListMock = MockApiClient.addMockResponse({
  70. method: 'GET',
  71. url: `/organizations/org-slug/eventsv2/`,
  72. body: {
  73. data: [
  74. {
  75. 'issue.id': 123,
  76. transaction: '/issue/:id/',
  77. title: 'Error: Something is broken.',
  78. 'project.id': 1,
  79. count: 3100,
  80. issue: 'JAVASCRIPT-ABCD',
  81. },
  82. ],
  83. },
  84. match: [(...args) => issuesPredicate(...args)],
  85. });
  86. eventsTrendsStats = MockApiClient.addMockResponse({
  87. method: 'GET',
  88. url: '/organizations/org-slug/events-trends-stats/',
  89. body: [],
  90. });
  91. });
  92. afterEach(function () {
  93. if (wrapper) {
  94. wrapper.unmount();
  95. wrapper = undefined;
  96. }
  97. });
  98. it('Check requests when changing widget props', function () {
  99. const data = initializeData();
  100. wrapper = render(
  101. <WrappedComponent
  102. data={data}
  103. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  104. />
  105. );
  106. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  107. // Change eventView reference
  108. data.eventView = data.eventView.clone();
  109. wrapper.rerender(
  110. <WrappedComponent
  111. data={data}
  112. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  113. />
  114. );
  115. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  116. // Change eventView statsperiod
  117. const modifiedData = initializeData({
  118. statsPeriod: '14d',
  119. });
  120. wrapper.rerender(
  121. <WrappedComponent
  122. data={modifiedData}
  123. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  124. />
  125. );
  126. expect(eventStatsMock).toHaveBeenCalledTimes(2);
  127. expect(eventStatsMock).toHaveBeenNthCalledWith(
  128. 2,
  129. expect.anything(),
  130. expect.objectContaining({
  131. query: expect.objectContaining({
  132. interval: '1h',
  133. partial: '1',
  134. query: 'transaction.op:pageload',
  135. statsPeriod: '28d',
  136. yAxis: 'tpm()',
  137. }),
  138. })
  139. );
  140. });
  141. it('Check requests when changing widget props for GenericDiscoverQuery based widget', function () {
  142. const data = initializeData();
  143. wrapper = render(
  144. <WrappedComponent
  145. data={data}
  146. defaultChartSetting={PerformanceWidgetSetting.MOST_IMPROVED}
  147. />
  148. );
  149. expect(eventsTrendsStats).toHaveBeenCalledTimes(1);
  150. // Change eventView reference
  151. data.eventView = data.eventView.clone();
  152. wrapper.rerender(
  153. <WrappedComponent
  154. data={data}
  155. defaultChartSetting={PerformanceWidgetSetting.MOST_IMPROVED}
  156. />
  157. );
  158. expect(eventsTrendsStats).toHaveBeenCalledTimes(1);
  159. // Change eventView statsperiod
  160. const modifiedData = initializeData({
  161. statsPeriod: '14d',
  162. });
  163. wrapper.rerender(
  164. <WrappedComponent
  165. data={modifiedData}
  166. defaultChartSetting={PerformanceWidgetSetting.MOST_IMPROVED}
  167. />
  168. );
  169. expect(eventsTrendsStats).toHaveBeenCalledTimes(2);
  170. expect(eventsTrendsStats).toHaveBeenNthCalledWith(
  171. 2,
  172. expect.anything(),
  173. expect.objectContaining({
  174. query: expect.objectContaining({
  175. cursor: '0:0:1',
  176. environment: ['prod'],
  177. field: ['transaction', 'project'],
  178. interval: undefined,
  179. middle: undefined,
  180. noPagination: true,
  181. per_page: 3,
  182. project: ['-42'],
  183. query:
  184. 'transaction.op:pageload tpm():>0.01 count_percentage():>0.25 count_percentage():<4 trend_percentage():>0% confidence():>6',
  185. sort: 'trend_percentage()',
  186. statsPeriod: '14d',
  187. trendFunction: 'avg(transaction.duration)',
  188. trendType: 'improved',
  189. }),
  190. })
  191. );
  192. });
  193. it('should call PageError Provider when errors are present', async function () {
  194. const data = initializeData();
  195. eventStatsMock = MockApiClient.addMockResponse({
  196. method: 'GET',
  197. url: `/organizations/org-slug/events-stats/`,
  198. statusCode: 400,
  199. body: {
  200. detail: 'Request did not work :(',
  201. },
  202. });
  203. wrapper = render(
  204. <PageErrorProvider>
  205. <PageErrorAlert />
  206. <WrappedComponent
  207. data={data}
  208. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  209. />
  210. </PageErrorProvider>
  211. );
  212. // Provider update is after request promise.
  213. await act(async () => {});
  214. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  215. 'Transactions Per Minute'
  216. );
  217. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  218. expect(await screen.findByTestId('page-error-alert')).toHaveTextContent(
  219. 'Request did not work :('
  220. );
  221. });
  222. it('TPM Widget', async function () {
  223. const data = initializeData();
  224. wrapper = render(
  225. <WrappedComponent
  226. data={data}
  227. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  228. />
  229. );
  230. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  231. 'Transactions Per Minute'
  232. );
  233. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  234. expect(eventStatsMock).toHaveBeenNthCalledWith(
  235. 1,
  236. expect.anything(),
  237. expect.objectContaining({
  238. query: expect.objectContaining({
  239. interval: '1h',
  240. partial: '1',
  241. query: 'transaction.op:pageload',
  242. statsPeriod: '14d',
  243. yAxis: 'tpm()',
  244. }),
  245. })
  246. );
  247. });
  248. it('Failure Rate Widget', async function () {
  249. const data = initializeData();
  250. wrapper = render(
  251. <WrappedComponent
  252. data={data}
  253. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  254. />
  255. );
  256. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  257. 'Failure Rate'
  258. );
  259. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  260. expect(eventStatsMock).toHaveBeenNthCalledWith(
  261. 1,
  262. expect.anything(),
  263. expect.objectContaining({
  264. query: expect.objectContaining({
  265. interval: '1h',
  266. partial: '1',
  267. query: 'transaction.op:pageload',
  268. statsPeriod: '14d',
  269. yAxis: 'failure_rate()',
  270. }),
  271. })
  272. );
  273. });
  274. it('Widget with MEP enabled and metric meta set to true', async function () {
  275. const data = initializeData(
  276. {},
  277. {
  278. features: ['performance-use-metrics'],
  279. }
  280. );
  281. eventStatsMock = MockApiClient.addMockResponse({
  282. method: 'GET',
  283. url: `/organizations/org-slug/events-stats/`,
  284. body: {
  285. data: [],
  286. isMetricsData: true,
  287. },
  288. });
  289. wrapper = render(
  290. <WrappedComponent
  291. data={data}
  292. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  293. />
  294. );
  295. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  296. expect(eventStatsMock).toHaveBeenNthCalledWith(
  297. 1,
  298. expect.anything(),
  299. expect.objectContaining({
  300. query: expect.objectContaining({
  301. metricsEnhanced: '1',
  302. preventMetricAggregates: '1',
  303. }),
  304. })
  305. );
  306. expect(await screen.findByTestId('has-metrics-data-tag')).toHaveTextContent(
  307. 'metrics'
  308. );
  309. });
  310. it('Widget with MEP enabled and metric meta set to undefined', async function () {
  311. const data = initializeData(
  312. {},
  313. {
  314. features: ['performance-use-metrics'],
  315. }
  316. );
  317. eventStatsMock = MockApiClient.addMockResponse({
  318. method: 'GET',
  319. url: `/organizations/org-slug/events-stats/`,
  320. body: {
  321. data: [],
  322. isMetricsData: undefined,
  323. },
  324. });
  325. wrapper = render(
  326. <WrappedComponent
  327. data={data}
  328. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  329. />
  330. );
  331. expect(await screen.findByTestId('no-metrics-data-tag')).toBeInTheDocument();
  332. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  333. expect(eventStatsMock).toHaveBeenNthCalledWith(
  334. 1,
  335. expect.anything(),
  336. expect.objectContaining({
  337. query: expect.objectContaining({
  338. metricsEnhanced: '1',
  339. preventMetricAggregates: '1',
  340. }),
  341. })
  342. );
  343. });
  344. it('Widget with MEP enabled and metric meta set to false', async function () {
  345. const data = initializeData(
  346. {},
  347. {
  348. features: ['performance-use-metrics'],
  349. }
  350. );
  351. eventStatsMock = MockApiClient.addMockResponse({
  352. method: 'GET',
  353. url: `/organizations/org-slug/events-stats/`,
  354. body: {
  355. data: [],
  356. isMetricsData: false,
  357. },
  358. });
  359. wrapper = render(
  360. <WrappedComponent
  361. data={data}
  362. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  363. />
  364. );
  365. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  366. expect(eventStatsMock).toHaveBeenNthCalledWith(
  367. 1,
  368. expect.anything(),
  369. expect.objectContaining({
  370. query: expect.objectContaining({
  371. metricsEnhanced: '1',
  372. preventMetricAggregates: '1',
  373. }),
  374. })
  375. );
  376. expect(await screen.findByTestId('has-metrics-data-tag')).toHaveTextContent(
  377. 'transactions'
  378. );
  379. });
  380. it('User misery Widget', async function () {
  381. const data = initializeData();
  382. wrapper = render(
  383. <WrappedComponent
  384. data={data}
  385. defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA}
  386. />
  387. );
  388. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  389. 'User Misery'
  390. );
  391. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  392. expect(eventStatsMock).toHaveBeenNthCalledWith(
  393. 1,
  394. expect.anything(),
  395. expect.objectContaining({
  396. query: expect.objectContaining({
  397. interval: '1h',
  398. partial: '1',
  399. query: 'transaction.op:pageload',
  400. statsPeriod: '14d',
  401. yAxis: 'user_misery()',
  402. }),
  403. })
  404. );
  405. });
  406. it('Worst LCP widget', async function () {
  407. const data = initializeData();
  408. wrapper = render(
  409. <WrappedComponent
  410. data={data}
  411. defaultChartSetting={PerformanceWidgetSetting.WORST_LCP_VITALS}
  412. />
  413. );
  414. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  415. 'Worst LCP Web Vitals'
  416. );
  417. expect(await screen.findByTestId('view-all-button')).toHaveTextContent('View All');
  418. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  419. expect(eventsV2Mock).toHaveBeenNthCalledWith(
  420. 1,
  421. expect.anything(),
  422. expect.objectContaining({
  423. query: expect.objectContaining({
  424. environment: ['prod'],
  425. field: [
  426. 'transaction',
  427. 'title',
  428. 'project.id',
  429. 'count_web_vitals(measurements.lcp, poor)',
  430. 'count_web_vitals(measurements.lcp, meh)',
  431. 'count_web_vitals(measurements.lcp, good)',
  432. ],
  433. per_page: 4,
  434. project: ['-42'],
  435. query: 'transaction.op:pageload',
  436. sort: '-count_web_vitals(measurements.lcp, poor)',
  437. statsPeriod: '7d',
  438. }),
  439. })
  440. );
  441. });
  442. it('Worst LCP widget - MEP', async function () {
  443. const data = initializeData(
  444. {},
  445. {
  446. features: ['performance-use-metrics'],
  447. }
  448. );
  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(eventsV2Mock).toHaveBeenCalledTimes(1);
  460. expect(eventsV2Mock).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. metricsEnhanced: '1',
  480. preventMetricAggregates: '1',
  481. }),
  482. })
  483. );
  484. });
  485. it('Worst FCP widget', async function () {
  486. const data = initializeData();
  487. wrapper = render(
  488. <WrappedComponent
  489. data={data}
  490. defaultChartSetting={PerformanceWidgetSetting.WORST_FCP_VITALS}
  491. />
  492. );
  493. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  494. 'Worst FCP Web Vitals'
  495. );
  496. expect(await screen.findByTestId('view-all-button')).toHaveTextContent('View All');
  497. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  498. expect(eventsV2Mock).toHaveBeenNthCalledWith(
  499. 1,
  500. expect.anything(),
  501. expect.objectContaining({
  502. query: expect.objectContaining({
  503. environment: ['prod'],
  504. field: [
  505. 'transaction',
  506. 'title',
  507. 'project.id',
  508. 'count_web_vitals(measurements.fcp, poor)',
  509. 'count_web_vitals(measurements.fcp, meh)',
  510. 'count_web_vitals(measurements.fcp, good)',
  511. ],
  512. per_page: 4,
  513. project: ['-42'],
  514. query: 'transaction.op:pageload',
  515. sort: '-count_web_vitals(measurements.fcp, poor)',
  516. statsPeriod: '7d',
  517. }),
  518. })
  519. );
  520. });
  521. it('Worst FID widget', async function () {
  522. const data = initializeData();
  523. wrapper = render(
  524. <WrappedComponent
  525. data={data}
  526. defaultChartSetting={PerformanceWidgetSetting.WORST_FID_VITALS}
  527. />
  528. );
  529. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  530. 'Worst FID Web Vitals'
  531. );
  532. expect(await screen.findByTestId('view-all-button')).toHaveTextContent('View All');
  533. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  534. expect(eventsV2Mock).toHaveBeenNthCalledWith(
  535. 1,
  536. expect.anything(),
  537. expect.objectContaining({
  538. query: expect.objectContaining({
  539. environment: ['prod'],
  540. field: [
  541. 'transaction',
  542. 'title',
  543. 'project.id',
  544. 'count_web_vitals(measurements.fid, poor)',
  545. 'count_web_vitals(measurements.fid, meh)',
  546. 'count_web_vitals(measurements.fid, good)',
  547. ],
  548. per_page: 4,
  549. project: ['-42'],
  550. query: 'transaction.op:pageload',
  551. sort: '-count_web_vitals(measurements.fid, poor)',
  552. statsPeriod: '7d',
  553. }),
  554. })
  555. );
  556. });
  557. it('LCP Histogram Widget', async function () {
  558. const data = initializeData();
  559. wrapper = render(
  560. <WrappedComponent
  561. data={data}
  562. defaultChartSetting={PerformanceWidgetSetting.LCP_HISTOGRAM}
  563. />
  564. );
  565. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  566. 'LCP Distribution'
  567. );
  568. // TODO(k-fish): Add histogram mock
  569. });
  570. it('FCP Histogram Widget', async function () {
  571. const data = initializeData();
  572. wrapper = render(
  573. <WrappedComponent
  574. data={data}
  575. defaultChartSetting={PerformanceWidgetSetting.FCP_HISTOGRAM}
  576. />
  577. );
  578. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  579. 'FCP Distribution'
  580. );
  581. // TODO(k-fish): Add histogram mock
  582. });
  583. it('Most errors widget', async function () {
  584. const data = initializeData();
  585. wrapper = render(
  586. <WrappedComponent
  587. data={data}
  588. defaultChartSetting={PerformanceWidgetSetting.MOST_RELATED_ERRORS}
  589. />
  590. );
  591. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  592. 'Most Related Errors'
  593. );
  594. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  595. expect(eventsV2Mock).toHaveBeenNthCalledWith(
  596. 1,
  597. expect.anything(),
  598. expect.objectContaining({
  599. query: expect.objectContaining({
  600. environment: ['prod'],
  601. field: ['transaction', 'project.id', 'failure_count()'],
  602. per_page: 3,
  603. project: ['-42'],
  604. query: 'transaction.op:pageload failure_count():>0',
  605. sort: '-failure_count()',
  606. statsPeriod: '7d',
  607. }),
  608. })
  609. );
  610. });
  611. it('Most related issues widget', async function () {
  612. const data = initializeData();
  613. wrapper = render(
  614. <WrappedComponent
  615. data={data}
  616. defaultChartSetting={PerformanceWidgetSetting.MOST_RELATED_ISSUES}
  617. />
  618. );
  619. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  620. 'Most Related Issues'
  621. );
  622. expect(issuesListMock).toHaveBeenCalledTimes(1);
  623. expect(issuesListMock).toHaveBeenNthCalledWith(
  624. 1,
  625. expect.anything(),
  626. expect.objectContaining({
  627. query: expect.objectContaining({
  628. environment: ['prod'],
  629. field: ['issue', 'transaction', 'title', 'project.id', 'count()'],
  630. per_page: 3,
  631. project: ['-42'],
  632. query: 'event.type:error !tags[transaction]:"" count():>0',
  633. sort: '-count()',
  634. statsPeriod: '7d',
  635. }),
  636. })
  637. );
  638. });
  639. it('Switching from issues to errors widget', async function () {
  640. const data = initializeData();
  641. wrapper = render(
  642. <WrappedComponent
  643. data={data}
  644. defaultChartSetting={PerformanceWidgetSetting.MOST_RELATED_ISSUES}
  645. />
  646. );
  647. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  648. 'Most Related Issues'
  649. );
  650. expect(issuesListMock).toHaveBeenCalledTimes(1);
  651. wrapper.rerender(
  652. <WrappedComponent
  653. data={data}
  654. defaultChartSetting={PerformanceWidgetSetting.MOST_RELATED_ERRORS}
  655. />
  656. );
  657. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  658. 'Most Related Errors'
  659. );
  660. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  661. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  662. });
  663. it('Most improved trends widget', async function () {
  664. const data = initializeData();
  665. wrapper = render(
  666. <WrappedComponent
  667. data={data}
  668. defaultChartSetting={PerformanceWidgetSetting.MOST_IMPROVED}
  669. />
  670. );
  671. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  672. 'Most Improved'
  673. );
  674. expect(eventsTrendsStats).toHaveBeenCalledTimes(1);
  675. expect(eventsTrendsStats).toHaveBeenNthCalledWith(
  676. 1,
  677. expect.anything(),
  678. expect.objectContaining({
  679. query: expect.objectContaining({
  680. environment: ['prod'],
  681. field: ['transaction', 'project'],
  682. interval: undefined,
  683. middle: undefined,
  684. per_page: 3,
  685. project: ['-42'],
  686. query:
  687. 'transaction.op:pageload tpm():>0.01 count_percentage():>0.25 count_percentage():<4 trend_percentage():>0% confidence():>6',
  688. sort: 'trend_percentage()',
  689. statsPeriod: '7d',
  690. trendFunction: 'avg(transaction.duration)',
  691. trendType: 'improved',
  692. }),
  693. })
  694. );
  695. });
  696. it('Most regressed trends widget', async function () {
  697. const data = initializeData();
  698. wrapper = render(
  699. <WrappedComponent
  700. data={data}
  701. defaultChartSetting={PerformanceWidgetSetting.MOST_REGRESSED}
  702. />
  703. );
  704. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  705. 'Most Regressed'
  706. );
  707. expect(eventsTrendsStats).toHaveBeenCalledTimes(1);
  708. expect(eventsTrendsStats).toHaveBeenNthCalledWith(
  709. 1,
  710. expect.anything(),
  711. expect.objectContaining({
  712. query: expect.objectContaining({
  713. environment: ['prod'],
  714. field: ['transaction', 'project'],
  715. interval: undefined,
  716. middle: undefined,
  717. per_page: 3,
  718. project: ['-42'],
  719. query:
  720. 'transaction.op:pageload tpm():>0.01 count_percentage():>0.25 count_percentage():<4 trend_percentage():>0% confidence():>6',
  721. sort: '-trend_percentage()',
  722. statsPeriod: '7d',
  723. trendFunction: 'avg(transaction.duration)',
  724. trendType: 'regression',
  725. }),
  726. })
  727. );
  728. });
  729. it('Most slow frames widget', async function () {
  730. const data = initializeData();
  731. wrapper = render(
  732. <WrappedComponent
  733. data={data}
  734. defaultChartSetting={PerformanceWidgetSetting.MOST_SLOW_FRAMES}
  735. />
  736. );
  737. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  738. 'Most Slow Frames'
  739. );
  740. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  741. expect(eventsV2Mock).toHaveBeenNthCalledWith(
  742. 1,
  743. expect.anything(),
  744. expect.objectContaining({
  745. query: expect.objectContaining({
  746. cursor: '0:0:1',
  747. environment: ['prod'],
  748. field: ['transaction', 'project.id', 'epm()', 'avg(measurements.frames_slow)'],
  749. noPagination: true,
  750. per_page: 3,
  751. project: ['-42'],
  752. query: 'transaction.op:pageload epm():>0.01 avg(measurements.frames_slow):>0',
  753. sort: '-avg(measurements.frames_slow)',
  754. statsPeriod: '7d',
  755. }),
  756. })
  757. );
  758. expect(await screen.findByTestId('empty-message')).toBeInTheDocument();
  759. });
  760. it('Most slow frames widget - MEP', async function () {
  761. const data = initializeData(
  762. {},
  763. {
  764. features: ['performance-use-metrics'],
  765. }
  766. );
  767. wrapper = render(
  768. <WrappedComponent
  769. data={data}
  770. defaultChartSetting={PerformanceWidgetSetting.MOST_SLOW_FRAMES}
  771. />
  772. );
  773. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  774. 'Most Slow Frames'
  775. );
  776. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  777. expect(eventsV2Mock).toHaveBeenNthCalledWith(
  778. 1,
  779. expect.anything(),
  780. expect.objectContaining({
  781. query: expect.objectContaining({
  782. cursor: '0:0:1',
  783. environment: ['prod'],
  784. field: ['transaction', 'project.id', 'epm()', 'avg(measurements.frames_slow)'],
  785. noPagination: true,
  786. per_page: 3,
  787. project: ['-42'],
  788. query: 'transaction.op:pageload epm():>0.01 avg(measurements.frames_slow):>0',
  789. sort: '-avg(measurements.frames_slow)',
  790. statsPeriod: '7d',
  791. metricsEnhanced: '1',
  792. preventMetricAggregates: '1',
  793. }),
  794. })
  795. );
  796. expect(await screen.findByTestId('empty-message')).toBeInTheDocument();
  797. });
  798. it('Most frozen frames widget', async function () {
  799. const data = initializeData();
  800. wrapper = render(
  801. <WrappedComponent
  802. data={data}
  803. defaultChartSetting={PerformanceWidgetSetting.MOST_FROZEN_FRAMES}
  804. />
  805. );
  806. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  807. 'Most Frozen Frames'
  808. );
  809. expect(eventsV2Mock).toHaveBeenCalledTimes(1);
  810. expect(eventsV2Mock).toHaveBeenNthCalledWith(
  811. 1,
  812. expect.anything(),
  813. expect.objectContaining({
  814. query: expect.objectContaining({
  815. cursor: '0:0:1',
  816. environment: ['prod'],
  817. field: [
  818. 'transaction',
  819. 'project.id',
  820. 'epm()',
  821. 'avg(measurements.frames_frozen)',
  822. ],
  823. noPagination: true,
  824. per_page: 3,
  825. project: ['-42'],
  826. query: 'transaction.op:pageload epm():>0.01 avg(measurements.frames_frozen):>0',
  827. sort: '-avg(measurements.frames_frozen)',
  828. statsPeriod: '7d',
  829. }),
  830. })
  831. );
  832. expect(await screen.findByTestId('empty-message')).toBeInTheDocument();
  833. });
  834. it('Able to change widget type from menu', async function () {
  835. const data = initializeData();
  836. const setRowChartSettings = jest.fn(() => {});
  837. wrapper = render(
  838. <WrappedComponent
  839. data={data}
  840. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  841. setRowChartSettings={setRowChartSettings}
  842. />
  843. );
  844. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  845. 'Failure Rate'
  846. );
  847. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  848. expect(setRowChartSettings).toHaveBeenCalledTimes(0);
  849. userEvent.click(await screen.findByLabelText('More'));
  850. userEvent.click(await screen.findByText('User Misery'));
  851. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  852. 'User Misery'
  853. );
  854. expect(eventStatsMock).toHaveBeenCalledTimes(2);
  855. expect(setRowChartSettings).toHaveBeenCalledTimes(1);
  856. });
  857. it('Chart settings passed from the row are disabled in the menu', async function () {
  858. const data = initializeData();
  859. const setRowChartSettings = jest.fn(() => {});
  860. wrapper = render(
  861. <WrappedComponent
  862. data={data}
  863. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  864. setRowChartSettings={setRowChartSettings}
  865. rowChartSettings={[
  866. PerformanceWidgetSetting.FAILURE_RATE_AREA,
  867. PerformanceWidgetSetting.USER_MISERY_AREA,
  868. ]}
  869. />
  870. );
  871. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  872. 'Failure Rate'
  873. );
  874. // Open context menu
  875. userEvent.click(await screen.findByLabelText('More'));
  876. // Check that the the "User Misery" option is disabled by clicking on it,
  877. // expecting that the selected option doesn't change
  878. const userMiseryOption = await screen.findByTestId('user_misery_area');
  879. userEvent.click(userMiseryOption);
  880. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  881. 'Failure Rate'
  882. );
  883. });
  884. });