useMetricsData.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import {useCallback, useEffect, useState} from 'react';
  2. import type {DateString, MetricsApiResponse} from 'sentry/types';
  3. import {getMetricsApiRequestQuery, mapToMRIFields} from 'sentry/utils/metrics';
  4. import type {MetricsQuery} from 'sentry/utils/metrics/types';
  5. import {useApiQuery} from 'sentry/utils/queryClient';
  6. import useOrganization from 'sentry/utils/useOrganization';
  7. import type {MetricsApiRequestQueryOptions} from '../../types/metrics';
  8. export function useMetricsData(
  9. {mri, op, datetime, projects, environments, query, groupBy}: MetricsQuery,
  10. overrides: Partial<MetricsApiRequestQueryOptions> = {}
  11. ) {
  12. const organization = useOrganization();
  13. const field = op ? `${op}(${mri})` : mri;
  14. const queryToSend = getMetricsApiRequestQuery(
  15. {
  16. field,
  17. query: query ?? '',
  18. groupBy,
  19. },
  20. {datetime, projects, environments},
  21. {...overrides}
  22. );
  23. const metricsApiRepsonse = useApiQuery<MetricsApiResponse>(
  24. [`/organizations/${organization.slug}/metrics/data/`, {query: queryToSend}],
  25. {
  26. retry: 0,
  27. staleTime: 0,
  28. refetchOnReconnect: true,
  29. refetchOnWindowFocus: true,
  30. refetchInterval: false,
  31. }
  32. );
  33. mapToMRIFields(metricsApiRepsonse.data, [field]);
  34. return metricsApiRepsonse;
  35. }
  36. // Wraps useMetricsData and provides two additional features:
  37. // 1. return data is undefined only during the initial load
  38. // 2. provides a callback to trim the data to a specific time range when chart zoom is used
  39. export function useMetricsDataZoom(
  40. metricsQuery: MetricsQuery,
  41. overrides: Partial<MetricsApiRequestQueryOptions> = {}
  42. ) {
  43. const [metricsData, setMetricsData] = useState<MetricsApiResponse | undefined>();
  44. const {
  45. data: rawData,
  46. isLoading,
  47. isError,
  48. error,
  49. } = useMetricsData(metricsQuery, overrides);
  50. useEffect(() => {
  51. if (rawData) {
  52. setMetricsData(rawData);
  53. }
  54. }, [rawData]);
  55. const trimData = useCallback(
  56. (
  57. currentData: MetricsApiResponse | undefined,
  58. start,
  59. end
  60. ): MetricsApiResponse | undefined => {
  61. if (!currentData) {
  62. return currentData;
  63. }
  64. // find the index of the first interval that is greater than the start time
  65. const startIndex =
  66. currentData.intervals.findIndex(interval => interval >= start) - 1;
  67. const endIndex = currentData.intervals.findIndex(interval => interval >= end);
  68. if (startIndex === -1 || endIndex === -1) {
  69. return currentData;
  70. }
  71. return {
  72. ...currentData,
  73. intervals: currentData.intervals.slice(startIndex, endIndex),
  74. groups: currentData.groups.map(group => ({
  75. ...group,
  76. series: Object.fromEntries(
  77. Object.entries(group.series).map(([seriesName, series]) => [
  78. seriesName,
  79. series.slice(startIndex, endIndex),
  80. ])
  81. ),
  82. })),
  83. };
  84. },
  85. []
  86. );
  87. const handleZoom = useCallback(
  88. (start: DateString, end: DateString) => {
  89. setMetricsData(currentData => trimData(currentData, start, end));
  90. },
  91. [trimData]
  92. );
  93. return {
  94. data: metricsData,
  95. isLoading,
  96. isError,
  97. error,
  98. onZoom: handleZoom,
  99. };
  100. }