mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Allow custom quick time ranges specified in dashboard model (#93724)
* TimeRangePicker: allow to customize quick ranges per dashboard * TimeRangePicker: show selected custom time range using its name * rangeutil: add tests for describeTextRange + quickRanges * Fix up tests, and add an extra case for hidden time ranges * Don't construct an object to find options, add findRangeInOptions util * fix type errors detected by TypeScript --------- Co-authored-by: joshhunt <josh@trtr.co>
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
import { each } from 'lodash';
|
||||
|
||||
import { RawTimeRange, TimeRange, TimeZone, IntervalValues, RelativeTimeRange, TimeOption } from '../types/time';
|
||||
|
||||
import * as dateMath from './datemath';
|
||||
@@ -17,7 +15,7 @@ const spans: { [key: string]: { display: string; section?: number } } = {
|
||||
y: { display: 'year' },
|
||||
};
|
||||
|
||||
const rangeOptions: TimeOption[] = [
|
||||
const BASE_RANGE_OPTIONS: TimeOption[] = [
|
||||
{ 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' },
|
||||
@@ -66,7 +64,7 @@ const rangeOptions: TimeOption[] = [
|
||||
{ from: 'now/fy', to: 'now/fy', display: 'This fiscal year' },
|
||||
];
|
||||
|
||||
const hiddenRangeOptions: TimeOption[] = [
|
||||
const HIDDEN_RANGE_OPTIONS: TimeOption[] = [
|
||||
{ from: 'now', to: 'now+1m', display: 'Next minute' },
|
||||
{ from: 'now', to: 'now+5m', display: 'Next 5 minutes' },
|
||||
{ from: 'now', to: 'now+15m', display: 'Next 15 minutes' },
|
||||
@@ -86,13 +84,11 @@ const hiddenRangeOptions: TimeOption[] = [
|
||||
{ from: 'now', to: 'now+5y', display: 'Next 5 years' },
|
||||
];
|
||||
|
||||
const rangeIndex: Record<string, TimeOption> = {};
|
||||
each(rangeOptions, (frame) => {
|
||||
rangeIndex[frame.from + ' to ' + frame.to] = frame;
|
||||
});
|
||||
each(hiddenRangeOptions, (frame) => {
|
||||
rangeIndex[frame.from + ' to ' + frame.to] = frame;
|
||||
});
|
||||
const STANDARD_RANGE_OPTIONS = BASE_RANGE_OPTIONS.concat(HIDDEN_RANGE_OPTIONS);
|
||||
|
||||
function findRangeInOptions(range: RawTimeRange, options: TimeOption[]) {
|
||||
return options.find((option) => option.from === range.from && option.to === range.to);
|
||||
}
|
||||
|
||||
// handles expressions like
|
||||
// 5m
|
||||
@@ -106,7 +102,7 @@ export function describeTextRange(expr: string): TimeOption {
|
||||
expr = (isLast ? 'now-' : 'now') + expr;
|
||||
}
|
||||
|
||||
let opt = rangeIndex[expr + ' to now'];
|
||||
let opt = findRangeInOptions({ from: expr, to: 'now' }, STANDARD_RANGE_OPTIONS);
|
||||
if (opt) {
|
||||
return opt;
|
||||
}
|
||||
@@ -141,17 +137,15 @@ export function describeTextRange(expr: string): TimeOption {
|
||||
/**
|
||||
* Use this function to get a properly formatted string representation of a {@link @grafana/data:RawTimeRange | range}.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* // Prints "2":
|
||||
* console.log(add(1,1));
|
||||
* ```
|
||||
* @category TimeUtils
|
||||
* @param range - a time range (usually specified by the TimePicker)
|
||||
* @param timeZone - optional time zone.
|
||||
* @param quickRanges - optional dashboard's custom quick ranges to pick range names from.
|
||||
* @alpha
|
||||
*/
|
||||
export function describeTimeRange(range: RawTimeRange, timeZone?: TimeZone): string {
|
||||
const option = rangeIndex[range.from.toString() + ' to ' + range.to.toString()];
|
||||
export function describeTimeRange(range: RawTimeRange, timeZone?: TimeZone, quickRanges?: TimeOption[]): string {
|
||||
const rangeOptions = quickRanges ? quickRanges.concat(STANDARD_RANGE_OPTIONS) : STANDARD_RANGE_OPTIONS;
|
||||
const option = findRangeInOptions(range, rangeOptions);
|
||||
|
||||
if (option) {
|
||||
return option.display;
|
||||
|
||||
@@ -73,6 +73,7 @@ export type {
|
||||
VariableModel,
|
||||
DataSourceRef,
|
||||
DataTransformerConfig,
|
||||
TimeOption,
|
||||
TimePickerConfig,
|
||||
Panel,
|
||||
FieldConfigSource,
|
||||
|
||||
@@ -651,6 +651,15 @@ export interface DataTransformerConfig {
|
||||
topic?: ('series' | 'annotations' | 'alertStates'); // replaced with common.DataTopic
|
||||
}
|
||||
|
||||
/**
|
||||
* Counterpart for TypeScript's TimeOption type.
|
||||
*/
|
||||
export interface TimeOption {
|
||||
display: string;
|
||||
from: string;
|
||||
to: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time picker configuration
|
||||
* It defines the default config for the time picker and the refresh picker for the specific dashboard.
|
||||
@@ -664,6 +673,10 @@ export interface TimePickerConfig {
|
||||
* Override the now time by entering a time delay. Use this option to accommodate known delays in data aggregation to avoid null values.
|
||||
*/
|
||||
nowDelay?: string;
|
||||
/**
|
||||
* Quick ranges for time picker.
|
||||
*/
|
||||
quick_ranges?: Array<TimeOption>;
|
||||
/**
|
||||
* Interval options available in the refresh picker dropdown.
|
||||
*/
|
||||
@@ -676,6 +689,7 @@ export interface TimePickerConfig {
|
||||
|
||||
export const defaultTimePickerConfig: Partial<TimePickerConfig> = {
|
||||
hidden: false,
|
||||
quick_ranges: [],
|
||||
refresh_intervals: ['5s', '10s', '30s', '1m', '5m', '15m', '30m', '1h', '2h', '1d'],
|
||||
time_options: ['5m', '15m', '1h', '6h', '12h', '24h', '2d', '7d', '30d'],
|
||||
};
|
||||
|
||||
@@ -62,6 +62,8 @@ export interface DataTransformerConfig<TOptions = any> extends raw.DataTransform
|
||||
topic?: DataTopic;
|
||||
}
|
||||
|
||||
export interface TimeOption extends raw.TimeOption {}
|
||||
|
||||
export interface TimePickerConfig extends raw.TimePickerConfig {}
|
||||
|
||||
export const defaultDashboard = raw.defaultDashboard as Dashboard;
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
GrafanaTheme2,
|
||||
dateTimeFormat,
|
||||
timeZoneFormatUserFriendly,
|
||||
TimeOption,
|
||||
TimeRange,
|
||||
TimeZone,
|
||||
dateMath,
|
||||
@@ -55,6 +56,7 @@ export interface TimeRangePickerProps {
|
||||
onZoom: () => void;
|
||||
onError?: (error?: string) => void;
|
||||
history?: TimeRange[];
|
||||
quickRanges?: TimeOption[];
|
||||
hideQuickRanges?: boolean;
|
||||
widthOverride?: number;
|
||||
isOnCanvas?: boolean;
|
||||
@@ -81,6 +83,7 @@ export function TimeRangePicker(props: TimeRangePickerProps) {
|
||||
history,
|
||||
onChangeTimeZone,
|
||||
onChangeFiscalYearStartMonth,
|
||||
quickRanges,
|
||||
hideQuickRanges,
|
||||
widthOverride,
|
||||
isOnCanvas,
|
||||
@@ -141,7 +144,7 @@ export function TimeRangePicker(props: TimeRangePickerProps) {
|
||||
const isFromAfterTo = value?.to?.isBefore(value.from);
|
||||
const timePickerIcon = isFromAfterTo ? 'exclamation-triangle' : 'clock-nine';
|
||||
|
||||
const currentTimeRange = formattedRange(value, timeZone);
|
||||
const currentTimeRange = formattedRange(value, timeZone, quickRanges);
|
||||
|
||||
return (
|
||||
<ButtonGroup className={styles.container}>
|
||||
@@ -187,7 +190,7 @@ export function TimeRangePicker(props: TimeRangePickerProps) {
|
||||
fiscalYearStartMonth={fiscalYearStartMonth}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
quickOptions={quickOptions}
|
||||
quickOptions={quickRanges || quickOptions}
|
||||
history={history}
|
||||
showHistory
|
||||
widthOverride={widthOverride}
|
||||
@@ -255,9 +258,9 @@ export const TimePickerTooltip = ({ timeRange, timeZone }: { timeRange: TimeRang
|
||||
);
|
||||
};
|
||||
|
||||
type LabelProps = Pick<TimeRangePickerProps, 'hideText' | 'value' | 'timeZone'>;
|
||||
type LabelProps = Pick<TimeRangePickerProps, 'hideText' | 'value' | 'timeZone' | 'quickRanges'>;
|
||||
|
||||
export const TimePickerButtonLabel = memo<LabelProps>(({ hideText, value, timeZone }) => {
|
||||
export const TimePickerButtonLabel = memo<LabelProps>(({ hideText, value, timeZone, quickRanges }) => {
|
||||
const styles = useStyles2(getLabelStyles);
|
||||
|
||||
if (hideText) {
|
||||
@@ -266,7 +269,7 @@ export const TimePickerButtonLabel = memo<LabelProps>(({ hideText, value, timeZo
|
||||
|
||||
return (
|
||||
<span className={styles.container} aria-live="polite" aria-atomic="true">
|
||||
<span>{formattedRange(value, timeZone)}</span>
|
||||
<span>{formattedRange(value, timeZone, quickRanges)}</span>
|
||||
<span className={styles.utc}>{rangeUtil.describeTimeRangeAbbreviation(value, timeZone)}</span>
|
||||
</span>
|
||||
);
|
||||
@@ -274,12 +277,12 @@ export const TimePickerButtonLabel = memo<LabelProps>(({ hideText, value, timeZo
|
||||
|
||||
TimePickerButtonLabel.displayName = 'TimePickerButtonLabel';
|
||||
|
||||
const formattedRange = (value: TimeRange, timeZone?: TimeZone) => {
|
||||
const formattedRange = (value: TimeRange, timeZone?: TimeZone, quickRanges?: TimeOption[]) => {
|
||||
const adjustedTimeRange = {
|
||||
to: dateMath.isMathString(value.raw.to) ? value.raw.to : value.to,
|
||||
from: dateMath.isMathString(value.raw.from) ? value.raw.from : value.from,
|
||||
};
|
||||
return rangeUtil.describeTimeRange(adjustedTimeRange, timeZone);
|
||||
return rangeUtil.describeTimeRange(adjustedTimeRange, timeZone, quickRanges);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => {
|
||||
|
||||
Reference in New Issue
Block a user