useProfileEventsStats.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import {useQuery} from '@tanstack/react-query';
  2. import {ResponseMeta} from 'sentry/api';
  3. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  4. import {EventsStatsSeries} from 'sentry/types';
  5. import useApi from 'sentry/utils/useApi';
  6. import useOrganization from 'sentry/utils/useOrganization';
  7. import usePageFilters from 'sentry/utils/usePageFilters';
  8. interface UseProfileEventStatsOptions<F> {
  9. referrer: string;
  10. yAxes: readonly F[];
  11. interval?: string;
  12. query?: string;
  13. }
  14. export function useProfileEventsStats<F extends string>({
  15. yAxes,
  16. interval,
  17. query,
  18. referrer,
  19. }: UseProfileEventStatsOptions<F>) {
  20. const api = useApi();
  21. const organization = useOrganization();
  22. const {selection} = usePageFilters();
  23. const path = `/organizations/${organization.slug}/events-stats/`;
  24. const endpointOptions = {
  25. query: {
  26. dataset: 'profiles',
  27. referrer,
  28. project: selection.projects,
  29. environment: selection.environments,
  30. ...normalizeDateTimeParams(selection.datetime),
  31. yAxis: yAxes,
  32. interval,
  33. query,
  34. },
  35. };
  36. const queryKey = [path, endpointOptions];
  37. const queryFn = () =>
  38. api
  39. .requestPromise(path, {
  40. method: 'GET',
  41. includeAllArgs: true,
  42. query: endpointOptions.query,
  43. })
  44. .then(response => transformStatsResponse(yAxes, response));
  45. return useQuery<ApiResponse<EventsStatsSeries<F>>>({
  46. queryKey,
  47. queryFn,
  48. refetchOnWindowFocus: false,
  49. retry: false,
  50. });
  51. }
  52. type ApiResponse<F> = [F, string | undefined, ResponseMeta | undefined];
  53. function transformStatsResponse<F extends string>(
  54. yAxes: readonly F[],
  55. rawResponse: ApiResponse<any>
  56. ): ApiResponse<EventsStatsSeries<F>> {
  57. // the events stats endpoint has a legacy response format so here we transform it
  58. // into the proposed update for forward compatibility and ease of use
  59. if (yAxes.length === 0) {
  60. return [
  61. {
  62. data: [],
  63. meta: {
  64. dataset: 'profiles',
  65. end: 0,
  66. start: 0,
  67. },
  68. timestamps: [],
  69. },
  70. rawResponse[1],
  71. rawResponse[2],
  72. ];
  73. }
  74. if (yAxes.length === 1) {
  75. const {series, meta, timestamps} = transformSingleSeries(yAxes[0], rawResponse[0]);
  76. return [
  77. {
  78. data: [series],
  79. meta,
  80. timestamps,
  81. },
  82. rawResponse[1],
  83. rawResponse[2],
  84. ];
  85. }
  86. const data: EventsStatsSeries<F>['data'] = [];
  87. let meta: EventsStatsSeries<F>['meta'] = {
  88. dataset: 'profiles',
  89. end: -1,
  90. start: -1,
  91. };
  92. let timestamps: EventsStatsSeries<F>['timestamps'] = [];
  93. let firstAxis = true;
  94. for (const yAxis of yAxes) {
  95. const transformed = transformSingleSeries(yAxis, rawResponse[0][yAxis]);
  96. if (firstAxis) {
  97. meta = transformed.meta;
  98. timestamps = transformed.timestamps;
  99. } else if (
  100. meta.start !== transformed.meta.start ||
  101. meta.end !== transformed.meta.end
  102. ) {
  103. throw new Error('Mismatching start/end times');
  104. } else if (
  105. timestamps.length !== transformed.timestamps.length ||
  106. timestamps.some((ts, i) => ts !== transformed.timestamps[i])
  107. ) {
  108. throw new Error('Mismatching timestamps');
  109. }
  110. data.push(transformed.series);
  111. firstAxis = false;
  112. }
  113. return [
  114. {
  115. data,
  116. meta,
  117. timestamps,
  118. },
  119. rawResponse[1],
  120. rawResponse[2],
  121. ];
  122. }
  123. function transformSingleSeries<F extends string>(yAxis: F, rawSeries: any) {
  124. const series: EventsStatsSeries<F>['data'][number] = {
  125. axis: yAxis,
  126. values: [],
  127. };
  128. const meta: EventsStatsSeries<F>['meta'] = {
  129. dataset: 'profiles',
  130. end: rawSeries.end,
  131. start: rawSeries.start,
  132. };
  133. const timestamps: EventsStatsSeries<F>['timestamps'] = [];
  134. for (let i = 0; i < rawSeries.data.length; i++) {
  135. const [timestamp, value] = rawSeries.data[i];
  136. // the api has this awkward structure for legacy reason
  137. series.values.push(value[0].count as number);
  138. timestamps.push(timestamp);
  139. }
  140. return {
  141. series,
  142. meta,
  143. timestamps,
  144. };
  145. }