GrafanaDS: Add support for annotation time regions (#65462)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
Co-authored-by: Adela Almasan <adela.almasan@grafana.com>
This commit is contained in:
Leon Sorokin
2023-04-21 15:25:50 -05:00
committed by GitHub
parent a2b97547a6
commit faad4b92ad
12 changed files with 1231 additions and 83 deletions

View File

@@ -1,40 +1,160 @@
import { dateTime, TimeRange } from '@grafana/data';
import { TimeRegionConfig } from 'app/core/utils/timeRegions';
import { calculateTimesWithin, TimeRegionConfig } from './timeRegions';
import { calculateTimesWithin } from './timeRegions';
// note: calculateTimesWithin always returns time ranges in UTC
describe('timeRegions', () => {
describe('day of week', () => {
it('4 sundays in january 2021', () => {
it('returns regions with 4 Mondays in March 2023', () => {
const cfg: TimeRegionConfig = {
fromDayOfWeek: 1,
from: '12:00',
};
const tr: TimeRange = {
from: dateTime('2021-01-00', 'YYYY-MM-dd'),
to: dateTime('2021-02-00', 'YYYY-MM-dd'),
from: dateTime('2023-03-01'),
to: dateTime('2023-03-31'),
raw: {
to: '',
from: '',
},
};
const regions = calculateTimesWithin(cfg, tr);
expect(regions).toMatchInlineSnapshot(`
[
{
"from": 1609779600000,
"to": 1609779600000,
"from": 1678060800000,
"to": 1678147199000,
},
{
"from": 1610384400000,
"to": 1610384400000,
"from": 1678665600000,
"to": 1678751999000,
},
{
"from": 1610989200000,
"to": 1610989200000,
"from": 1679270400000,
"to": 1679356799000,
},
{
"from": 1611594000000,
"to": 1611594000000,
"from": 1679875200000,
"to": 1679961599000,
},
]
`);
});
});
describe('day and time of week', () => {
it('returns regions with 4 Mondays at 20:00 in March 2023', () => {
const cfg: TimeRegionConfig = {
fromDayOfWeek: 1,
from: '20:00',
};
const tr: TimeRange = {
from: dateTime('2023-03-01'),
to: dateTime('2023-03-31'),
raw: {
to: '',
from: '',
},
};
const regions = calculateTimesWithin(cfg, tr);
expect(regions).toMatchInlineSnapshot(`
[
{
"from": 1678132800000,
"to": 1678132800000,
},
{
"from": 1678737600000,
"to": 1678737600000,
},
{
"from": 1679342400000,
"to": 1679342400000,
},
{
"from": 1679947200000,
"to": 1679947200000,
},
]
`);
});
});
describe('day of week range', () => {
it('returns regions with days range', () => {
const cfg: TimeRegionConfig = {
fromDayOfWeek: 1,
toDayOfWeek: 3,
};
const tr: TimeRange = {
from: dateTime('2023-03-01'),
to: dateTime('2023-03-31'),
raw: {
to: '',
from: '',
},
};
const regions = calculateTimesWithin(cfg, tr);
expect(regions).toMatchInlineSnapshot(`
[
{
"from": 1678060800000,
"to": 1678319999000,
},
{
"from": 1678665600000,
"to": 1678924799000,
},
{
"from": 1679270400000,
"to": 1679529599000,
},
{
"from": 1679875200000,
"to": 1680134399000,
},
]
`);
});
it('returns regions with days/times range', () => {
const cfg: TimeRegionConfig = {
fromDayOfWeek: 1,
from: '20:00',
toDayOfWeek: 2,
to: '10:00',
};
const tr: TimeRange = {
from: dateTime('2023-03-01'),
to: dateTime('2023-03-31'),
raw: {
to: '',
from: '',
},
};
const regions = calculateTimesWithin(cfg, tr);
expect(regions).toMatchInlineSnapshot(`
[
{
"from": 1678132800000,
"to": 1678183200000,
},
{
"from": 1678737600000,
"to": 1678788000000,
},
{
"from": 1679342400000,
"to": 1679392800000,
},
{
"from": 1679947200000,
"to": 1679997600000,
},
]
`);

View File

@@ -6,6 +6,8 @@ export interface TimeRegionConfig {
to?: string;
toDayOfWeek?: number; // 1-7
timezone?: string;
}
interface ParsedTime {
@@ -32,8 +34,8 @@ export function calculateTimesWithin(cfg: TimeRegionConfig, tRange: TimeRange):
}
const hRange = {
from: parseTimeRange(timeRegion.from),
to: parseTimeRange(timeRegion.to),
from: parseTimeOfDay(timeRegion.from),
to: parseTimeOfDay(timeRegion.to),
};
if (!timeRegion.fromDayOfWeek && timeRegion.toDayOfWeek) {
@@ -78,10 +80,11 @@ export function calculateTimesWithin(cfg: TimeRegionConfig, tRange: TimeRange):
const regions: AbsoluteTimeRange[] = [];
const fromStart = dateTime(tRange.from);
const fromStart = dateTime(tRange.from).utc();
fromStart.set('hour', 0);
fromStart.set('minute', 0);
fromStart.set('second', 0);
fromStart.set('millisecond', 0);
fromStart.add(hRange.from.h, 'hours');
fromStart.add(hRange.from.m, 'minutes');
fromStart.add(hRange.from.s, 'seconds');
@@ -95,7 +98,7 @@ export function calculateTimesWithin(cfg: TimeRegionConfig, tRange: TimeRange):
break;
}
const fromEnd = dateTime(fromStart);
const fromEnd = dateTime(fromStart).utc();
if (fromEnd.hour) {
if (hRange.from.h <= hRange.to.h) {
@@ -134,35 +137,36 @@ export function calculateTimesWithin(cfg: TimeRegionConfig, tRange: TimeRange):
return regions;
}
function parseTimeRange(str?: string): ParsedTime {
export function parseTimeOfDay(str?: string): ParsedTime {
const result: ParsedTime = {};
if (!str?.length) {
return result;
}
const timeRegex = /^([\d]+):?(\d{2})?/;
const match = timeRegex.exec(str);
if (!match) {
const match = str.split(':');
if (!match?.length) {
return result;
}
result.h = Math.min(23, Math.max(0, Number(match[0])));
if (match.length > 1) {
result.h = Number(match[1]);
result.m = 0;
if (match.length > 2 && match[2] !== undefined) {
result.m = Number(match[2]);
}
if (result.h > 23) {
result.h = 23;
}
if (result.m > 59) {
result.m = 59;
result.m = Math.min(60, Math.max(0, Number(match[1])));
if (match.length > 2) {
result.s = Math.min(60, Math.max(0, Number(match[2])));
}
}
return result;
}
export function formatTimeOfDayString(t?: ParsedTime): string {
if (!t || (t.h == null && t.m == null && t.s == null)) {
return '';
}
let str = String(t.h ?? 0).padStart(2, '0') + ':' + String(t.m ?? 0).padStart(2, '0');
if (t.s != null) {
str += String(t.s ?? 0).padStart(2, '0');
}
return str;
}