widgetContainer.spec.tsx 28 KB

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