useMetricsData.tsx 3.2 KB

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