widgetContainer.spec.tsx 28 KB

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