useMetricsIntervalParam.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import {useCallback, useEffect, useMemo} from 'react';
  2. import {
  3. getDiffInMinutes,
  4. GranularityLadder,
  5. ONE_HOUR,
  6. ONE_WEEK,
  7. SIXTY_DAYS,
  8. THIRTY_DAYS,
  9. TWENTY_FOUR_HOURS,
  10. TWO_WEEKS,
  11. } from 'sentry/components/charts/utils';
  12. import {parseStatsPeriod} from 'sentry/components/organizations/pageFilters/parse';
  13. import {t} from 'sentry/locale';
  14. import type {PageFilters} from 'sentry/types/core';
  15. import {parsePeriodToHours} from 'sentry/utils/duration/parsePeriodToHours';
  16. import {useUpdateQuery} from 'sentry/utils/metrics';
  17. import {parseMRI} from 'sentry/utils/metrics/mri';
  18. import {isMetricsQueryWidget} from 'sentry/utils/metrics/types';
  19. import {decodeScalar} from 'sentry/utils/queryString';
  20. import useLocationQuery from 'sentry/utils/url/useLocationQuery';
  21. import usePageFilters from 'sentry/utils/usePageFilters';
  22. import {useMetricsContext} from 'sentry/views/metrics/context';
  23. const ALL_INTERVAL_OPTIONS = [
  24. {value: '1m', label: t('1 minute')},
  25. {value: '5m', label: t('5 minutes')},
  26. {value: '15m', label: t('15 minutes')},
  27. {value: '30m', label: t('30 minutes')},
  28. {value: '1h', label: t('1 hour')},
  29. {value: '4h', label: t('4 hours')},
  30. {value: '1d', label: t('1 day')},
  31. {value: '1w', label: t('1 week')},
  32. {value: '4w', label: t('1 month')},
  33. ];
  34. const minimumInterval = new GranularityLadder([
  35. [SIXTY_DAYS, '1d'],
  36. [THIRTY_DAYS, '2h'],
  37. [TWO_WEEKS, '1h'],
  38. [ONE_WEEK, '30m'],
  39. [TWENTY_FOUR_HOURS, '5m'],
  40. [ONE_HOUR, '1m'],
  41. [0, '1m'],
  42. ]);
  43. const maximumInterval = new GranularityLadder([
  44. [SIXTY_DAYS, '4w'],
  45. [THIRTY_DAYS, '1w'],
  46. [TWO_WEEKS, '1w'],
  47. [ONE_WEEK, '1d'],
  48. [TWENTY_FOUR_HOURS, '6h'],
  49. [ONE_HOUR, '15m'],
  50. [0, '1m'],
  51. ]);
  52. export function getIntervalOptionsForStatsPeriod(
  53. datetime: PageFilters['datetime'],
  54. maxInterval?: string
  55. ) {
  56. const diffInMinutes = getDiffInMinutes(datetime);
  57. const maxIntervalInHours = maxInterval && parsePeriodToHours(maxInterval);
  58. const minimumOption = minimumInterval.getInterval(diffInMinutes);
  59. const minimumOptionInHours = parsePeriodToHours(minimumOption);
  60. const defaultMaximumOption = maximumInterval.getInterval(diffInMinutes);
  61. const maximumOption =
  62. maxIntervalInHours && maxIntervalInHours > parsePeriodToHours(defaultMaximumOption)
  63. ? maxInterval
  64. : defaultMaximumOption;
  65. const maximumOptionInHours = parsePeriodToHours(maximumOption);
  66. return ALL_INTERVAL_OPTIONS.filter(option => {
  67. const optionInHours = parsePeriodToHours(option.value);
  68. return optionInHours >= minimumOptionInHours && optionInHours <= maximumOptionInHours;
  69. });
  70. }
  71. export function validateInterval(
  72. interval: string,
  73. options: {label: string; value: string; disabled?: boolean}[]
  74. ) {
  75. const isPeriod = !!parseStatsPeriod(interval);
  76. const enabledOptions = options.filter(option => !option.disabled);
  77. const currentIntervalValues = enabledOptions.map(option => option.value);
  78. return isPeriod && currentIntervalValues.includes(interval)
  79. ? interval
  80. : // Take the 2nd most granular option if available
  81. enabledOptions[1]?.value ?? enabledOptions[0].value;
  82. }
  83. export function useMetricsIntervalParam() {
  84. const {datetime} = usePageFilters().selection;
  85. const {interval} = useLocationQuery({fields: {interval: decodeScalar}});
  86. const {widgets} = useMetricsContext();
  87. const updateQuery = useUpdateQuery();
  88. const hasSetMetric = useMemo(
  89. () =>
  90. widgets.some(
  91. widget => isMetricsQueryWidget(widget) && parseMRI(widget.mri)!.type === 's'
  92. ),
  93. [widgets]
  94. );
  95. const handleIntervalChange = useCallback(
  96. (newInterval: string) => {
  97. updateQuery({interval: newInterval}, {replace: true});
  98. },
  99. [updateQuery]
  100. );
  101. const metricsIntervalOptions = useMetricsIntervalOptions({
  102. interval,
  103. hasSetMetric,
  104. datetime,
  105. onIntervalChange: handleIntervalChange,
  106. });
  107. useEffect(() => {
  108. if (interval !== metricsIntervalOptions.interval) {
  109. handleIntervalChange(metricsIntervalOptions.interval);
  110. }
  111. }, [interval, metricsIntervalOptions.interval, handleIntervalChange]);
  112. return metricsIntervalOptions;
  113. }
  114. export interface MetricsIntervalParamProps {
  115. datetime: PageFilters['datetime'];
  116. hasSetMetric: boolean;
  117. interval: string;
  118. onIntervalChange: (interval: string) => void;
  119. }
  120. export function useMetricsIntervalOptions({
  121. interval,
  122. hasSetMetric,
  123. datetime,
  124. onIntervalChange,
  125. }: MetricsIntervalParamProps) {
  126. const currentIntervalOptions = useMemo(() => {
  127. const minInterval = hasSetMetric ? '1h' : undefined;
  128. const options = getIntervalOptionsForStatsPeriod(datetime, minInterval);
  129. if (!hasSetMetric) {
  130. return options;
  131. }
  132. // Disable all intervals that are smaller than one hour for set metrics
  133. return options.map(option => {
  134. return parsePeriodToHours(option.value) < 1
  135. ? {
  136. ...option,
  137. disabled: true,
  138. tooltip: t(
  139. 'Intervals smaller than 1 hour are not available for set metrics.'
  140. ),
  141. }
  142. : option;
  143. });
  144. }, [datetime, hasSetMetric]);
  145. const setInterval = useCallback(
  146. (newInterval: string) => {
  147. onIntervalChange(newInterval);
  148. },
  149. [onIntervalChange]
  150. );
  151. const validatedInterval = useMemo(
  152. () => validateInterval(interval, currentIntervalOptions),
  153. [interval, currentIntervalOptions]
  154. );
  155. return {
  156. interval: validatedInterval,
  157. setInterval,
  158. currentIntervalOptions,
  159. };
  160. }