-
Notifications
You must be signed in to change notification settings - Fork 298
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Display human readable time ranges in AG filters (#4288)
# What this PR does Display human readable time ranges in AG filters ## Which issue(s) this PR closes Closes #4272 ## Checklist - [ ] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes. --------- Co-authored-by: Michael Derynck <[email protected]>
- Loading branch information
Showing
10 changed files
with
140 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import moment from 'moment-timezone'; | ||
|
||
import { convertRelativeToAbsoluteDate, getValueForDateRangeFilterType } from './datetime'; | ||
|
||
const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ss'; | ||
|
||
describe('convertRelativeToAbsoluteDate', () => { | ||
it(`should convert relative date to absolute dates pair separated by an underscore`, () => { | ||
const result = convertRelativeToAbsoluteDate('now-24h_now'); | ||
|
||
const now = moment().utc(); | ||
const nowString = now.format(DATE_FORMAT); | ||
const dayBefore = now.subtract('1', 'day'); | ||
const dayBeforeString = dayBefore.format(DATE_FORMAT); | ||
|
||
expect(result).toBe(`${dayBeforeString}_${nowString}`); | ||
}); | ||
}); | ||
|
||
describe('getValueForDateRangeFilterType', () => { | ||
it(`should convert relative date range string to a suitable format for TimeRangeInput component`, () => { | ||
const input = 'now-2d_now'; | ||
const [from, to] = input.split('_'); | ||
const result = getValueForDateRangeFilterType(input); | ||
|
||
const now = moment(); | ||
const twoDaysBefore = now.subtract('2', 'day'); | ||
|
||
expect(result.from.diff(twoDaysBefore, 'seconds') < 1).toBe(true); | ||
expect(result.raw.from).toBe(from); | ||
expect(result.from.diff(twoDaysBefore, 'seconds') < 1).toBe(true); | ||
expect(result.raw.to).toBe(to); | ||
}); | ||
|
||
it(`should convert absolute date range string to a suitable format for TimeRangeInput component`, () => { | ||
const input = '2024-03-31T23:00:00_2024-04-15T22:59:59'; | ||
const [from, to] = input.split('_'); | ||
const result = getValueForDateRangeFilterType(input); | ||
|
||
const fromMoment = moment(from + 'Z'); | ||
const toMoment = moment(to + 'Z'); | ||
|
||
expect(result.from.diff(fromMoment, 'seconds') < 1).toBe(true); | ||
expect(result.raw.from).toBe(from); | ||
expect(result.from.diff(toMoment, 'seconds') < 1).toBe(true); | ||
expect(result.raw.to).toBe(to); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,68 +1,70 @@ | ||
import { TimeOption, TimeRange, rangeUtil } from '@grafana/data'; | ||
import { TimeZone } from '@grafana/schema'; | ||
import moment from 'moment-timezone'; | ||
|
||
// Valid mapping accepted by @grafana/ui and @grafana/data packages | ||
export const quickOptions = [ | ||
{ from: 'now-5m', to: 'now', display: 'Last 5 minutes' }, | ||
{ from: 'now-15m', to: 'now', display: 'Last 15 minutes' }, | ||
{ from: 'now-30m', to: 'now', display: 'Last 30 minutes' }, | ||
{ from: 'now-1h', to: 'now', display: 'Last 1 hour' }, | ||
{ from: 'now-3h', to: 'now', display: 'Last 3 hours' }, | ||
{ from: 'now-6h', to: 'now', display: 'Last 6 hours' }, | ||
{ from: 'now-12h', to: 'now', display: 'Last 12 hours' }, | ||
{ from: 'now-24h', to: 'now', display: 'Last 24 hours' }, | ||
{ from: 'now-2d', to: 'now', display: 'Last 2 days' }, | ||
{ from: 'now-7d', to: 'now', display: 'Last 7 days' }, | ||
{ from: 'now-30d', to: 'now', display: 'Last 30 days' }, | ||
{ from: 'now-90d', to: 'now', display: 'Last 90 days' }, | ||
{ from: 'now-6M', to: 'now', display: 'Last 6 months' }, | ||
{ from: 'now-1y', to: 'now', display: 'Last 1 year' }, | ||
{ from: 'now-2y', to: 'now', display: 'Last 2 years' }, | ||
{ from: 'now-5y', to: 'now', display: 'Last 5 years' }, | ||
{ from: 'now-1d/d', to: 'now-1d/d', display: 'Yesterday' }, | ||
{ from: 'now-2d/d', to: 'now-2d/d', display: 'Day before yesterday' }, | ||
{ from: 'now-7d/d', to: 'now-7d/d', display: 'This day last week' }, | ||
{ from: 'now-1w/w', to: 'now-1w/w', display: 'Previous week' }, | ||
{ from: 'now-1M/M', to: 'now-1M/M', display: 'Previous month' }, | ||
{ from: 'now-1Q/fQ', to: 'now-1Q/fQ', display: 'Previous fiscal quarter' }, | ||
{ from: 'now-1y/y', to: 'now-1y/y', display: 'Previous year' }, | ||
{ from: 'now-1y/fy', to: 'now-1y/fy', display: 'Previous fiscal year' }, | ||
{ from: 'now/d', to: 'now/d', display: 'Today' }, | ||
{ from: 'now/d', to: 'now', display: 'Today so far' }, | ||
{ from: 'now/w', to: 'now/w', display: 'This week' }, | ||
{ from: 'now/w', to: 'now', display: 'This week so far' }, | ||
{ from: 'now/M', to: 'now/M', display: 'This month' }, | ||
{ from: 'now/M', to: 'now', display: 'This month so far' }, | ||
{ from: 'now/y', to: 'now/y', display: 'This year' }, | ||
{ from: 'now/y', to: 'now', display: 'This year so far' }, | ||
{ from: 'now/fQ', to: 'now', display: 'This fiscal quarter so far' }, | ||
{ from: 'now/fQ', to: 'now/fQ', display: 'This fiscal quarter' }, | ||
{ from: 'now/fy', to: 'now', display: 'This fiscal year so far' }, | ||
{ from: 'now/fy', to: 'now/fy', display: 'This fiscal year' }, | ||
]; | ||
export const DATE_RANGE_DELIMITER = '_'; | ||
|
||
export const mapOptionToTimeRange = (option: TimeOption, timeZone?: TimeZone): TimeRange => { | ||
return rangeUtil.convertRawToRange({ from: option.from, to: option.to }, timeZone); | ||
}; | ||
|
||
export function convertRelativeToAbsoluteDate(dateRangeString: string) { | ||
const [from, to] = dateRangeString?.split('/') || []; | ||
const isValidMapping = quickOptions.find((option) => option.from === from && option.to === to); | ||
if (!dateRangeString) { | ||
return undefined; | ||
} | ||
|
||
if (isValidMapping) { | ||
const { from: startDate, to: endDate } = mapOptionToTimeRange({ from, to } as TimeOption); | ||
const [from, to] = dateRangeString?.split(DATE_RANGE_DELIMITER) || []; | ||
if (rangeUtil.isRelativeTimeRange({ from, to })) { | ||
const { from: startDate, to: endDate } = rangeUtil.convertRawToRange({ from, to }); | ||
|
||
// Return in the format used by on call filters | ||
return `${startDate.format('YYYY-MM-DDTHH:mm:ss')}/${endDate.format('YYYY-MM-DDTHH:mm:ss')}`; | ||
return `${startDate.utc().format('YYYY-MM-DDTHH:mm:ss')}${DATE_RANGE_DELIMITER}${endDate | ||
.utc() | ||
.format('YYYY-MM-DDTHH:mm:ss')}`; | ||
} | ||
return dateRangeString; | ||
} | ||
|
||
const quickOption = quickOptions.find((option) => option.display.toLowerCase() === dateRangeString.toLowerCase()); | ||
|
||
if (quickOption) { | ||
const { from: startDate, to: endDate } = mapOptionToTimeRange(quickOption as TimeOption); | ||
export const convertTimerangeToFilterValue = (timeRange: TimeRange) => { | ||
const isRelative = rangeUtil.isRelativeTimeRange(timeRange.raw); | ||
|
||
return `${startDate.format('YYYY-MM-DDTHH:mm:ss')}/${endDate.format('YYYY-MM-DDTHH:mm:ss')}`; | ||
if (isRelative) { | ||
return timeRange.raw.from + DATE_RANGE_DELIMITER + timeRange.raw.to; | ||
} else if (timeRange.from.isValid() && timeRange.to.isValid()) { | ||
return ( | ||
timeRange.from.utc().format('YYYY-MM-DDTHH:mm:ss') + | ||
DATE_RANGE_DELIMITER + | ||
timeRange.to.utc().format('YYYY-MM-DDTHH:mm:ss') | ||
); | ||
} | ||
return ''; | ||
}; | ||
|
||
return dateRangeString; | ||
} | ||
export const getValueForDateRangeFilterType = (rawInput: string) => { | ||
let value = { from: undefined, to: undefined, raw: { from: '', to: '' } }; | ||
if (rawInput) { | ||
const [fromString, toString] = rawInput.split(DATE_RANGE_DELIMITER); | ||
const isRelative = rangeUtil.isRelativeTimeRange({ from: fromString, to: toString }); | ||
|
||
const raw = { | ||
from: fromString, | ||
to: toString, | ||
}; | ||
|
||
if (isRelative) { | ||
const absolute = convertRelativeToAbsoluteDate(rawInput); | ||
const [absoluteFrom, absoluteTo] = absolute.split(DATE_RANGE_DELIMITER); | ||
value = { | ||
from: moment(absoluteFrom + 'Z'), | ||
to: moment(absoluteTo + 'Z'), | ||
raw, | ||
}; | ||
} else { | ||
value = { | ||
from: moment(fromString + 'Z'), | ||
to: moment(toString + 'Z'), | ||
raw, | ||
}; | ||
} | ||
} | ||
return value; | ||
}; |