Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions static/app/utils/useChartInterval.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,39 @@ describe('useChartInterval', () => {
expect(chartInterval).toBe('3h');
});

it('offers a 30 day interval for long ranges when requested', () => {
let chartInterval!: ReturnType<typeof useChartInterval>[0];
let intervalOptions!: ReturnType<typeof useChartInterval>[2];
let defaultIntervalOptions!: ReturnType<typeof useChartInterval>[2];

function TestPage() {
// oxlint-disable-next-line react/globals -- Test captures the hook result in an outer variable to assert on it.
[chartInterval, , intervalOptions] = useChartInterval({
unspecifiedStrategy: ChartIntervalUnspecifiedStrategy.USE_BIGGEST,
includeThirtyDayInterval: true,
});
// oxlint-disable-next-line react/globals -- Test captures the hook result in an outer variable to assert on it.
[, , defaultIntervalOptions] = useChartInterval();
return null;
}

render(<TestPage />);
expect(intervalOptions.map(o => o.value)).not.toContain('30d');

act(() =>
PageFiltersStore.updateDateTime({
period: '90d',
start: null,
end: null,
utc: true,
})
);

expect(intervalOptions).toContainEqual({value: '30d', label: '30 days'});
expect(chartInterval).not.toBe('30d');
expect(defaultIntervalOptions.map(o => o.value)).not.toContain('30d');
});

it('preserves a custom interval that is not too granular for the time range', () => {
// Default 14d period produces ladder-derived options ['1h', '3h', '6h', '1d'].
// A 12h interval is coarser than the minimum (1h), so it should be preserved
Expand Down
22 changes: 20 additions & 2 deletions static/app/utils/useChartInterval.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
GranularityLadder,
ONE_HOUR,
SIX_HOURS,
SIXTY_DAYS,
THIRTY_DAYS,
TWELVE_HOURS,
TWO_WEEKS,
Expand All @@ -31,7 +32,14 @@ export enum ChartIntervalUnspecifiedStrategy {

export function useChartInterval({
unspecifiedStrategy = ChartIntervalUnspecifiedStrategy.USE_SMALLEST,
}: {unspecifiedStrategy?: ChartIntervalUnspecifiedStrategy} = {}): [
includeThirtyDayInterval = false,
}: {
/**
* Offer a 30 day interval for long time ranges. Never used as the default.
*/
includeThirtyDayInterval?: boolean;
unspecifiedStrategy?: ChartIntervalUnspecifiedStrategy;
} = {}): [
string,
(interval: string) => void,
intervalOptions: Array<{label: string; value: string}>,
Expand Down Expand Up @@ -64,6 +72,13 @@ export function useChartInterval({
options.push(ONE_DAY_OPTION);
}

if (
includeThirtyDayInterval &&
diffInMinutes >= MINIMUM_DURATION_FOR_THIRTY_DAY_INTERVAL
) {
options.push(THIRTY_DAY_OPTION);
}

return {
baseIntervalOptions: options,
defaultInterval: fallback,
Expand All @@ -76,7 +91,7 @@ export function useChartInterval({
// this would produce a single (incomplete) bucket.
timeRangeInHours: diffInMinutes / 60,
};
}, [datetime, unspecifiedStrategy]);
}, [datetime, unspecifiedStrategy, includeThirtyDayInterval]);

const [interval, intervalOptions] = useMemo((): [
string,
Expand Down Expand Up @@ -168,6 +183,9 @@ const MAXIMUM_INTERVAL = new GranularityLadder([
const ONE_DAY_OPTION = {value: '1d', label: t('1 day')};
const MINIMUM_DURATION_FOR_ONE_DAY_INTERVAL = TWO_WEEKS;

const THIRTY_DAY_OPTION = {value: '30d', label: t('30 days')};
const MINIMUM_DURATION_FOR_THIRTY_DAY_INTERVAL = SIXTY_DAYS;

/**
* Builds a human readable label for an arbitrary interval shorthand (e.g. `1d`),
* preserving the unit chosen rather than normalizing it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import {

/**
* Wrapper around `useChartInterval` that uses the `USE_SECOND_BIGGEST`
* strategy for dashboards. This keeps the dashboard detail page, widget
* builder preview, and filters bar in sync.
* strategy for dashboards and offers a 30 day interval on long ranges. This
* keeps the dashboard detail page, widget builder preview, and filters bar in
* sync.
*/
export function useDashboardChartInterval() {
return useChartInterval({
unspecifiedStrategy: ChartIntervalUnspecifiedStrategy.USE_SECOND_BIGGEST,
includeThirtyDayInterval: true,
});
}
Loading