mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
467 lines
14 KiB
TypeScript
467 lines
14 KiB
TypeScript
import { each, has } from 'lodash';
|
|
|
|
import { RawTimeRange, TimeRange, TimeZone, IntervalValues, RelativeTimeRange, TimeOption } from '../types/time';
|
|
|
|
import * as dateMath from './datemath';
|
|
import { timeZoneAbbrevation, dateTimeFormat, dateTimeFormatTimeAgo } from './formatter';
|
|
import { isDateTime, DateTime, dateTime } from './moment_wrapper';
|
|
import { dateTimeParse } from './parser';
|
|
|
|
const spans: { [key: string]: { display: string; section?: number } } = {
|
|
s: { display: 'second' },
|
|
m: { display: 'minute' },
|
|
h: { display: 'hour' },
|
|
d: { display: 'day' },
|
|
w: { display: 'week' },
|
|
M: { display: 'month' },
|
|
y: { display: 'year' },
|
|
};
|
|
|
|
const rangeOptions: 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' },
|
|
{ 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-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-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/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' },
|
|
];
|
|
|
|
const hiddenRangeOptions: 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' },
|
|
{ from: 'now', to: 'now+30m', display: 'Next 30 minutes' },
|
|
{ from: 'now', to: 'now+1h', display: 'Next hour' },
|
|
{ from: 'now', to: 'now+3h', display: 'Next 3 hours' },
|
|
{ from: 'now', to: 'now+6h', display: 'Next 6 hours' },
|
|
{ from: 'now', to: 'now+12h', display: 'Next 12 hours' },
|
|
{ from: 'now', to: 'now+24h', display: 'Next 24 hours' },
|
|
{ from: 'now', to: 'now+2d', display: 'Next 2 days' },
|
|
{ from: 'now', to: 'now+7d', display: 'Next 7 days' },
|
|
{ from: 'now', to: 'now+30d', display: 'Next 30 days' },
|
|
{ from: 'now', to: 'now+90d', display: 'Next 90 days' },
|
|
{ from: 'now', to: 'now+6M', display: 'Next 6 months' },
|
|
{ from: 'now', to: 'now+1y', display: 'Next year' },
|
|
{ from: 'now', to: 'now+2y', display: 'Next 2 years' },
|
|
{ from: 'now', to: 'now+5y', display: 'Next 5 years' },
|
|
];
|
|
|
|
const rangeIndex: Record<string, any> = {};
|
|
each(rangeOptions, (frame) => {
|
|
rangeIndex[frame.from + ' to ' + frame.to] = frame;
|
|
});
|
|
each(hiddenRangeOptions, (frame) => {
|
|
rangeIndex[frame.from + ' to ' + frame.to] = frame;
|
|
});
|
|
|
|
// handles expressions like
|
|
// 5m
|
|
// 5m to now/d
|
|
// now/d to now
|
|
// now/d
|
|
// if no to <expr> then to now is assumed
|
|
export function describeTextRange(expr: string) {
|
|
const isLast = expr.indexOf('+') !== 0;
|
|
if (expr.indexOf('now') === -1) {
|
|
expr = (isLast ? 'now-' : 'now') + expr;
|
|
}
|
|
|
|
let opt = rangeIndex[expr + ' to now'];
|
|
if (opt) {
|
|
return opt;
|
|
}
|
|
|
|
if (isLast) {
|
|
opt = { from: expr, to: 'now' };
|
|
} else {
|
|
opt = { from: 'now', to: expr };
|
|
}
|
|
|
|
const parts = /^now([-+])(\d+)(\w)/.exec(expr);
|
|
if (parts) {
|
|
const unit = parts[3];
|
|
const amount = parseInt(parts[2], 10);
|
|
const span = spans[unit];
|
|
if (span) {
|
|
opt.display = isLast ? 'Last ' : 'Next ';
|
|
opt.display += amount + ' ' + span.display;
|
|
opt.section = span.section;
|
|
if (amount > 1) {
|
|
opt.display += 's';
|
|
}
|
|
}
|
|
} else {
|
|
opt.display = opt.from + ' to ' + opt.to;
|
|
opt.invalid = true;
|
|
}
|
|
|
|
return opt;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
* @alpha
|
|
*/
|
|
export function describeTimeRange(range: RawTimeRange, timeZone?: TimeZone): string {
|
|
const option = rangeIndex[range.from.toString() + ' to ' + range.to.toString()];
|
|
|
|
if (option) {
|
|
return option.display;
|
|
}
|
|
|
|
const options = { timeZone };
|
|
|
|
if (isDateTime(range.from) && isDateTime(range.to)) {
|
|
return dateTimeFormat(range.from, options) + ' to ' + dateTimeFormat(range.to, options);
|
|
}
|
|
|
|
if (isDateTime(range.from)) {
|
|
const parsed = dateMath.parse(range.to, true, 'utc');
|
|
return parsed ? dateTimeFormat(range.from, options) + ' to ' + dateTimeFormatTimeAgo(parsed, options) : '';
|
|
}
|
|
|
|
if (isDateTime(range.to)) {
|
|
const parsed = dateMath.parse(range.from, false, 'utc');
|
|
return parsed ? dateTimeFormatTimeAgo(parsed, options) + ' to ' + dateTimeFormat(range.to, options) : '';
|
|
}
|
|
|
|
if (range.to.toString() === 'now') {
|
|
const res = describeTextRange(range.from);
|
|
return res.display;
|
|
}
|
|
|
|
return range.from.toString() + ' to ' + range.to.toString();
|
|
}
|
|
|
|
export const isValidTimeSpan = (value: string) => {
|
|
if (value.indexOf('$') === 0 || value.indexOf('+$') === 0) {
|
|
return true;
|
|
}
|
|
|
|
const info = describeTextRange(value);
|
|
return info.invalid !== true;
|
|
};
|
|
|
|
export const describeTimeRangeAbbreviation = (range: TimeRange, timeZone?: TimeZone) => {
|
|
if (isDateTime(range.from)) {
|
|
return timeZoneAbbrevation(range.from, { timeZone });
|
|
}
|
|
const parsed = dateMath.parse(range.from, true);
|
|
return parsed ? timeZoneAbbrevation(parsed, { timeZone }) : '';
|
|
};
|
|
|
|
export const convertRawToRange = (raw: RawTimeRange, timeZone?: TimeZone, fiscalYearStartMonth?: number): TimeRange => {
|
|
const from = dateTimeParse(raw.from, { roundUp: false, timeZone, fiscalYearStartMonth });
|
|
const to = dateTimeParse(raw.to, { roundUp: true, timeZone, fiscalYearStartMonth });
|
|
|
|
if (dateMath.isMathString(raw.from) || dateMath.isMathString(raw.to)) {
|
|
return { from, to, raw };
|
|
}
|
|
|
|
return { from, to, raw: { from, to } };
|
|
};
|
|
|
|
function isRelativeTime(v: DateTime | string) {
|
|
if (typeof v === 'string') {
|
|
return v.indexOf('now') >= 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function isFiscal(timeRange: TimeRange) {
|
|
if (typeof timeRange.raw.from === 'string' && timeRange.raw.from.indexOf('f') > 0) {
|
|
return true;
|
|
} else if (typeof timeRange.raw.to === 'string' && timeRange.raw.to.indexOf('f') > 0) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
export function isRelativeTimeRange(raw: RawTimeRange): boolean {
|
|
return isRelativeTime(raw.from) || isRelativeTime(raw.to);
|
|
}
|
|
|
|
export function secondsToHms(seconds: number): string {
|
|
const numYears = Math.floor(seconds / 31536000);
|
|
if (numYears) {
|
|
return numYears + 'y';
|
|
}
|
|
const numDays = Math.floor((seconds % 31536000) / 86400);
|
|
if (numDays) {
|
|
return numDays + 'd';
|
|
}
|
|
const numHours = Math.floor(((seconds % 31536000) % 86400) / 3600);
|
|
if (numHours) {
|
|
return numHours + 'h';
|
|
}
|
|
const numMinutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60);
|
|
if (numMinutes) {
|
|
return numMinutes + 'm';
|
|
}
|
|
const numSeconds = Math.floor((((seconds % 31536000) % 86400) % 3600) % 60);
|
|
if (numSeconds) {
|
|
return numSeconds + 's';
|
|
}
|
|
const numMilliseconds = Math.floor(seconds * 1000.0);
|
|
if (numMilliseconds) {
|
|
return numMilliseconds + 'ms';
|
|
}
|
|
|
|
return 'less than a millisecond'; //'just now' //or other string you like;
|
|
}
|
|
|
|
// Format timeSpan (in sec) to string used in log's meta info
|
|
export function msRangeToTimeString(rangeMs: number): string {
|
|
const rangeSec = Number((rangeMs / 1000).toFixed());
|
|
|
|
const h = Math.floor(rangeSec / 60 / 60);
|
|
const m = Math.floor(rangeSec / 60) - h * 60;
|
|
const s = Number((rangeSec % 60).toFixed());
|
|
let formattedH = h ? h + 'h' : '';
|
|
let formattedM = m ? m + 'min' : '';
|
|
let formattedS = s ? s + 'sec' : '';
|
|
|
|
formattedH && formattedM ? (formattedH = formattedH + ' ') : (formattedH = formattedH);
|
|
(formattedM || formattedH) && formattedS ? (formattedM = formattedM + ' ') : (formattedM = formattedM);
|
|
|
|
return formattedH + formattedM + formattedS || 'less than 1sec';
|
|
}
|
|
|
|
export function calculateInterval(range: TimeRange, resolution: number, lowLimitInterval?: string): IntervalValues {
|
|
let lowLimitMs = 1; // 1 millisecond default low limit
|
|
if (lowLimitInterval) {
|
|
lowLimitMs = intervalToMs(lowLimitInterval);
|
|
}
|
|
|
|
let intervalMs = roundInterval((range.to.valueOf() - range.from.valueOf()) / resolution);
|
|
if (lowLimitMs > intervalMs) {
|
|
intervalMs = lowLimitMs;
|
|
}
|
|
return {
|
|
intervalMs: intervalMs,
|
|
interval: secondsToHms(intervalMs / 1000),
|
|
};
|
|
}
|
|
|
|
const interval_regex = /(-?\d+(?:\.\d+)?)(ms|[Mwdhmsy])/;
|
|
// histogram & trends
|
|
const intervals_in_seconds = {
|
|
y: 31536000,
|
|
M: 2592000,
|
|
w: 604800,
|
|
d: 86400,
|
|
h: 3600,
|
|
m: 60,
|
|
s: 1,
|
|
ms: 0.001,
|
|
};
|
|
|
|
export function describeInterval(str: string) {
|
|
// Default to seconds if no unit is provided
|
|
if (Number(str)) {
|
|
return {
|
|
sec: intervals_in_seconds.s,
|
|
type: 's',
|
|
count: parseInt(str, 10),
|
|
};
|
|
}
|
|
|
|
const matches = str.match(interval_regex);
|
|
if (!matches || !has(intervals_in_seconds, matches[2])) {
|
|
throw new Error(
|
|
`Invalid interval string, has to be either unit-less or end with one of the following units: "${Object.keys(
|
|
intervals_in_seconds
|
|
).join(', ')}"`
|
|
);
|
|
}
|
|
return {
|
|
sec: (intervals_in_seconds as any)[matches[2]] as number,
|
|
type: matches[2],
|
|
count: parseInt(matches[1], 10),
|
|
};
|
|
}
|
|
|
|
export function intervalToSeconds(str: string): number {
|
|
const info = describeInterval(str);
|
|
return info.sec * info.count;
|
|
}
|
|
|
|
export function intervalToMs(str: string): number {
|
|
const info = describeInterval(str);
|
|
return info.sec * 1000 * info.count;
|
|
}
|
|
|
|
export function roundInterval(interval: number) {
|
|
switch (true) {
|
|
// 0.01s
|
|
case interval < 10:
|
|
return 1; // 0.001s
|
|
// 0.015s
|
|
case interval < 15:
|
|
return 10; // 0.01s
|
|
// 0.035s
|
|
case interval < 35:
|
|
return 20; // 0.02s
|
|
// 0.075s
|
|
case interval < 75:
|
|
return 50; // 0.05s
|
|
// 0.15s
|
|
case interval < 150:
|
|
return 100; // 0.1s
|
|
// 0.35s
|
|
case interval < 350:
|
|
return 200; // 0.2s
|
|
// 0.75s
|
|
case interval < 750:
|
|
return 500; // 0.5s
|
|
// 1.5s
|
|
case interval < 1500:
|
|
return 1000; // 1s
|
|
// 3.5s
|
|
case interval < 3500:
|
|
return 2000; // 2s
|
|
// 7.5s
|
|
case interval < 7500:
|
|
return 5000; // 5s
|
|
// 12.5s
|
|
case interval < 12500:
|
|
return 10000; // 10s
|
|
// 17.5s
|
|
case interval < 17500:
|
|
return 15000; // 15s
|
|
// 25s
|
|
case interval < 25000:
|
|
return 20000; // 20s
|
|
// 45s
|
|
case interval < 45000:
|
|
return 30000; // 30s
|
|
// 1.5m
|
|
case interval < 90000:
|
|
return 60000; // 1m
|
|
// 3.5m
|
|
case interval < 210000:
|
|
return 120000; // 2m
|
|
// 7.5m
|
|
case interval < 450000:
|
|
return 300000; // 5m
|
|
// 12.5m
|
|
case interval < 750000:
|
|
return 600000; // 10m
|
|
// 17.5m
|
|
case interval < 1050000:
|
|
return 900000; // 15m
|
|
// 25m
|
|
case interval < 1500000:
|
|
return 1200000; // 20m
|
|
// 45m
|
|
case interval < 2700000:
|
|
return 1800000; // 30m
|
|
// 1.5h
|
|
case interval < 5400000:
|
|
return 3600000; // 1h
|
|
// 2.5h
|
|
case interval < 9000000:
|
|
return 7200000; // 2h
|
|
// 4.5h
|
|
case interval < 16200000:
|
|
return 10800000; // 3h
|
|
// 9h
|
|
case interval < 32400000:
|
|
return 21600000; // 6h
|
|
// 1d
|
|
case interval < 86400000:
|
|
return 43200000; // 12h
|
|
// 1w
|
|
case interval < 604800000:
|
|
return 86400000; // 1d
|
|
// 3w
|
|
case interval < 1814400000:
|
|
return 604800000; // 1w
|
|
// 6w
|
|
case interval < 3628800000:
|
|
return 2592000000; // 30d
|
|
default:
|
|
return 31536000000; // 1y
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts a TimeRange to a RelativeTimeRange that can be used in
|
|
* e.g. alerting queries/rules.
|
|
*
|
|
* @internal
|
|
*/
|
|
export function timeRangeToRelative(timeRange: TimeRange, now: DateTime = dateTime()): RelativeTimeRange {
|
|
const from = now.unix() - timeRange.from.unix();
|
|
const to = now.unix() - timeRange.to.unix();
|
|
|
|
return {
|
|
from,
|
|
to,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Converts a RelativeTimeRange to a TimeRange
|
|
*
|
|
* @internal
|
|
*/
|
|
export function relativeToTimeRange(relativeTimeRange: RelativeTimeRange, now: DateTime = dateTime()): TimeRange {
|
|
const from = dateTime(now).subtract(relativeTimeRange.from, 's');
|
|
const to = relativeTimeRange.to === 0 ? dateTime(now) : dateTime(now).subtract(relativeTimeRange.to, 's');
|
|
|
|
return {
|
|
from,
|
|
to,
|
|
raw: { from, to },
|
|
};
|
|
}
|