widgetContainer.spec.tsx 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  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 Spent in DB 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:
  777. 'has:span.description span.module:db transaction.op:http.server transaction.op:pageload',
  778. sort: '-time_spent_percentage()',
  779. statsPeriod: '7d',
  780. }),
  781. })
  782. );
  783. });
  784. it('Most regressed trends widget', async function () {
  785. const data = initializeData();
  786. wrapper = render(
  787. <WrappedComponent
  788. data={data}
  789. defaultChartSetting={PerformanceWidgetSetting.MOST_REGRESSED}
  790. />
  791. );
  792. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  793. 'Most Regressed'
  794. );
  795. expect(eventsTrendsStats).toHaveBeenCalledTimes(1);
  796. expect(eventsTrendsStats).toHaveBeenNthCalledWith(
  797. 1,
  798. expect.anything(),
  799. expect.objectContaining({
  800. query: expect.objectContaining({
  801. environment: ['prod'],
  802. field: ['transaction', 'project'],
  803. interval: undefined,
  804. middle: undefined,
  805. per_page: QUERY_LIMIT_PARAM,
  806. project: ['-42'],
  807. query:
  808. 'transaction.op:pageload tpm():>0.01 count_percentage():>0.25 count_percentage():<4 trend_percentage():>0% confidence():>6',
  809. sort: '-trend_percentage()',
  810. statsPeriod: '7d',
  811. trendFunction: 'p50(transaction.duration)',
  812. trendType: 'regression',
  813. }),
  814. })
  815. );
  816. });
  817. it('Most slow frames widget', async function () {
  818. const data = initializeData();
  819. wrapper = render(
  820. <WrappedComponent
  821. data={data}
  822. defaultChartSetting={PerformanceWidgetSetting.MOST_SLOW_FRAMES}
  823. />
  824. );
  825. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  826. 'Most Slow Frames'
  827. );
  828. expect(eventsMock).toHaveBeenCalledTimes(1);
  829. expect(eventsMock).toHaveBeenNthCalledWith(
  830. 1,
  831. expect.anything(),
  832. expect.objectContaining({
  833. query: expect.objectContaining({
  834. cursor: '0:0:1',
  835. environment: ['prod'],
  836. field: ['transaction', 'project.id', 'epm()', 'avg(measurements.frames_slow)'],
  837. noPagination: true,
  838. per_page: QUERY_LIMIT_PARAM,
  839. project: ['-42'],
  840. query: 'transaction.op:pageload epm():>0.01 avg(measurements.frames_slow):>0',
  841. sort: '-avg(measurements.frames_slow)',
  842. statsPeriod: '7d',
  843. }),
  844. })
  845. );
  846. expect(await screen.findByTestId('empty-state')).toBeInTheDocument();
  847. });
  848. it('Most slow frames widget - MEP', async function () {
  849. const data = initializeData(
  850. {},
  851. {
  852. features: ['performance-use-metrics'],
  853. }
  854. );
  855. wrapper = render(
  856. <WrappedComponent
  857. data={data}
  858. defaultChartSetting={PerformanceWidgetSetting.MOST_SLOW_FRAMES}
  859. />
  860. );
  861. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  862. 'Most Slow Frames'
  863. );
  864. expect(eventsMock).toHaveBeenCalledTimes(1);
  865. expect(eventsMock).toHaveBeenNthCalledWith(
  866. 1,
  867. expect.anything(),
  868. expect.objectContaining({
  869. query: expect.objectContaining({
  870. cursor: '0:0:1',
  871. environment: ['prod'],
  872. field: ['transaction', 'project.id', 'epm()', 'avg(measurements.frames_slow)'],
  873. noPagination: true,
  874. per_page: QUERY_LIMIT_PARAM,
  875. project: ['-42'],
  876. query: 'transaction.op:pageload epm():>0.01 avg(measurements.frames_slow):>0',
  877. sort: '-avg(measurements.frames_slow)',
  878. statsPeriod: '7d',
  879. }),
  880. })
  881. );
  882. expect(await screen.findByTestId('empty-state')).toBeInTheDocument();
  883. });
  884. it('Most frozen frames widget', async function () {
  885. const data = initializeData();
  886. wrapper = render(
  887. <WrappedComponent
  888. data={data}
  889. defaultChartSetting={PerformanceWidgetSetting.MOST_FROZEN_FRAMES}
  890. />
  891. );
  892. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  893. 'Most Frozen Frames'
  894. );
  895. expect(eventsMock).toHaveBeenCalledTimes(1);
  896. expect(eventsMock).toHaveBeenNthCalledWith(
  897. 1,
  898. expect.anything(),
  899. expect.objectContaining({
  900. query: expect.objectContaining({
  901. cursor: '0:0:1',
  902. environment: ['prod'],
  903. field: [
  904. 'transaction',
  905. 'project.id',
  906. 'epm()',
  907. 'avg(measurements.frames_frozen)',
  908. ],
  909. noPagination: true,
  910. per_page: QUERY_LIMIT_PARAM,
  911. project: ['-42'],
  912. query: 'transaction.op:pageload epm():>0.01 avg(measurements.frames_frozen):>0',
  913. sort: '-avg(measurements.frames_frozen)',
  914. statsPeriod: '7d',
  915. }),
  916. })
  917. );
  918. expect(await screen.findByTestId('empty-state')).toBeInTheDocument();
  919. });
  920. it('Able to change widget type from menu', async function () {
  921. const data = initializeData();
  922. const setRowChartSettings = jest.fn(() => {});
  923. wrapper = render(
  924. <WrappedComponent
  925. data={data}
  926. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  927. setRowChartSettings={setRowChartSettings}
  928. />
  929. );
  930. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  931. 'Failure Rate'
  932. );
  933. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  934. expect(setRowChartSettings).toHaveBeenCalledTimes(0);
  935. await userEvent.click(await screen.findByLabelText('More'));
  936. await userEvent.click(await screen.findByText('User Misery'));
  937. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  938. 'User Misery'
  939. );
  940. expect(eventStatsMock).toHaveBeenCalledTimes(2);
  941. expect(setRowChartSettings).toHaveBeenCalledTimes(1);
  942. });
  943. it('Chart settings passed from the row are disabled in the menu', async function () {
  944. const data = initializeData();
  945. const setRowChartSettings = jest.fn(() => {});
  946. wrapper = render(
  947. <WrappedComponent
  948. data={data}
  949. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  950. setRowChartSettings={setRowChartSettings}
  951. rowChartSettings={[
  952. PerformanceWidgetSetting.FAILURE_RATE_AREA,
  953. PerformanceWidgetSetting.USER_MISERY_AREA,
  954. ]}
  955. />
  956. );
  957. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  958. 'Failure Rate'
  959. );
  960. // Open context menu
  961. await userEvent.click(await screen.findByLabelText('More'));
  962. // Check that the the "User Misery" option is disabled by clicking on it,
  963. // expecting that the selected option doesn't change
  964. const userMiseryOption = await screen.findByRole('option', {name: 'User Misery'});
  965. await userEvent.click(userMiseryOption);
  966. expect(await screen.findByTestId('performance-widget-title')).toHaveTextContent(
  967. 'Failure Rate'
  968. );
  969. });
  970. });