From ededf1dd6f39e43a6b16e57655dbdadf85fdde16 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 12 Jul 2022 12:09:49 -0700 Subject: [PATCH] Graph: move time region calculation to a utility function (#51413) --- .betterer.results | 8 +- public/app/core/utils/timeRegions.test.ts | 43 ++++ public/app/core/utils/timeRegions.ts | 168 ++++++++++++++ .../panel/graph/time_region_manager.ts | 211 ++++-------------- 4 files changed, 255 insertions(+), 175 deletions(-) create mode 100644 public/app/core/utils/timeRegions.test.ts create mode 100644 public/app/core/utils/timeRegions.ts diff --git a/.betterer.results b/.betterer.results index 9c625a489e0..bc6732cb55f 100644 --- a/.betterer.results +++ b/.betterer.results @@ -9414,13 +9414,7 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"], [0, 0, 0, "Unexpected any. Specify a different type.", "5"], - [0, 0, 0, "Unexpected any. Specify a different type.", "6"], - [0, 0, 0, "Unexpected any. Specify a different type.", "7"], - [0, 0, 0, "Unexpected any. Specify a different type.", "8"], - [0, 0, 0, "Unexpected any. Specify a different type.", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "Unexpected any. Specify a different type.", "11"], - [0, 0, 0, "Unexpected any. Specify a different type.", "12"] + [0, 0, 0, "Unexpected any. Specify a different type.", "6"] ], "public/app/plugins/panel/graph/time_regions_form.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], diff --git a/public/app/core/utils/timeRegions.test.ts b/public/app/core/utils/timeRegions.test.ts new file mode 100644 index 00000000000..16a20e47d24 --- /dev/null +++ b/public/app/core/utils/timeRegions.test.ts @@ -0,0 +1,43 @@ +import { dateTime, TimeRange } from '@grafana/data'; + +import { calculateTimesWithin, TimeRegionConfig } from './timeRegions'; + +describe('timeRegions', () => { + describe('day of week', () => { + it('4 sundays in january 2021', () => { + 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'), + raw: { + to: '', + from: '', + }, + }; + const regions = calculateTimesWithin(cfg, tr); + expect(regions).toMatchInlineSnapshot(` + Array [ + Object { + "from": 1609779600000, + "to": 1609779600000, + }, + Object { + "from": 1610384400000, + "to": 1610384400000, + }, + Object { + "from": 1610989200000, + "to": 1610989200000, + }, + Object { + "from": 1611594000000, + "to": 1611594000000, + }, + ] + `); + }); + }); +}); diff --git a/public/app/core/utils/timeRegions.ts b/public/app/core/utils/timeRegions.ts new file mode 100644 index 00000000000..329453cf014 --- /dev/null +++ b/public/app/core/utils/timeRegions.ts @@ -0,0 +1,168 @@ +import { AbsoluteTimeRange, dateTime, TimeRange } from '@grafana/data'; + +export interface TimeRegionConfig { + from?: string; + fromDayOfWeek?: number; // 1-7 + + to?: string; + toDayOfWeek?: number; // 1-7 +} + +interface ParsedTime { + dayOfWeek?: number; // 1-7 + h?: number; // 0-23 + m?: number; // 0-59 + s?: number; // 0-59 +} + +export function calculateTimesWithin(cfg: TimeRegionConfig, tRange: TimeRange): AbsoluteTimeRange[] { + if (!(cfg.fromDayOfWeek || cfg.from) && !(cfg.toDayOfWeek || cfg.to)) { + return []; + } + + // So we can mutate + const timeRegion = { ...cfg }; + + if (timeRegion.from && !timeRegion.to) { + timeRegion.to = timeRegion.from; + } + + if (!timeRegion.from && timeRegion.to) { + timeRegion.from = timeRegion.to; + } + + const hRange = { + from: parseTimeRange(timeRegion.from), + to: parseTimeRange(timeRegion.to), + }; + + if (!timeRegion.fromDayOfWeek && timeRegion.toDayOfWeek) { + timeRegion.fromDayOfWeek = timeRegion.toDayOfWeek; + } + + if (!timeRegion.toDayOfWeek && timeRegion.fromDayOfWeek) { + timeRegion.toDayOfWeek = timeRegion.fromDayOfWeek; + } + + if (timeRegion.fromDayOfWeek) { + hRange.from.dayOfWeek = Number(timeRegion.fromDayOfWeek); + } + + if (timeRegion.toDayOfWeek) { + hRange.to.dayOfWeek = Number(timeRegion.toDayOfWeek); + } + + if (hRange.from.dayOfWeek && hRange.from.h == null && hRange.from.m == null) { + hRange.from.h = 0; + hRange.from.m = 0; + hRange.from.s = 0; + } + + if (hRange.to.dayOfWeek && hRange.to.h == null && hRange.to.m == null) { + hRange.to.h = 23; + hRange.to.m = 59; + hRange.to.s = 59; + } + + if (!hRange.from || !hRange.to) { + return []; + } + + if (hRange.from.h == null) { + hRange.from.h = 0; + } + + if (hRange.to.h == null) { + hRange.to.h = 23; + } + + const regions: AbsoluteTimeRange[] = []; + + const fromStart = dateTime(tRange.from); + fromStart.set('hour', 0); + fromStart.set('minute', 0); + fromStart.set('second', 0); + fromStart.add(hRange.from.h, 'hours'); + fromStart.add(hRange.from.m, 'minutes'); + fromStart.add(hRange.from.s, 'seconds'); + + while (fromStart.unix() <= tRange.to.unix()) { + while (hRange.from.dayOfWeek && hRange.from.dayOfWeek !== fromStart.isoWeekday()) { + fromStart.add(24, 'hours'); + } + + if (fromStart.unix() > tRange.to.unix()) { + break; + } + + const fromEnd = dateTime(fromStart); + + if (fromEnd.hour) { + if (hRange.from.h <= hRange.to.h) { + fromEnd.add(hRange.to.h - hRange.from.h, 'hours'); + } else if (hRange.from.h > hRange.to.h) { + while (fromEnd.hour() !== hRange.to.h) { + fromEnd.add(1, 'hours'); + } + } else { + fromEnd.add(24 - hRange.from.h, 'hours'); + + while (fromEnd.hour() !== hRange.to.h) { + fromEnd.add(1, 'hours'); + } + } + } + + fromEnd.set('minute', hRange.to.m ?? 0); + fromEnd.set('second', hRange.to.s ?? 0); + + while (hRange.to.dayOfWeek && hRange.to.dayOfWeek !== fromEnd.isoWeekday()) { + fromEnd.add(24, 'hours'); + } + + const outsideRange = + (fromStart.unix() < tRange.from.unix() && fromEnd.unix() < tRange.from.unix()) || + (fromStart.unix() > tRange.to.unix() && fromEnd.unix() > tRange.to.unix()); + + if (!outsideRange) { + regions.push({ from: fromStart.valueOf(), to: fromEnd.valueOf() }); + } + + fromStart.add(24, 'hours'); + } + + return regions; +} + +function parseTimeRange(str?: string): ParsedTime { + const result: ParsedTime = {}; + if (!str?.length) { + return result; + } + + const timeRegex = /^([\d]+):?(\d{2})?/; + const match = timeRegex.exec(str); + + if (!match) { + return result; + } + + 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; + } + } + + return result; +} diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts index 8c5272e52cd..04744dea9ec 100644 --- a/public/app/plugins/panel/graph/time_region_manager.ts +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -1,8 +1,9 @@ import 'vendor/flot/jquery.flot'; import { map } from 'lodash'; -import { dateTime, DateTime, AbsoluteTimeRange, GrafanaTheme } from '@grafana/data'; +import { dateTime, GrafanaTheme, TimeRange } from '@grafana/data'; import { config } from 'app/core/config'; +import { calculateTimesWithin, TimeRegionConfig } from 'app/core/utils/timeRegions'; type TimeRegionColorDefinition = { fill: string | null; @@ -68,9 +69,19 @@ function getColor(timeRegion: any, theme: GrafanaTheme): TimeRegionColorDefiniti }; } +interface GraphTimeRegionConfig extends TimeRegionConfig { + colorMode: string; + + fill: boolean; + fillColor: string; + + line: boolean; + lineColor: string; +} + export class TimeRegionManager { plot: any; - timeRegions: any; + timeRegions?: TimeRegionConfig[]; constructor(private panelCtrl: any) {} @@ -80,183 +91,47 @@ export class TimeRegionManager { } addFlotOptions(options: any, panel: any) { - if (!panel.timeRegions || panel.timeRegions.length === 0) { + if (!panel.timeRegions?.length) { return; } - const tRange = { + // The panel range + const tRange: TimeRange = { from: dateTime(this.panelCtrl.range.from).utc(), to: dateTime(this.panelCtrl.range.to).utc(), + raw: { + from: '', + to: '', + }, }; - let i: number, - hRange: { from: any; to: any }, - timeRegion: any, - regions: AbsoluteTimeRange[], - fromStart: DateTime, - fromEnd: DateTime, - timeRegionColor: TimeRegionColorDefinition; - - const timeRegionsCopy = panel.timeRegions.map((a: any) => ({ ...a })); - - for (i = 0; i < timeRegionsCopy.length; i++) { - timeRegion = timeRegionsCopy[i]; - - if (!(timeRegion.fromDayOfWeek || timeRegion.from) && !(timeRegion.toDayOfWeek || timeRegion.to)) { - continue; - } - - if (timeRegion.from && !timeRegion.to) { - timeRegion.to = timeRegion.from; - } - - if (!timeRegion.from && timeRegion.to) { - timeRegion.from = timeRegion.to; - } - - hRange = { - from: this.parseTimeRange(timeRegion.from), - to: this.parseTimeRange(timeRegion.to), - }; - - if (!timeRegion.fromDayOfWeek && timeRegion.toDayOfWeek) { - timeRegion.fromDayOfWeek = timeRegion.toDayOfWeek; - } - - if (!timeRegion.toDayOfWeek && timeRegion.fromDayOfWeek) { - timeRegion.toDayOfWeek = timeRegion.fromDayOfWeek; - } - - if (timeRegion.fromDayOfWeek) { - hRange.from.dayOfWeek = Number(timeRegion.fromDayOfWeek); - } - - if (timeRegion.toDayOfWeek) { - hRange.to.dayOfWeek = Number(timeRegion.toDayOfWeek); - } - - if (hRange.from.dayOfWeek && hRange.from.h === null && hRange.from.m === null) { - hRange.from.h = 0; - hRange.from.m = 0; - hRange.from.s = 0; - } - - if (hRange.to.dayOfWeek && hRange.to.h === null && hRange.to.m === null) { - hRange.to.h = 23; - hRange.to.m = 59; - hRange.to.s = 59; - } - - if (!hRange.from || !hRange.to) { - continue; - } - - regions = []; - - fromStart = dateTime(tRange.from); - fromStart.set('hour', 0); - fromStart.set('minute', 0); - fromStart.set('second', 0); - fromStart.add(hRange.from.h, 'hours'); - fromStart.add(hRange.from.m, 'minutes'); - fromStart.add(hRange.from.s, 'seconds'); - - while (fromStart.unix() <= tRange.to.unix()) { - while (hRange.from.dayOfWeek && hRange.from.dayOfWeek !== fromStart.isoWeekday()) { - fromStart.add(24, 'hours'); - } - - if (fromStart.unix() > tRange.to.unix()) { - break; - } - - fromEnd = dateTime(fromStart); - - if (fromEnd.hour) { - if (hRange.from.h <= hRange.to.h) { - fromEnd.add(hRange.to.h - hRange.from.h, 'hours'); - } else if (hRange.from.h > hRange.to.h) { - while (fromEnd.hour() !== hRange.to.h) { - fromEnd.add(1, 'hours'); - } - } else { - fromEnd.add(24 - hRange.from.h, 'hours'); - - while (fromEnd.hour() !== hRange.to.h) { - fromEnd.add(1, 'hours'); - } + for (const tr of panel.timeRegions) { + const timeRegion: GraphTimeRegionConfig = tr; + const regions = calculateTimesWithin(tr, tRange); + if (regions.length) { + const timeRegionColor = getColor(timeRegion, config.theme); + + for (let j = 0; j < regions.length; j++) { + const r = regions[j]; + if (timeRegion.fill) { + options.grid.markings.push({ + xaxis: { from: r.from, to: r.to }, + color: timeRegionColor.fill, + }); } - } - - fromEnd.set('minute', hRange.to.m); - fromEnd.set('second', hRange.to.s); - - while (hRange.to.dayOfWeek && hRange.to.dayOfWeek !== fromEnd.isoWeekday()) { - fromEnd.add(24, 'hours'); - } - - const outsideRange = - (fromStart.unix() < tRange.from.unix() && fromEnd.unix() < tRange.from.unix()) || - (fromStart.unix() > tRange.to.unix() && fromEnd.unix() > tRange.to.unix()); - - if (!outsideRange) { - regions.push({ from: fromStart.valueOf(), to: fromEnd.valueOf() }); - } - fromStart.add(24, 'hours'); - } - - timeRegionColor = getColor(timeRegion, config.theme); - - for (let j = 0; j < regions.length; j++) { - const r = regions[j]; - if (timeRegion.fill) { - options.grid.markings.push({ - xaxis: { from: r.from, to: r.to }, - color: timeRegionColor.fill, - }); - } - - if (timeRegion.line) { - options.grid.markings.push({ - xaxis: { from: r.from, to: r.from }, - color: timeRegionColor.line, - }); - options.grid.markings.push({ - xaxis: { from: r.to, to: r.to }, - color: timeRegionColor.line, - }); + if (timeRegion.line) { + options.grid.markings.push({ + xaxis: { from: r.from, to: r.from }, + color: timeRegionColor.line, + }); + options.grid.markings.push({ + xaxis: { from: r.to, to: r.to }, + color: timeRegionColor.line, + }); + } } } } } - - parseTimeRange(str: string) { - const timeRegex = /^([\d]+):?(\d{2})?/; - const result: any = { h: null, m: null }; - const match = timeRegex.exec(str); - - if (!match) { - return result; - } - - 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; - } - } - - return result; - } }