widgetContainer.spec.tsx 31 KB

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