useSpanMetricsSeries.spec.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  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 useOrganization from 'sentry/utils/useOrganization';
  9. import usePageFilters from 'sentry/utils/usePageFilters';
  10. import {useSpanMetricsSeries} from 'sentry/views/starfish/queries/useSpanMetricsSeries';
  11. import type {MetricsProperty} from 'sentry/views/starfish/types';
  12. jest.mock('sentry/utils/useLocation');
  13. jest.mock('sentry/utils/usePageFilters');
  14. jest.mock('sentry/utils/useOrganization');
  15. function Wrapper({children}: {children?: ReactNode}) {
  16. return (
  17. <QueryClientProvider client={makeTestQueryClient()}>{children}</QueryClientProvider>
  18. );
  19. }
  20. describe('useSpanMetricsSeries', () => {
  21. const organization = OrganizationFixture();
  22. jest.mocked(usePageFilters).mockReturnValue({
  23. isReady: true,
  24. desyncedFilters: new Set(),
  25. pinnedFilters: new Set(),
  26. shouldPersist: true,
  27. selection: {
  28. datetime: {
  29. period: '10d',
  30. start: null,
  31. end: null,
  32. utc: false,
  33. },
  34. environments: [],
  35. projects: [],
  36. },
  37. });
  38. jest.mocked(useLocation).mockReturnValue({
  39. pathname: '',
  40. search: '',
  41. query: {},
  42. hash: '',
  43. state: undefined,
  44. action: 'PUSH',
  45. key: '',
  46. });
  47. jest.mocked(useOrganization).mockReturnValue(organization);
  48. it('respects the `enabled` prop', () => {
  49. const eventsRequest = MockApiClient.addMockResponse({
  50. url: `/organizations/${organization.slug}/events-stats/`,
  51. method: 'GET',
  52. body: {},
  53. });
  54. const {result} = renderHook(
  55. ({filters, enabled}) =>
  56. useSpanMetricsSeries({
  57. search: MutableSearch.fromQueryObject(filters),
  58. enabled,
  59. }),
  60. {
  61. wrapper: Wrapper,
  62. initialProps: {
  63. filters: {
  64. 'span.group': '221aa7ebd216',
  65. },
  66. enabled: false,
  67. },
  68. }
  69. );
  70. expect(result.current.isFetching).toEqual(false);
  71. expect(eventsRequest).not.toHaveBeenCalled();
  72. });
  73. it('queries for current selection', async () => {
  74. const eventsRequest = MockApiClient.addMockResponse({
  75. url: `/organizations/${organization.slug}/events-stats/`,
  76. method: 'GET',
  77. body: {
  78. 'spm()': {
  79. data: [
  80. [1699907700, [{count: 7810.2}]],
  81. [1699908000, [{count: 1216.8}]],
  82. ],
  83. },
  84. },
  85. });
  86. const {result} = renderHook(
  87. ({filters, yAxis}) =>
  88. useSpanMetricsSeries({search: MutableSearch.fromQueryObject(filters), yAxis}),
  89. {
  90. wrapper: Wrapper,
  91. initialProps: {
  92. filters: {
  93. 'span.group': '221aa7ebd216',
  94. transaction: '/api/details',
  95. release: '0.0.1',
  96. 'resource.render_blocking_status': 'blocking' as const,
  97. environment: undefined,
  98. },
  99. yAxis: ['spm()'] as MetricsProperty[],
  100. },
  101. }
  102. );
  103. expect(result.current.isLoading).toEqual(true);
  104. expect(eventsRequest).toHaveBeenCalledWith(
  105. '/organizations/org-slug/events-stats/',
  106. expect.objectContaining({
  107. method: 'GET',
  108. query: expect.objectContaining({
  109. query: `span.group:221aa7ebd216 transaction:/api/details release:0.0.1 resource.render_blocking_status:blocking`,
  110. dataset: 'spansMetrics',
  111. statsPeriod: '10d',
  112. referrer: 'span-metrics-series',
  113. interval: '30m',
  114. yAxis: 'spm()',
  115. }),
  116. })
  117. );
  118. await waitFor(() => expect(result.current.isLoading).toEqual(false));
  119. });
  120. it('adjusts interval based on the yAxis', async () => {
  121. const eventsRequest = MockApiClient.addMockResponse({
  122. url: `/organizations/${organization.slug}/events-stats/`,
  123. method: 'GET',
  124. body: {},
  125. });
  126. const {rerender} = renderHook(({yAxis}) => useSpanMetricsSeries({yAxis}), {
  127. wrapper: Wrapper,
  128. initialProps: {
  129. yAxis: ['avg(span.self_time)', 'spm()'] as MetricsProperty[],
  130. },
  131. });
  132. expect(eventsRequest).toHaveBeenLastCalledWith(
  133. '/organizations/org-slug/events-stats/',
  134. expect.objectContaining({
  135. method: 'GET',
  136. query: expect.objectContaining({
  137. interval: '30m',
  138. yAxis: ['avg(span.self_time)', 'spm()'] as MetricsProperty[],
  139. }),
  140. })
  141. );
  142. rerender({
  143. yAxis: ['p95(span.self_time)', 'spm()'] as MetricsProperty[],
  144. });
  145. await waitFor(() =>
  146. expect(eventsRequest).toHaveBeenLastCalledWith(
  147. '/organizations/org-slug/events-stats/',
  148. expect.objectContaining({
  149. method: 'GET',
  150. query: expect.objectContaining({
  151. interval: '1h',
  152. yAxis: ['p95(span.self_time)', 'spm()'] as MetricsProperty[],
  153. }),
  154. })
  155. )
  156. );
  157. });
  158. it('rolls single-axis responses up into a series', async () => {
  159. MockApiClient.addMockResponse({
  160. url: `/organizations/${organization.slug}/events-stats/`,
  161. method: 'GET',
  162. body: {
  163. data: [
  164. [1699907700, [{count: 7810.2}]],
  165. [1699908000, [{count: 1216.8}]],
  166. ],
  167. },
  168. });
  169. const {result} = renderHook(({yAxis}) => useSpanMetricsSeries({yAxis}), {
  170. wrapper: Wrapper,
  171. initialProps: {
  172. yAxis: ['spm()'] as MetricsProperty[],
  173. },
  174. });
  175. await waitFor(() => expect(result.current.isLoading).toEqual(false));
  176. expect(result.current.data).toEqual({
  177. 'spm()': {
  178. data: [
  179. {name: '2023-11-13T20:35:00+00:00', value: 7810.2},
  180. {name: '2023-11-13T20:40:00+00:00', value: 1216.8},
  181. ],
  182. seriesName: 'spm()',
  183. },
  184. });
  185. });
  186. it('rolls multi-axis responses up into multiple series', async () => {
  187. MockApiClient.addMockResponse({
  188. url: `/organizations/${organization.slug}/events-stats/`,
  189. method: 'GET',
  190. body: {
  191. 'http_response_rate(3)': {
  192. data: [
  193. [1699907700, [{count: 10.1}]],
  194. [1699908000, [{count: 11.2}]],
  195. ],
  196. },
  197. 'http_response_rate(4)': {
  198. data: [
  199. [1699907700, [{count: 12.6}]],
  200. [1699908000, [{count: 13.8}]],
  201. ],
  202. },
  203. },
  204. });
  205. const {result} = renderHook(({yAxis}) => useSpanMetricsSeries({yAxis}), {
  206. wrapper: Wrapper,
  207. initialProps: {
  208. yAxis: ['http_response_rate(3)', 'http_response_rate(4)'] as MetricsProperty[],
  209. },
  210. });
  211. await waitFor(() => expect(result.current.isLoading).toEqual(false));
  212. expect(result.current.data).toEqual({
  213. 'http_response_rate(3)': {
  214. data: [
  215. {name: '2023-11-13T20:35:00+00:00', value: 10.1},
  216. {name: '2023-11-13T20:40:00+00:00', value: 11.2},
  217. ],
  218. seriesName: 'http_response_rate(3)',
  219. },
  220. 'http_response_rate(4)': {
  221. data: [
  222. {name: '2023-11-13T20:35:00+00:00', value: 12.6},
  223. {name: '2023-11-13T20:40:00+00:00', value: 13.8},
  224. ],
  225. seriesName: 'http_response_rate(4)',
  226. },
  227. });
  228. });
  229. });