queryBatcher.spec.tsx 10 KB

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