useMetricsIntervalParam.tsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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';
  15. import {parsePeriodToHours} from 'sentry/utils/dates';
  16. import {useUpdateQuery} from 'sentry/utils/metrics';
  17. import {parseMRI} from 'sentry/utils/metrics/mri';
  18. import {isMetricsEquationWidget} 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: '10s', label: t('10 seconds')},
  25. {value: '1m', label: t('1 minute')},
  26. {value: '5m', label: t('5 minutes')},
  27. {value: '15m', label: t('15 minutes')},
  28. {value: '30m', label: t('30 minutes')},
  29. {value: '1h', label: t('1 hour')},
  30. {value: '4h', label: t('4 hours')},
  31. {value: '1d', label: t('1 day')},
  32. {value: '1w', label: t('1 week')},
  33. {value: '4w', label: t('1 month')},
  34. ];
  35. const minimumInterval = new GranularityLadder([
  36. [SIXTY_DAYS, '1d'],
  37. [THIRTY_DAYS, '2h'],
  38. [TWO_WEEKS, '1h'],
  39. [ONE_WEEK, '30m'],
  40. [TWENTY_FOUR_HOURS, '5m'],
  41. [ONE_HOUR, '1m'],
  42. [0, '1m'],
  43. ]);
  44. const maximumInterval = new GranularityLadder([
  45. [SIXTY_DAYS, '4w'],
  46. [THIRTY_DAYS, '1w'],
  47. [TWO_WEEKS, '1w'],
  48. [ONE_WEEK, '1d'],
  49. [TWENTY_FOUR_HOURS, '6h'],
  50. [ONE_HOUR, '15m'],
  51. [0, '1m'],
  52. ]);
  53. export function getIntervalOptionsForStatsPeriod(
  54. datetime: PageFilters['datetime'],
  55. isCustomMetricsOnly: boolean
  56. ) {
  57. const diffInMinutes = getDiffInMinutes(datetime);
  58. const diffInHours = diffInMinutes / 60;
  59. const minimumOption =
  60. // BE returns empty timeseries if we request less than 1 minute granularity
  61. // for other data sets than custom metrics
  62. isCustomMetricsOnly && diffInHours <= 1
  63. ? '10s'
  64. : minimumInterval.getInterval(diffInMinutes);
  65. const minimumOptionInHours = parsePeriodToHours(minimumOption);
  66. const maximumOption = maximumInterval.getInterval(diffInMinutes);
  67. const maximumOptionInHours = parsePeriodToHours(maximumOption);
  68. return ALL_INTERVAL_OPTIONS.filter(option => {
  69. const optionInHours = parsePeriodToHours(option.value);
  70. return optionInHours >= minimumOptionInHours && optionInHours <= maximumOptionInHours;
  71. });
  72. }
  73. export function useMetricsIntervalParam() {
  74. const {datetime} = usePageFilters().selection;
  75. const {interval} = useLocationQuery({fields: {interval: decodeScalar}});
  76. const {widgets} = useMetricsContext();
  77. const isCustomMetricsOnly = useMemo(() => {
  78. return widgets.every(
  79. widget =>
  80. isMetricsEquationWidget(widget) || parseMRI(widget.mri)?.useCase === 'custom'
  81. );
  82. }, [widgets]);
  83. const currentIntervalOptions = useMemo(
  84. () => getIntervalOptionsForStatsPeriod(datetime, isCustomMetricsOnly),
  85. [datetime, isCustomMetricsOnly]
  86. );
  87. const updateQuery = useUpdateQuery();
  88. const setInterval = useCallback(
  89. (newInterval: string) => {
  90. updateQuery({interval: newInterval}, {replace: true});
  91. },
  92. [updateQuery]
  93. );
  94. const validatedInterval = useMemo(() => {
  95. const isPeriod = !!parseStatsPeriod(interval);
  96. const currentIntervalValues = currentIntervalOptions.map(option => option.value);
  97. return isPeriod && currentIntervalValues.includes(interval)
  98. ? interval
  99. : // Take the 2nd most granular option if available
  100. currentIntervalOptions[1]?.value ?? currentIntervalOptions[0].value;
  101. }, [currentIntervalOptions, interval]);
  102. useEffect(() => {
  103. if (interval !== validatedInterval) {
  104. setInterval(validatedInterval);
  105. }
  106. }, [interval, validatedInterval, setInterval]);
  107. return {
  108. interval: validatedInterval,
  109. setInterval,
  110. currentIntervalOptions,
  111. };
  112. }