widgetContainer.spec.tsx 29 KB

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