queryBatcher.spec.tsx 10 KB

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