useDiscoverSeries.spec.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. import type {ReactNode} from 'react';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {makeTestQueryClient} from 'sentry-test/queryClient';
  4. import {renderHook, waitFor} from 'sentry-test/reactTestingLibrary';
  5. import {QueryClientProvider} from 'sentry/utils/queryClient';
  6. import {MutableSearch} from 'sentry/utils/tokenizeSearch';
  7. import {useLocation} from 'sentry/utils/useLocation';
  8. import usePageFilters from 'sentry/utils/usePageFilters';
  9. import {useSpanMetricsSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
  10. import type {SpanMetricsProperty} from 'sentry/views/insights/types';
  11. import {OrganizationContext} from 'sentry/views/organizationContext';
  12. jest.mock('sentry/utils/useLocation');
  13. jest.mock('sentry/utils/usePageFilters');
  14. describe('useSpanMetricsSeries', () => {
  15. const organization = OrganizationFixture();
  16. function Wrapper({children}: {children?: ReactNode}) {
  17. return (
  18. <QueryClientProvider client={makeTestQueryClient()}>
  19. <OrganizationContext.Provider value={organization}>
  20. {children}
  21. </OrganizationContext.Provider>
  22. </QueryClientProvider>
  23. );
  24. }
  25. jest.mocked(usePageFilters).mockReturnValue({
  26. isReady: true,
  27. desyncedFilters: new Set(),
  28. pinnedFilters: new Set(),
  29. shouldPersist: true,
  30. selection: {
  31. datetime: {
  32. period: '10d',
  33. start: null,
  34. end: null,
  35. utc: false,
  36. },
  37. environments: [],
  38. projects: [],
  39. },
  40. });
  41. jest.mocked(useLocation).mockReturnValue({
  42. pathname: '',
  43. search: '',
  44. query: {},
  45. hash: '',
  46. state: undefined,
  47. action: 'PUSH',
  48. key: '',
  49. });
  50. it('respects the `enabled` prop', () => {
  51. const eventsRequest = MockApiClient.addMockResponse({
  52. url: `/organizations/${organization.slug}/events-stats/`,
  53. method: 'GET',
  54. body: {},
  55. });
  56. const {result} = renderHook(
  57. ({filters, enabled}) =>
  58. useSpanMetricsSeries(
  59. {
  60. search: MutableSearch.fromQueryObject(filters),
  61. enabled,
  62. },
  63. 'span-metrics-series'
  64. ),
  65. {
  66. wrapper: Wrapper,
  67. initialProps: {
  68. filters: {
  69. 'span.group': '221aa7ebd216',
  70. },
  71. enabled: false,
  72. },
  73. }
  74. );
  75. expect(result.current.isFetching).toBe(false);
  76. expect(eventsRequest).not.toHaveBeenCalled();
  77. });
  78. it('queries for current selection', async () => {
  79. const eventsRequest = MockApiClient.addMockResponse({
  80. url: `/organizations/${organization.slug}/events-stats/`,
  81. method: 'GET',
  82. body: {
  83. 'spm()': {
  84. data: [
  85. [1699907700, [{count: 7810.2}]],
  86. [1699908000, [{count: 1216.8}]],
  87. ],
  88. },
  89. },
  90. });
  91. const {result} = renderHook(
  92. ({filters, yAxis}) =>
  93. useSpanMetricsSeries(
  94. {search: MutableSearch.fromQueryObject(filters), yAxis},
  95. 'span-metrics-series'
  96. ),
  97. {
  98. wrapper: Wrapper,
  99. initialProps: {
  100. filters: {
  101. 'span.group': '221aa7ebd216',
  102. transaction: '/api/details',
  103. release: '0.0.1',
  104. 'resource.render_blocking_status': 'blocking' as const,
  105. environment: undefined,
  106. },
  107. yAxis: ['spm()'] as SpanMetricsProperty[],
  108. },
  109. }
  110. );
  111. expect(result.current.isPending).toBe(true);
  112. expect(eventsRequest).toHaveBeenCalledWith(
  113. '/organizations/org-slug/events-stats/',
  114. expect.objectContaining({
  115. method: 'GET',
  116. query: expect.objectContaining({
  117. query: `span.group:221aa7ebd216 transaction:/api/details release:0.0.1 resource.render_blocking_status:blocking`,
  118. dataset: 'spansMetrics',
  119. statsPeriod: '10d',
  120. referrer: 'span-metrics-series',
  121. interval: '30m',
  122. yAxis: 'spm()',
  123. }),
  124. })
  125. );
  126. await waitFor(() => expect(result.current.isPending).toBe(false));
  127. });
  128. it('adjusts interval based on the yAxis', async () => {
  129. const eventsRequest = MockApiClient.addMockResponse({
  130. url: `/organizations/${organization.slug}/events-stats/`,
  131. method: 'GET',
  132. body: {},
  133. });
  134. const {rerender} = renderHook(
  135. ({yAxis}) => useSpanMetricsSeries({yAxis}, 'span-metrics-series'),
  136. {
  137. wrapper: Wrapper,
  138. initialProps: {
  139. yAxis: ['avg(span.self_time)', 'spm()'] as SpanMetricsProperty[],
  140. },
  141. }
  142. );
  143. expect(eventsRequest).toHaveBeenLastCalledWith(
  144. '/organizations/org-slug/events-stats/',
  145. expect.objectContaining({
  146. method: 'GET',
  147. query: expect.objectContaining({
  148. interval: '30m',
  149. yAxis: ['avg(span.self_time)', 'spm()'] as SpanMetricsProperty[],
  150. }),
  151. })
  152. );
  153. rerender({
  154. yAxis: ['p95(span.self_time)', 'spm()'] as SpanMetricsProperty[],
  155. });
  156. await waitFor(() =>
  157. expect(eventsRequest).toHaveBeenLastCalledWith(
  158. '/organizations/org-slug/events-stats/',
  159. expect.objectContaining({
  160. method: 'GET',
  161. query: expect.objectContaining({
  162. interval: '1h',
  163. yAxis: ['p95(span.self_time)', 'spm()'] as SpanMetricsProperty[],
  164. }),
  165. })
  166. )
  167. );
  168. });
  169. it('rolls single-axis responses up into a series', async () => {
  170. MockApiClient.addMockResponse({
  171. url: `/organizations/${organization.slug}/events-stats/`,
  172. method: 'GET',
  173. body: {
  174. data: [
  175. [1699907700, [{count: 7810.2}]],
  176. [1699908000, [{count: 1216.8}]],
  177. ],
  178. meta: {
  179. fields: {
  180. 'spm()': 'rate',
  181. },
  182. units: {
  183. 'spm()': '1/minute',
  184. },
  185. },
  186. },
  187. });
  188. const {result} = renderHook(
  189. ({yAxis}) => useSpanMetricsSeries({yAxis}, 'span-metrics-series'),
  190. {
  191. wrapper: Wrapper,
  192. initialProps: {
  193. yAxis: ['spm()'] as SpanMetricsProperty[],
  194. },
  195. }
  196. );
  197. await waitFor(() => expect(result.current.isPending).toBe(false));
  198. expect(result.current.data).toEqual({
  199. 'spm()': {
  200. data: [
  201. {name: '2023-11-13T20:35:00+00:00', value: 7810.2},
  202. {name: '2023-11-13T20:40:00+00:00', value: 1216.8},
  203. ],
  204. meta: {
  205. fields: {
  206. 'spm()': 'rate',
  207. },
  208. units: {
  209. 'spm()': '1/minute',
  210. },
  211. },
  212. seriesName: 'spm()',
  213. },
  214. });
  215. });
  216. it('rolls multi-axis responses up into multiple series', async () => {
  217. MockApiClient.addMockResponse({
  218. url: `/organizations/${organization.slug}/events-stats/`,
  219. method: 'GET',
  220. body: {
  221. 'http_response_rate(3)': {
  222. data: [
  223. [1699907700, [{count: 10.1}]],
  224. [1699908000, [{count: 11.2}]],
  225. ],
  226. meta: {
  227. fields: {
  228. 'http_response_rate(3)': 'rate',
  229. },
  230. units: {
  231. 'http_response_rate(3)': '1/minute',
  232. },
  233. },
  234. },
  235. 'http_response_rate(4)': {
  236. data: [
  237. [1699907700, [{count: 12.6}]],
  238. [1699908000, [{count: 13.8}]],
  239. ],
  240. meta: {
  241. fields: {
  242. 'http_response_rate(4)': 'rate',
  243. },
  244. units: {
  245. 'http_response_rate(4)': '1/minute',
  246. },
  247. },
  248. },
  249. },
  250. });
  251. const {result} = renderHook(
  252. ({yAxis}) => useSpanMetricsSeries({yAxis}, 'span-metrics-series'),
  253. {
  254. wrapper: Wrapper,
  255. initialProps: {
  256. yAxis: [
  257. 'http_response_rate(3)',
  258. 'http_response_rate(4)',
  259. ] as SpanMetricsProperty[],
  260. },
  261. }
  262. );
  263. await waitFor(() => expect(result.current.isPending).toBe(false));
  264. expect(result.current.data).toEqual({
  265. 'http_response_rate(3)': {
  266. data: [
  267. {name: '2023-11-13T20:35:00+00:00', value: 10.1},
  268. {name: '2023-11-13T20:40:00+00:00', value: 11.2},
  269. ],
  270. meta: {
  271. fields: {
  272. 'http_response_rate(3)': 'rate',
  273. },
  274. units: {
  275. 'http_response_rate(3)': '1/minute',
  276. },
  277. },
  278. seriesName: 'http_response_rate(3)',
  279. },
  280. 'http_response_rate(4)': {
  281. data: [
  282. {name: '2023-11-13T20:35:00+00:00', value: 12.6},
  283. {name: '2023-11-13T20:40:00+00:00', value: 13.8},
  284. ],
  285. meta: {
  286. fields: {
  287. 'http_response_rate(4)': 'rate',
  288. },
  289. units: {
  290. 'http_response_rate(4)': '1/minute',
  291. },
  292. },
  293. seriesName: 'http_response_rate(4)',
  294. },
  295. });
  296. });
  297. it('returns a series for all requested yAxis even without data', async () => {
  298. MockApiClient.addMockResponse({
  299. url: `/organizations/${organization.slug}/events-stats/`,
  300. method: 'GET',
  301. body: {},
  302. });
  303. const {result} = renderHook(
  304. ({yAxis}) => useSpanMetricsSeries({yAxis}, 'span-metrics-series'),
  305. {
  306. wrapper: Wrapper,
  307. initialProps: {
  308. yAxis: [
  309. 'http_response_rate(3)',
  310. 'http_response_rate(4)',
  311. ] as SpanMetricsProperty[],
  312. },
  313. }
  314. );
  315. await waitFor(() => expect(result.current.isPending).toBe(false));
  316. expect(result.current.data).toEqual({
  317. 'http_response_rate(3)': {
  318. data: [],
  319. meta: undefined,
  320. seriesName: 'http_response_rate(3)',
  321. },
  322. 'http_response_rate(4)': {
  323. data: [],
  324. meta: undefined,
  325. seriesName: 'http_response_rate(4)',
  326. },
  327. });
  328. });
  329. });