widgetContainer.spec.tsx 28 KB

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