queryBatcher.spec.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. import {Fragment} from 'react';
  2. import {initializeData as _initializeData} from 'sentry-test/performance/initializePerformanceData';
  3. import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
  4. import {GenericQueryBatcher} from 'sentry/utils/performance/contexts/genericQueryBatcher';
  5. import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  6. import {PerformanceDisplayProvider} from 'sentry/utils/performance/contexts/performanceDisplayContext';
  7. import {OrganizationContext} from 'sentry/views/organizationContext';
  8. import WidgetContainer from 'sentry/views/performance/landing/widgets/components/widgetContainer';
  9. import {PerformanceWidgetSetting} from 'sentry/views/performance/landing/widgets/widgetDefinitions';
  10. import {ProjectPerformanceType} from 'sentry/views/performance/utils';
  11. const initializeData = () => {
  12. const data = _initializeData({
  13. query: {statsPeriod: '7d', environment: ['prod'], project: [-42]},
  14. });
  15. data.eventView.additionalConditions.addFilterValues('transaction.op', ['pageload']);
  16. return data;
  17. };
  18. const BASIC_QUERY_PARAMS = {
  19. interval: '1h',
  20. partial: '1',
  21. query: 'transaction.op:pageload',
  22. statsPeriod: '14d',
  23. };
  24. function WrappedComponent({data, ...rest}: any) {
  25. return (
  26. <OrganizationContext.Provider value={data.organization}>
  27. <MEPSettingProvider>
  28. <PerformanceDisplayProvider value={{performanceType: ProjectPerformanceType.ANY}}>
  29. <WidgetContainer
  30. chartHeight={100}
  31. allowedCharts={[
  32. PerformanceWidgetSetting.TPM_AREA,
  33. PerformanceWidgetSetting.FAILURE_RATE_AREA,
  34. PerformanceWidgetSetting.USER_MISERY_AREA,
  35. ]}
  36. rowChartSettings={[]}
  37. forceDefaultChartSetting
  38. {...data}
  39. {...rest}
  40. />
  41. </PerformanceDisplayProvider>
  42. </MEPSettingProvider>
  43. </OrganizationContext.Provider>
  44. );
  45. }
  46. describe('Performance > Widgets > Query Batching', function () {
  47. let eventsMock: jest.Mock;
  48. let eventStatsMock: jest.Mock;
  49. beforeEach(function () {
  50. eventsMock = MockApiClient.addMockResponse({
  51. method: 'GET',
  52. url: '/organizations/org-slug/events/',
  53. body: {
  54. data: [
  55. {
  56. 'tpm()': 53.12,
  57. 'user_misery()': 0.023,
  58. 'failure_rate()': 0.012,
  59. },
  60. ],
  61. meta: {
  62. isMetricsData: false,
  63. },
  64. },
  65. });
  66. eventStatsMock = MockApiClient.addMockResponse({
  67. method: 'GET',
  68. url: `/organizations/org-slug/events-stats/`,
  69. body: {
  70. 'tpm()': {
  71. data: [
  72. [
  73. 1636822800,
  74. [
  75. {
  76. count: 30.0,
  77. },
  78. ],
  79. ],
  80. [
  81. 1636995600,
  82. [
  83. {
  84. count: 60.1,
  85. },
  86. ],
  87. ],
  88. ],
  89. order: 1,
  90. start: 1636822800,
  91. end: 1636995600,
  92. },
  93. 'user_misery()': {
  94. data: [
  95. [
  96. 1636822800,
  97. [
  98. {
  99. count: 0.02,
  100. },
  101. ],
  102. ],
  103. [
  104. 1636995600,
  105. [
  106. {
  107. count: 0.03,
  108. },
  109. ],
  110. ],
  111. ],
  112. order: 1,
  113. start: 1636822800,
  114. end: 1636995600,
  115. },
  116. 'failure_rate()': {
  117. data: [
  118. [
  119. 1636822800,
  120. [
  121. {
  122. count: 0.002,
  123. },
  124. ],
  125. ],
  126. [
  127. 1636995600,
  128. [
  129. {
  130. count: 0.001,
  131. },
  132. ],
  133. ],
  134. ],
  135. order: 2,
  136. start: 1636822800,
  137. end: 1636995600,
  138. },
  139. },
  140. });
  141. });
  142. it('EventsRequest and DiscoverQuery based component fires queries without provider', async function () {
  143. const data = initializeData();
  144. render(
  145. <WrappedComponent
  146. data={data}
  147. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  148. isMEPEnabled={false}
  149. />,
  150. {
  151. organization: data.organization,
  152. }
  153. );
  154. expect(await screen.findByTestId('performance-widget-title')).toBeInTheDocument();
  155. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  156. expect(eventStatsMock).toHaveBeenNthCalledWith(
  157. 1,
  158. expect.anything(),
  159. expect.objectContaining({
  160. query: expect.objectContaining({
  161. ...BASIC_QUERY_PARAMS,
  162. yAxis: 'tpm()',
  163. }),
  164. })
  165. );
  166. expect(eventsMock).toHaveBeenCalledTimes(1);
  167. expect(eventsMock).toHaveBeenNthCalledWith(
  168. 1,
  169. expect.anything(),
  170. expect.objectContaining({
  171. query: expect.objectContaining({
  172. environment: ['prod'],
  173. statsPeriod: '7d',
  174. field: ['tpm()'],
  175. }),
  176. })
  177. );
  178. });
  179. it('Multiple EventsRequest and DiscoverQuery based components fire individual queries without provider', async function () {
  180. const data = initializeData();
  181. render(
  182. <Fragment>
  183. <WrappedComponent
  184. data={data}
  185. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  186. isMEPEnabled={false}
  187. />
  188. <WrappedComponent
  189. data={data}
  190. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  191. isMEPEnabled={false}
  192. />
  193. <WrappedComponent
  194. data={data}
  195. defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA}
  196. isMEPEnabled={false}
  197. />
  198. </Fragment>,
  199. {
  200. organization: data.organization,
  201. }
  202. );
  203. expect(await screen.findAllByTestId('performance-widget-title')).toHaveLength(3);
  204. expect(eventStatsMock).toHaveBeenCalledTimes(3);
  205. expect(eventsMock).toHaveBeenCalledTimes(3);
  206. expect(eventStatsMock).toHaveBeenNthCalledWith(
  207. 1,
  208. expect.anything(),
  209. expect.objectContaining({
  210. query: expect.objectContaining({
  211. ...BASIC_QUERY_PARAMS,
  212. yAxis: 'tpm()',
  213. }),
  214. })
  215. );
  216. });
  217. it('Multiple EventsRequest and DiscoverQuery based components merge queries with provider', async function () {
  218. const data = initializeData();
  219. render(
  220. <GenericQueryBatcher>
  221. <WrappedComponent
  222. data={data}
  223. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  224. isMEPEnabled={false}
  225. />
  226. <WrappedComponent
  227. data={data}
  228. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  229. isMEPEnabled={false}
  230. />
  231. <WrappedComponent
  232. data={data}
  233. defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA}
  234. isMEPEnabled={false}
  235. />
  236. </GenericQueryBatcher>,
  237. {
  238. organization: data.organization,
  239. }
  240. );
  241. await waitFor(() => expect(eventStatsMock).toHaveBeenCalled());
  242. await waitFor(() => expect(eventsMock).toHaveBeenCalled());
  243. expect(eventsMock).toHaveBeenNthCalledWith(
  244. 1,
  245. '/organizations/org-slug/events/',
  246. expect.objectContaining({
  247. query: expect.objectContaining({
  248. environment: ['prod'],
  249. field: ['tpm()', 'failure_rate()', 'user_misery()'],
  250. statsPeriod: '7d',
  251. }),
  252. })
  253. );
  254. expect(eventsMock).toHaveBeenCalledTimes(1);
  255. expect(eventStatsMock).toHaveBeenNthCalledWith(
  256. 1,
  257. expect.anything(),
  258. expect.objectContaining({
  259. query: expect.objectContaining({
  260. ...BASIC_QUERY_PARAMS,
  261. yAxis: ['tpm()', 'failure_rate()', 'user_misery()'],
  262. }),
  263. })
  264. );
  265. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  266. expect(await screen.findAllByTestId('widget-state-has-data')).toHaveLength(3);
  267. });
  268. it('Multiple EventsRequest and DiscoverQuery based components merge queries with provider and add MEP', async function () {
  269. const data = initializeData();
  270. render(
  271. <GenericQueryBatcher>
  272. <WrappedComponent
  273. data={data}
  274. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  275. isMEPEnabled
  276. />
  277. <WrappedComponent
  278. data={data}
  279. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  280. isMEPEnabled
  281. />
  282. <WrappedComponent
  283. data={data}
  284. defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA}
  285. isMEPEnabled
  286. />
  287. </GenericQueryBatcher>,
  288. {
  289. organization: data.organization,
  290. }
  291. );
  292. expect(await screen.findAllByTestId('performance-widget-title')).toHaveLength(3);
  293. expect(eventStatsMock).toHaveBeenNthCalledWith(
  294. 1,
  295. expect.anything(),
  296. expect.objectContaining({
  297. query: expect.objectContaining({
  298. ...BASIC_QUERY_PARAMS,
  299. yAxis: ['tpm()', 'failure_rate()', 'user_misery()'],
  300. }),
  301. })
  302. );
  303. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  304. expect(await screen.findAllByTestId('widget-state-has-data')).toHaveLength(3);
  305. });
  306. it('Errors work correctly', async function () {
  307. eventStatsMock = MockApiClient.addMockResponse({
  308. method: 'GET',
  309. url: `/organizations/org-slug/events-stats/`,
  310. statusCode: 404,
  311. body: {},
  312. });
  313. const data = initializeData();
  314. render(
  315. <GenericQueryBatcher>
  316. <WrappedComponent
  317. data={data}
  318. defaultChartSetting={PerformanceWidgetSetting.TPM_AREA}
  319. isMEPEnabled={false}
  320. />
  321. <WrappedComponent
  322. data={data}
  323. defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA}
  324. isMEPEnabled={false}
  325. />
  326. <WrappedComponent
  327. data={data}
  328. defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA}
  329. isMEPEnabled={false}
  330. />
  331. </GenericQueryBatcher>,
  332. {
  333. organization: data.organization,
  334. }
  335. );
  336. expect(await screen.findAllByTestId('performance-widget-title')).toHaveLength(3);
  337. expect(eventStatsMock).toHaveBeenNthCalledWith(
  338. 1,
  339. expect.anything(),
  340. expect.objectContaining({
  341. query: expect.objectContaining({
  342. ...BASIC_QUERY_PARAMS,
  343. yAxis: ['tpm()', 'failure_rate()', 'user_misery()'],
  344. }),
  345. })
  346. );
  347. expect(eventStatsMock).toHaveBeenCalledTimes(1);
  348. expect(await screen.findAllByTestId('widget-state-is-errored')).toHaveLength(3);
  349. });
  350. });