From b7910ade97746fdb42a7294131d28d087a7e0159 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 9 Jul 2024 13:18:24 +0200 Subject: [PATCH] Alerting: Allow future relative time (#89405) --- .../RelativeTimeRangePicker/utils.test.ts | 27 +++++++++ .../RelativeTimeRangePicker/utils.ts | 57 ++++++++++++------- 2 files changed, 64 insertions(+), 20 deletions(-) diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/utils.test.ts b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/utils.test.ts index fce263505e0..11868d66e04 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/utils.test.ts +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/utils.test.ts @@ -40,6 +40,17 @@ describe('utils', () => { display: 'now-100m to now-5m', }); }); + + it('should be able to work with future values', () => { + const relativeTimeRange = { from: 600, to: -600 }; + const timeOption = mapRelativeTimeRangeToOption(relativeTimeRange); + + expect(timeOption).toEqual({ + from: 'now-10m', + to: 'now+10m', + display: 'now-10m to now+10m', + }); + }); }); describe('mapOptionToRelativeTimeRange', () => { @@ -56,6 +67,13 @@ describe('utils', () => { expect(relativeTimeRange).toEqual({ from: 86400, to: 43200 }); }); + + it('should map future dates', () => { + const timeOption = { from: 'now-10m', to: 'now+10m', display: 'asdfasdf' }; + const relativeTimeRange = mapOptionToRelativeTimeRange(timeOption); + + expect(relativeTimeRange).toEqual({ from: 600, to: -600 }); + }); }); describe('isRelativeFormat', () => { @@ -83,6 +101,10 @@ describe('utils', () => { expect(isRelativeFormat('now-53w')).toBe(true); }); + it('should consider now+10m as a relative format', () => { + expect(isRelativeFormat('now+10m')).toBe(true); + }); + it('should consider 123123123 as a relative format', () => { expect(isRelativeFormat('123123123')).toBe(false); }); @@ -99,6 +121,11 @@ describe('utils', () => { expect(result.isValid).toBe(true); }); + it('should consider now+10m as a valid relative format', () => { + const result = isRangeValid('now+10m'); + expect(result.isValid).toBe(true); + }); + it('should consider now-90000000d as an invalid relative format', () => { const result = isRangeValid('now-90000000d'); expect(result.isValid).toBe(false); diff --git a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/utils.ts b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/utils.ts index cc4d321d886..d1b248d60f2 100644 --- a/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/utils.ts +++ b/packages/grafana-ui/src/components/DateTimePickers/RelativeTimeRangePicker/utils.ts @@ -1,6 +1,6 @@ import { RelativeTimeRange, TimeOption } from '@grafana/data'; -const regex = /^now$|^now\-(\d{1,10})([wdhms])$/; +const regex = /^now$|^now(\-|\+)(\d{1,10})([wdhms])$/; export const mapOptionToRelativeTimeRange = (option: TimeOption): RelativeTimeRange | undefined => { return { @@ -48,18 +48,19 @@ export const isRelativeFormat = (format: string): boolean => { const relativeToSeconds = (relative: string): number => { const match = regex.exec(relative); - if (!match || match.length !== 3) { + if (!match || match.length !== 4) { return 0; } - const [, value, unit] = match; + const [, sign, value, unit] = match; const parsed = parseInt(value, 10); if (isNaN(parsed)) { return 0; } - return parsed * units[unit]; + const seconds = parsed * units[unit]; + return sign === '+' ? seconds * -1 : seconds; }; const units: Record = { @@ -71,25 +72,41 @@ const units: Record = { }; const secondsToRelativeFormat = (seconds: number): string => { - if (seconds <= 0) { + if (seconds === 0) { return 'now'; } - if (seconds >= units.w && seconds % units.w === 0) { - return `now-${seconds / units.w}w`; + const absoluteSeconds = Math.abs(seconds); + if (seconds < 0) { + return `now+${formatDuration(absoluteSeconds)}`; } - if (seconds >= units.d && seconds % units.d === 0) { - return `now-${seconds / units.d}d`; - } - - if (seconds >= units.h && seconds % units.h === 0) { - return `now-${seconds / units.h}h`; - } - - if (seconds >= units.m && seconds % units.m === 0) { - return `now-${seconds / units.m}m`; - } - - return `now-${seconds}s`; + return `now-${formatDuration(absoluteSeconds)}`; }; + +/** + * Formats the given duration in seconds into a human-readable string representation. + * + * @param seconds - The duration in seconds. + * @returns The formatted duration string. + */ +function formatDuration(seconds: number): string { + const units = [ + { unit: 'w', value: 7 * 24 * 60 * 60 }, + { unit: 'd', value: 24 * 60 * 60 }, + { unit: 'h', value: 60 * 60 }, + { unit: 'm', value: 60 }, + { unit: 's', value: 1 }, + ]; + + for (const { unit, value } of units) { + if (seconds % value === 0) { + const quotient = seconds / value; + return `${quotient}${unit}`; + } + } + + // If no perfect division, use the least significant unit + const leastSignificant = units[units.length - 1]; + return `${seconds}${leastSignificant.unit}`; +}