From 63be43e3b2c1ce5cfa0c8db3fca20b6f591bdb11 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 21 Jun 2018 14:41:47 +0200 Subject: [PATCH 01/10] graph: Time region support --- public/app/plugins/panel/graph/graph.ts | 6 + public/app/plugins/panel/graph/module.ts | 2 + .../graph/specs/time_region_manager.test.ts | 217 +++++++++++++++ .../app/plugins/panel/graph/tab_display.html | 9 + .../plugins/panel/graph/thresholds_form.html | 77 ++++++ .../plugins/panel/graph/thresholds_form.ts | 82 +----- .../panel/graph/time_region_manager.ts | 249 ++++++++++++++++++ .../panel/graph/time_regions_form.html | 64 +++++ .../plugins/panel/graph/time_regions_form.ts | 73 +++++ 9 files changed, 698 insertions(+), 81 deletions(-) create mode 100644 public/app/plugins/panel/graph/specs/time_region_manager.test.ts create mode 100644 public/app/plugins/panel/graph/thresholds_form.html create mode 100644 public/app/plugins/panel/graph/time_region_manager.ts create mode 100644 public/app/plugins/panel/graph/time_regions_form.html create mode 100644 public/app/plugins/panel/graph/time_regions_form.ts diff --git a/public/app/plugins/panel/graph/graph.ts b/public/app/plugins/panel/graph/graph.ts index 01afd0716e6..c5f98792568 100755 --- a/public/app/plugins/panel/graph/graph.ts +++ b/public/app/plugins/panel/graph/graph.ts @@ -16,6 +16,7 @@ import { tickStep } from 'app/core/utils/ticks'; import { appEvents, coreModule, updateLegendValues } from 'app/core/core'; import GraphTooltip from './graph_tooltip'; import { ThresholdManager } from './threshold_manager'; +import { TimeRegionManager } from './time_region_manager'; import { EventManager } from 'app/features/annotations/all'; import { convertToHistogramData } from './histogram'; import { alignYLevel } from './align_yaxes'; @@ -38,6 +39,7 @@ class GraphElement { panelWidth: number; eventManager: EventManager; thresholdManager: ThresholdManager; + timeRegionManager: TimeRegionManager; legendElem: HTMLElement; constructor(private scope, private elem, private timeSrv) { @@ -49,6 +51,7 @@ class GraphElement { this.panelWidth = 0; this.eventManager = new EventManager(this.ctrl); this.thresholdManager = new ThresholdManager(this.ctrl); + this.timeRegionManager = new TimeRegionManager(this.ctrl); this.tooltip = new GraphTooltip(this.elem, this.ctrl.dashboard, this.scope, () => { return this.sortedSeries; }); @@ -125,6 +128,7 @@ class GraphElement { onPanelTeardown() { this.thresholdManager = null; + this.timeRegionManager = null; if (this.plot) { this.plot.destroy(); @@ -215,6 +219,7 @@ class GraphElement { } this.thresholdManager.draw(plot); + this.timeRegionManager.draw(plot); } processOffsetHook(plot, gridMargin) { @@ -293,6 +298,7 @@ class GraphElement { this.prepareXAxis(options, this.panel); this.configureYAxisOptions(this.data, options); this.thresholdManager.addFlotOptions(options, this.panel); + this.timeRegionManager.addFlotOptions(options, this.panel); this.eventManager.addFlotEvents(this.annotations, options); this.sortedSeries = this.sortSeries(this.data, this.panel); diff --git a/public/app/plugins/panel/graph/module.ts b/public/app/plugins/panel/graph/module.ts index a6c5190d937..5b48636de5f 100644 --- a/public/app/plugins/panel/graph/module.ts +++ b/public/app/plugins/panel/graph/module.ts @@ -1,6 +1,7 @@ import './graph'; import './series_overrides_ctrl'; import './thresholds_form'; +import './time_regions_form'; import template from './template'; import _ from 'lodash'; @@ -111,6 +112,7 @@ class GraphCtrl extends MetricsPanelCtrl { // other style overrides seriesOverrides: [], thresholds: [], + timeRegions: [], }; /** @ngInject */ diff --git a/public/app/plugins/panel/graph/specs/time_region_manager.test.ts b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts new file mode 100644 index 00000000000..d1b2290cb61 --- /dev/null +++ b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts @@ -0,0 +1,217 @@ +import { TimeRegionManager, colorModes } from '../time_region_manager'; +import moment from 'moment'; + +describe('TimeRegionManager', () => { + function plotOptionsScenario(desc, func) { + describe(desc, () => { + const ctx: any = { + panel: { + timeRegions: [], + }, + options: { + grid: { markings: [] }, + }, + panelCtrl: { + range: {}, + dashboard: { + isTimezoneUtc: () => false, + }, + }, + }; + + ctx.setup = (regions, from, to) => { + ctx.panel.timeRegions = regions; + ctx.panelCtrl.range.from = from; + ctx.panelCtrl.range.to = to; + const manager = new TimeRegionManager(ctx.panelCtrl); + manager.addFlotOptions(ctx.options, ctx.panel); + }; + + ctx.printScenario = () => { + console.log(`Time range: from=${ctx.panelCtrl.range.from.format()}, to=${ctx.panelCtrl.range.to.format()}`); + ctx.options.grid.markings.forEach((m, i) => { + console.log( + `Marking (${i}): from=${moment(m.xaxis.from).format()}, to=${moment(m.xaxis.to).format()}, color=${m.color}` + ); + }); + }; + + func(ctx); + }); + } + + describe('When creating plot markings', () => { + plotOptionsScenario('for day of week region', ctx => { + const regions = [{ fromDayOfWeek: 1, toDayOfWeek: 1, fill: true, line: true, colorMode: 'red' }]; + const from = moment('2018-01-01 00:00'); + const to = moment('2018-01-01 23:59'); + ctx.setup(regions, from, to); + + it('should add 3 markings', () => { + expect(ctx.options.grid.markings.length).toBe(3); + }); + + it('should add fill', () => { + const markings = ctx.options.grid.markings; + expect(moment(markings[0].xaxis.from).format()).toBe(from.format()); + expect(moment(markings[0].xaxis.to).format()).toBe(to.format()); + expect(markings[0].color).toBe(colorModes.red.color.fill); + }); + + it('should add line before', () => { + const markings = ctx.options.grid.markings; + expect(moment(markings[1].xaxis.from).format()).toBe(from.format()); + expect(moment(markings[1].xaxis.to).format()).toBe(from.format()); + expect(markings[1].color).toBe(colorModes.red.color.line); + }); + + it('should add line after', () => { + const markings = ctx.options.grid.markings; + expect(moment(markings[2].xaxis.from).format()).toBe(to.format()); + expect(moment(markings[2].xaxis.to).format()).toBe(to.format()); + expect(markings[2].color).toBe(colorModes.red.color.line); + }); + }); + + plotOptionsScenario('for time from region', ctx => { + const regions = [{ from: '05:00', fill: true, colorMode: 'red' }]; + const from = moment('2018-01-01 00:00'); + const to = moment('2018-01-03 23:59'); + ctx.setup(regions, from, to); + + it('should add 3 markings', () => { + expect(ctx.options.grid.markings.length).toBe(3); + }); + + it('should add one fill at 05:00 each day', () => { + const markings = ctx.options.grid.markings; + + const firstFill = moment(from.add(5, 'hours')); + expect(moment(markings[0].xaxis.from).format()).toBe(firstFill.format()); + expect(moment(markings[0].xaxis.to).format()).toBe(firstFill.format()); + expect(markings[0].color).toBe(colorModes.red.color.fill); + + const secondFill = moment(firstFill).add(1, 'days'); + expect(moment(markings[1].xaxis.from).format()).toBe(secondFill.format()); + expect(moment(markings[1].xaxis.to).format()).toBe(secondFill.format()); + expect(markings[1].color).toBe(colorModes.red.color.fill); + + const thirdFill = moment(secondFill).add(1, 'days'); + expect(moment(markings[2].xaxis.from).format()).toBe(thirdFill.format()); + expect(moment(markings[2].xaxis.to).format()).toBe(thirdFill.format()); + expect(markings[2].color).toBe(colorModes.red.color.fill); + }); + }); + + plotOptionsScenario('for time to region', ctx => { + const regions = [{ to: '05:00', fill: true, colorMode: 'red' }]; + const from = moment('2018-02-01 00:00'); + const to = moment('2018-02-03 23:59'); + ctx.setup(regions, from, to); + + it('should add 3 markings', () => { + expect(ctx.options.grid.markings.length).toBe(3); + }); + + it('should add one fill at 05:00 each day', () => { + const markings = ctx.options.grid.markings; + + const firstFill = moment(from.add(5, 'hours')); + expect(moment(markings[0].xaxis.from).format()).toBe(firstFill.format()); + expect(moment(markings[0].xaxis.to).format()).toBe(firstFill.format()); + expect(markings[0].color).toBe(colorModes.red.color.fill); + + const secondFill = moment(firstFill).add(1, 'days'); + expect(moment(markings[1].xaxis.from).format()).toBe(secondFill.format()); + expect(moment(markings[1].xaxis.to).format()).toBe(secondFill.format()); + expect(markings[1].color).toBe(colorModes.red.color.fill); + + const thirdFill = moment(secondFill).add(1, 'days'); + expect(moment(markings[2].xaxis.from).format()).toBe(thirdFill.format()); + expect(moment(markings[2].xaxis.to).format()).toBe(thirdFill.format()); + expect(markings[2].color).toBe(colorModes.red.color.fill); + }); + }); + + plotOptionsScenario('for day of week from/to region', ctx => { + const regions = [{ fromDayOfWeek: 7, toDayOfWeek: 7, fill: true, colorMode: 'red' }]; + const from = moment('2018-01-01 18:45:05'); + const to = moment('2018-01-22 08:27:00'); + ctx.setup(regions, from, to); + + it('should add 3 markings', () => { + expect(ctx.options.grid.markings.length).toBe(3); + }); + + it('should add one fill at each sunday', () => { + const markings = ctx.options.grid.markings; + + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07 00:00:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-07 23:59:59').format()); + expect(markings[0].color).toBe(colorModes.red.color.fill); + + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14 00:00:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-14 23:59:59').format()); + expect(markings[1].color).toBe(colorModes.red.color.fill); + + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21 00:00:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-21 23:59:59').format()); + expect(markings[2].color).toBe(colorModes.red.color.fill); + }); + }); + + plotOptionsScenario('for day of week from region', ctx => { + const regions = [{ fromDayOfWeek: 7, fill: true, colorMode: 'red' }]; + const from = moment('2018-01-01 18:45:05'); + const to = moment('2018-01-22 08:27:00'); + ctx.setup(regions, from, to); + + it('should add 3 markings', () => { + expect(ctx.options.grid.markings.length).toBe(3); + }); + + it('should add one fill at each sunday', () => { + const markings = ctx.options.grid.markings; + + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07 00:00:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-07 23:59:59').format()); + expect(markings[0].color).toBe(colorModes.red.color.fill); + + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14 00:00:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-14 23:59:59').format()); + expect(markings[1].color).toBe(colorModes.red.color.fill); + + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21 00:00:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-21 23:59:59').format()); + expect(markings[2].color).toBe(colorModes.red.color.fill); + }); + }); + + plotOptionsScenario('for day of week to region', ctx => { + const regions = [{ toDayOfWeek: 7, fill: true, colorMode: 'red' }]; + const from = moment('2018-01-01 18:45:05'); + const to = moment('2018-01-22 08:27:00'); + ctx.setup(regions, from, to); + + it('should add 3 markings', () => { + expect(ctx.options.grid.markings.length).toBe(3); + }); + + it('should add one fill at each sunday', () => { + const markings = ctx.options.grid.markings; + + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07 00:00:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-07 23:59:59').format()); + expect(markings[0].color).toBe(colorModes.red.color.fill); + + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14 00:00:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-14 23:59:59').format()); + expect(markings[1].color).toBe(colorModes.red.color.fill); + + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21 00:00:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-21 23:59:59').format()); + expect(markings[2].color).toBe(colorModes.red.color.fill); + }); + }); + }); +}); diff --git a/public/app/plugins/panel/graph/tab_display.html b/public/app/plugins/panel/graph/tab_display.html index ebc6cf9b18e..d407f30ffc8 100644 --- a/public/app/plugins/panel/graph/tab_display.html +++ b/public/app/plugins/panel/graph/tab_display.html @@ -14,6 +14,11 @@ Thresholds ({{ctrl.panel.thresholds.length}}) +
  • + + Time regions ({{ctrl.panel.timeRegions.length}}) + +
  • @@ -132,4 +137,8 @@ +
    + +
    + diff --git a/public/app/plugins/panel/graph/thresholds_form.html b/public/app/plugins/panel/graph/thresholds_form.html new file mode 100644 index 00000000000..81877150a47 --- /dev/null +++ b/public/app/plugins/panel/graph/thresholds_form.html @@ -0,0 +1,77 @@ +
    +
    Thresholds
    +

    + Visual thresholds options disabled. + Visit the Alert tab update your thresholds.
    + To re-enable thresholds, the alert rule must be deleted from this panel. +

    +
    +
    +
    + +
    + +
    +
    + +
    + +
    + +
    + +
    + +
    +
    + + + +
    + + + + +
    + + + +
    + + + + +
    + +
    + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    +
    +
    \ No newline at end of file diff --git a/public/app/plugins/panel/graph/thresholds_form.ts b/public/app/plugins/panel/graph/thresholds_form.ts index 5f1edb8aa9a..4f480873d5b 100644 --- a/public/app/plugins/panel/graph/thresholds_form.ts +++ b/public/app/plugins/panel/graph/thresholds_form.ts @@ -58,90 +58,10 @@ export class ThresholdFormCtrl { } } -const template = ` -
    -
    Thresholds
    -

    - Visual thresholds options disabled. - Visit the Alert tab update your thresholds.
    - To re-enable thresholds, the alert rule must be deleted from this panel. -

    -
    -
    -
    - -
    - -
    -
    - -
    - -
    - -
    - -
    - -
    -
    - - - -
    - - - - -
    - - - -
    - - - - -
    - -
    - -
    - -
    -
    - -
    - -
    -
    - -
    - -
    -
    -
    -`; - coreModule.directive('graphThresholdForm', () => { return { restrict: 'E', - template: template, + templateUrl: 'public/app/plugins/panel/graph/thresholds_form.html', controller: ThresholdFormCtrl, bindToController: true, controllerAs: 'ctrl', diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts new file mode 100644 index 00000000000..c3c6aadaa31 --- /dev/null +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -0,0 +1,249 @@ +import 'vendor/flot/jquery.flot'; +import _ from 'lodash'; +import moment from 'moment'; +import config from 'app/core/config'; + +export const colorModes = { + custom: { title: 'Custom' }, + red: { + title: 'Red', + color: { fill: 'rgba(234, 112, 112, 0.12)', line: 'rgba(237, 46, 24, 0.60)' }, + }, + yellow: { + title: 'Yellow', + color: { fill: 'rgba(235, 138, 14, 0.12)', line: 'rgba(247, 149, 32, 0.60)' }, + }, + green: { + title: 'Green', + color: { fill: 'rgba(11, 237, 50, 0.090)', line: 'rgba(6,163,69, 0.60)' }, + }, + background3: { + themeDependent: true, + title: 'Background (3%)', + darkColor: { fill: 'rgba(255, 255, 255, 0.03)', line: 'rgba(255, 255, 255, 0.1)' }, + lightColor: { fill: 'rgba(0, 0, 0, 0.03)', line: 'rgba(0, 0, 0, 0.1)' }, + }, + background6: { + themeDependent: true, + title: 'Background (6%)', + darkColor: { fill: 'rgba(255, 255, 255, 0.06)', line: 'rgba(255, 255, 255, 0.15)' }, + lightColor: { fill: 'rgba(0, 0, 0, 0.06)', line: 'rgba(0, 0, 0, 0.15)' }, + }, + background9: { + themeDependent: true, + title: 'Background (9%)', + darkColor: { fill: 'rgba(255, 255, 255, 0.09)', line: 'rgba(255, 255, 255, 0.2)' }, + lightColor: { fill: 'rgba(0, 0, 0, 0.09)', line: 'rgba(0, 0, 0, 0.2)' }, + }, +}; + +export function getColorModes() { + return _.map(Object.keys(colorModes), key => { + return { + key: key, + value: colorModes[key].title, + }; + }); +} + +function getColor(timeRegion) { + if (Object.keys(colorModes).indexOf(timeRegion.colorMode) === -1) { + timeRegion.colorMode = 'red'; + } + + if (timeRegion.colorMode === 'custom') { + return { + fill: timeRegion.fillColor, + line: timeRegion.lineColor, + }; + } + + const colorMode = colorModes[timeRegion.colorMode]; + if (colorMode.themeDependent === true) { + return config.bootData.user.lightTheme ? colorMode.lightColor : colorMode.darkColor; + } + + return colorMode.color; +} + +export class TimeRegionManager { + plot: any; + timeRegions: any; + + constructor(private panelCtrl) {} + + draw(plot) { + this.timeRegions = this.panelCtrl.panel.timeRegions; + this.plot = plot; + } + + addFlotOptions(options, panel) { + if (!panel.timeRegions || panel.timeRegions.length === 0) { + return; + } + + const tRange = this.panelCtrl.dashboard.isTimezoneUtc() + ? { from: this.panelCtrl.range.from, to: this.panelCtrl.range.to } + : { from: this.panelCtrl.range.from.local(), to: this.panelCtrl.range.to.local() }; + + let i, hRange, timeRegion, regions, fromStart, fromEnd, timeRegionColor; + + for (i = 0; i < panel.timeRegions.length; i++) { + timeRegion = panel.timeRegions[i]; + + if (!(timeRegion.fromDayOfWeek || timeRegion.from) && !(timeRegion.toDayOfWeek || timeRegion.to)) { + continue; + } + + 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.h && hRange.to.h) { + hRange.from = hRange.to; + } + + if (hRange.from.h && !hRange.to.h) { + hRange.to = hRange.from; + } + + if (hRange.from.dayOfWeek && !hRange.from.h && !hRange.from.m) { + hRange.from.h = 0; + hRange.from.m = 0; + hRange.from.s = 0; + } + + if (hRange.to.dayOfWeek && !hRange.to.h && !hRange.to.m) { + hRange.to.h = 23; + hRange.to.m = 59; + hRange.to.s = 59; + } + + if (!hRange.from || !hRange.to) { + continue; + } + + regions = []; + + if ( + hRange.from.h >= tRange.from.hour() && + hRange.from.h <= tRange.from.hour() && + hRange.from.m >= tRange.from.minute() && + hRange.from.m <= tRange.from.minute() && + hRange.to.h >= tRange.to.hour() && + hRange.to.h <= tRange.to.hour() && + hRange.to.m >= tRange.to.minute() && + hRange.to.m <= tRange.to.minute() + ) { + regions.push({ from: tRange.from.valueOf(), to: tRange.to.startOf('hour').valueOf() }); + } else { + fromStart = moment(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 = moment(fromStart); + + if (hRange.from.h <= hRange.to.h) { + fromEnd.add(hRange.to.h - hRange.from.h, 'hours'); + } else if (hRange.from.h + hRange.to.h < 23) { + fromEnd.add(hRange.to.h, 'hours'); + } else { + fromEnd.add(24 - hRange.from.h, 'hours'); + } + + 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'); + } + + regions.push({ from: fromStart.valueOf(), to: fromEnd.valueOf() }); + fromStart.add(24, 'hours'); + } + } + + timeRegionColor = getColor(timeRegion); + + 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, + }); + } + } + } + } + + parseTimeRange(str) { + const timeRegex = /^([\d]+):?(\d{2})?/; + const result = { 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; + } +} diff --git a/public/app/plugins/panel/graph/time_regions_form.html b/public/app/plugins/panel/graph/time_regions_form.html new file mode 100644 index 00000000000..66bf4352aa5 --- /dev/null +++ b/public/app/plugins/panel/graph/time_regions_form.html @@ -0,0 +1,64 @@ +
    +
    Time regions
    +
    +
    + +
    + +
    + +
    + +
    + + +
    + +
    + +
    + +
    + +
    + +
    +
    + + + +
    + + + + +
    + + + +
    + + + + +
    + +
    + +
    +
    + +
    + +
    +
    \ No newline at end of file diff --git a/public/app/plugins/panel/graph/time_regions_form.ts b/public/app/plugins/panel/graph/time_regions_form.ts new file mode 100644 index 00000000000..e01ec4acd0e --- /dev/null +++ b/public/app/plugins/panel/graph/time_regions_form.ts @@ -0,0 +1,73 @@ +import coreModule from 'app/core/core_module'; +import { getColorModes } from './time_region_manager'; + +export class TimeRegionFormCtrl { + panelCtrl: any; + panel: any; + disabled: boolean; + colorModes: any; + + /** @ngInject */ + constructor($scope) { + this.panel = this.panelCtrl.panel; + + const unbindDestroy = $scope.$on('$destroy', () => { + this.panelCtrl.editingTimeRegions = false; + this.panelCtrl.render(); + unbindDestroy(); + }); + + this.colorModes = getColorModes(); + this.panelCtrl.editingTimeRegions = true; + } + + render() { + this.panelCtrl.render(); + } + + addTimeRegion() { + this.panel.timeRegions.push({ + op: 'time', + fromDayOfWeek: undefined, + from: undefined, + toDayOfWeek: undefined, + to: undefined, + colorMode: 'critical', + fill: true, + line: false, + }); + this.panelCtrl.render(); + } + + removeTimeRegion(index) { + this.panel.timeRegions.splice(index, 1); + this.panelCtrl.render(); + } + + onFillColorChange(index) { + return newColor => { + this.panel.timeRegions[index].fillColor = newColor; + this.render(); + }; + } + + onLineColorChange(index) { + return newColor => { + this.panel.timeRegions[index].lineColor = newColor; + this.render(); + }; + } +} + +coreModule.directive('graphTimeRegionForm', () => { + return { + restrict: 'E', + templateUrl: 'public/app/plugins/panel/graph/time_regions_form.html', + controller: TimeRegionFormCtrl, + bindToController: true, + controllerAs: 'ctrl', + scope: { + panelCtrl: '=', + }, + }; +}); From e8e189d111bec5b5322f0e1309871333c691b720 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 13 Nov 2018 12:39:10 +0100 Subject: [PATCH 02/10] devenv: graph time regions test dashboard --- .../panel_tests_graph_time_regions.json | 417 ++++++++++++++++++ 1 file changed, 417 insertions(+) create mode 100644 devenv/dev-dashboards/panel_tests_graph_time_regions.json diff --git a/devenv/dev-dashboards/panel_tests_graph_time_regions.json b/devenv/dev-dashboards/panel_tests_graph_time_regions.json new file mode 100644 index 00000000000..a72d7d24c2a --- /dev/null +++ b/devenv/dev-dashboards/panel_tests_graph_time_regions.json @@ -0,0 +1,417 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-testdata", + "fill": 2, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk", + "target": "" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [ + { + "colorMode": "background6", + "fill": true, + "fillColor": "rgba(255, 255, 255, 0.03)", + "from": "08:30", + "fromDayOfWeek": 1, + "line": false, + "lineColor": "rgba(255, 255, 255, 0.2)", + "op": "time", + "to": "16:45", + "toDayOfWeek": 5 + } + ], + "timeShift": null, + "title": "Business Hours", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-testdata", + "fill": 2, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "", + "format": "time_series", + "intervalFactor": 1, + "refId": "A", + "scenarioId": "random_walk", + "target": "" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [ + { + "colorMode": "red", + "fill": true, + "fillColor": "rgba(255, 255, 255, 0.03)", + "from": "20:00", + "fromDayOfWeek": 7, + "line": false, + "lineColor": "rgba(255, 255, 255, 0.2)", + "op": "time", + "to": "23:00", + "toDayOfWeek": 7 + } + ], + "timeShift": null, + "title": "Sunday's 20-23", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "A-series": "#d683ce" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-testdata", + "fill": 2, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 3, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 0.5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "refId": "A", + "scenarioId": "random_walk", + "target": "" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [ + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(255, 0, 0, 0.22)", + "from": "", + "fromDayOfWeek": 1, + "line": true, + "lineColor": "rgba(255, 0, 0, 0.32)", + "op": "time", + "to": "", + "toDayOfWeek": 1 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(255, 127, 0, 0.22)", + "fromDayOfWeek": 2, + "line": true, + "lineColor": "rgba(255, 127, 0, 0.32)", + "op": "time", + "toDayOfWeek": 2 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(255, 255, 0, 0.22)", + "fromDayOfWeek": 3, + "line": true, + "lineColor": "rgba(255, 255, 0, 0.22)", + "op": "time", + "toDayOfWeek": 3 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(0, 255, 0, 0.22)", + "fromDayOfWeek": 4, + "line": true, + "lineColor": "rgba(0, 255, 0, 0.32)", + "op": "time", + "toDayOfWeek": 4 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(0, 0, 255, 0.22)", + "fromDayOfWeek": 5, + "line": true, + "lineColor": "rgba(0, 0, 255, 0.32)", + "op": "time", + "toDayOfWeek": 5 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(75, 0, 130, 0.22)", + "fromDayOfWeek": 6, + "line": true, + "lineColor": "rgba(75, 0, 130, 0.32)", + "op": "time", + "toDayOfWeek": 6 + }, + { + "colorMode": "custom", + "fill": true, + "fillColor": "rgba(148, 0, 211, 0.22)", + "fromDayOfWeek": 7, + "line": true, + "lineColor": "rgba(148, 0, 211, 0.32)", + "op": "time", + "toDayOfWeek": 7 + } + ], + "timeShift": null, + "title": "Each day of week", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 16, + "style": "dark", + "tags": [ + "gdev", + "panel-tests" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-30d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "utc", + "title": "Panel Tests - Graph (Time Regions)", + "uid": "XMjIZPmik", + "version": 43 +} \ No newline at end of file From 0f57c4b20ef99658d3f54654d45143fd635d9661 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 14 Nov 2018 17:21:20 +0100 Subject: [PATCH 03/10] create time regions solely based on utc time --- .../graph/specs/time_region_manager.test.ts | 157 +++++++++++------- .../panel/graph/time_region_manager.ts | 13 +- 2 files changed, 110 insertions(+), 60 deletions(-) diff --git a/public/app/plugins/panel/graph/specs/time_region_manager.test.ts b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts index d1b2290cb61..35e48897282 100644 --- a/public/app/plugins/panel/graph/specs/time_region_manager.test.ts +++ b/public/app/plugins/panel/graph/specs/time_region_manager.test.ts @@ -28,7 +28,10 @@ describe('TimeRegionManager', () => { }; ctx.printScenario = () => { - console.log(`Time range: from=${ctx.panelCtrl.range.from.format()}, to=${ctx.panelCtrl.range.to.format()}`); + console.log( + `Time range: from=${ctx.panelCtrl.range.from.format()}, to=${ctx.panelCtrl.range.to.format()}`, + ctx.panelCtrl.range.from._isUTC + ); ctx.options.grid.markings.forEach((m, i) => { console.log( `Marking (${i}): from=${moment(m.xaxis.from).format()}, to=${moment(m.xaxis.to).format()}, color=${m.color}` @@ -40,11 +43,11 @@ describe('TimeRegionManager', () => { }); } - describe('When creating plot markings', () => { + describe('When creating plot markings using local time', () => { plotOptionsScenario('for day of week region', ctx => { const regions = [{ fromDayOfWeek: 1, toDayOfWeek: 1, fill: true, line: true, colorMode: 'red' }]; - const from = moment('2018-01-01 00:00'); - const to = moment('2018-01-01 23:59'); + const from = moment('2018-01-01T00:00:00+01:00'); + const to = moment('2018-01-01T23:59:00+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -53,30 +56,30 @@ describe('TimeRegionManager', () => { it('should add fill', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(from.format()); - expect(moment(markings[0].xaxis.to).format()).toBe(to.format()); + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-01T01:00:00+01:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-02T00:59:59+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); }); it('should add line before', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[1].xaxis.from).format()).toBe(from.format()); - expect(moment(markings[1].xaxis.to).format()).toBe(from.format()); + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-01T01:00:00+01:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-01T01:00:00+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.line); }); it('should add line after', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[2].xaxis.from).format()).toBe(to.format()); - expect(moment(markings[2].xaxis.to).format()).toBe(to.format()); + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-02T00:59:59+01:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-02T00:59:59+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.line); }); }); plotOptionsScenario('for time from region', ctx => { const regions = [{ from: '05:00', fill: true, colorMode: 'red' }]; - const from = moment('2018-01-01 00:00'); - const to = moment('2018-01-03 23:59'); + const from = moment('2018-01-01T00:00+01:00'); + const to = moment('2018-01-03T23:59+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -86,27 +89,24 @@ describe('TimeRegionManager', () => { it('should add one fill at 05:00 each day', () => { const markings = ctx.options.grid.markings; - const firstFill = moment(from.add(5, 'hours')); - expect(moment(markings[0].xaxis.from).format()).toBe(firstFill.format()); - expect(moment(markings[0].xaxis.to).format()).toBe(firstFill.format()); + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-01T06:00:00+01:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-01T06:00:00+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - const secondFill = moment(firstFill).add(1, 'days'); - expect(moment(markings[1].xaxis.from).format()).toBe(secondFill.format()); - expect(moment(markings[1].xaxis.to).format()).toBe(secondFill.format()); + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-02T06:00:00+01:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-02T06:00:00+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - const thirdFill = moment(secondFill).add(1, 'days'); - expect(moment(markings[2].xaxis.from).format()).toBe(thirdFill.format()); - expect(moment(markings[2].xaxis.to).format()).toBe(thirdFill.format()); + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-03T06:00:00+01:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-03T06:00:00+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for time to region', ctx => { const regions = [{ to: '05:00', fill: true, colorMode: 'red' }]; - const from = moment('2018-02-01 00:00'); - const to = moment('2018-02-03 23:59'); + const from = moment('2018-02-01T00:00+01:00'); + const to = moment('2018-02-03T23:59+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -116,27 +116,24 @@ describe('TimeRegionManager', () => { it('should add one fill at 05:00 each day', () => { const markings = ctx.options.grid.markings; - const firstFill = moment(from.add(5, 'hours')); - expect(moment(markings[0].xaxis.from).format()).toBe(firstFill.format()); - expect(moment(markings[0].xaxis.to).format()).toBe(firstFill.format()); + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-02-01T06:00:00+01:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-02-01T06:00:00+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - const secondFill = moment(firstFill).add(1, 'days'); - expect(moment(markings[1].xaxis.from).format()).toBe(secondFill.format()); - expect(moment(markings[1].xaxis.to).format()).toBe(secondFill.format()); + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-02-02T06:00:00+01:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-02-02T06:00:00+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - const thirdFill = moment(secondFill).add(1, 'days'); - expect(moment(markings[2].xaxis.from).format()).toBe(thirdFill.format()); - expect(moment(markings[2].xaxis.to).format()).toBe(thirdFill.format()); + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-02-03T06:00:00+01:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-02-03T06:00:00+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for day of week from/to region', ctx => { const regions = [{ fromDayOfWeek: 7, toDayOfWeek: 7, fill: true, colorMode: 'red' }]; - const from = moment('2018-01-01 18:45:05'); - const to = moment('2018-01-22 08:27:00'); + const from = moment('2018-01-01T18:45:05+01:00'); + const to = moment('2018-01-22T08:27:00+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -146,24 +143,24 @@ describe('TimeRegionManager', () => { it('should add one fill at each sunday', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07 00:00:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-07 23:59:59').format()); + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07T01:00:00+01:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-08T00:59:59+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14 00:00:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-14 23:59:59').format()); + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14T01:00:00+01:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-15T00:59:59+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21 00:00:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-21 23:59:59').format()); + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21T01:00:00+01:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-22T00:59:59+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for day of week from region', ctx => { const regions = [{ fromDayOfWeek: 7, fill: true, colorMode: 'red' }]; - const from = moment('2018-01-01 18:45:05'); - const to = moment('2018-01-22 08:27:00'); + const from = moment('2018-01-01T18:45:05+01:00'); + const to = moment('2018-01-22T08:27:00+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -173,24 +170,24 @@ describe('TimeRegionManager', () => { it('should add one fill at each sunday', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07 00:00:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-07 23:59:59').format()); + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07T01:00:00+01:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-08T00:59:59+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14 00:00:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-14 23:59:59').format()); + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14T01:00:00+01:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-15T00:59:59+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21 00:00:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-21 23:59:59').format()); + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21T01:00:00+01:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-22T00:59:59+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); plotOptionsScenario('for day of week to region', ctx => { const regions = [{ toDayOfWeek: 7, fill: true, colorMode: 'red' }]; - const from = moment('2018-01-01 18:45:05'); - const to = moment('2018-01-22 08:27:00'); + const from = moment('2018-01-01T18:45:05+01:00'); + const to = moment('2018-01-22T08:27:00+01:00'); ctx.setup(regions, from, to); it('should add 3 markings', () => { @@ -200,18 +197,66 @@ describe('TimeRegionManager', () => { it('should add one fill at each sunday', () => { const markings = ctx.options.grid.markings; - expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07 00:00:00').format()); - expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-07 23:59:59').format()); + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-01-07T01:00:00+01:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-01-08T00:59:59+01:00').format()); expect(markings[0].color).toBe(colorModes.red.color.fill); - expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14 00:00:00').format()); - expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-14 23:59:59').format()); + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-01-14T01:00:00+01:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-01-15T00:59:59+01:00').format()); expect(markings[1].color).toBe(colorModes.red.color.fill); - expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21 00:00:00').format()); - expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-21 23:59:59').format()); + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-01-21T01:00:00+01:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-01-22T00:59:59+01:00').format()); expect(markings[2].color).toBe(colorModes.red.color.fill); }); }); + + plotOptionsScenario('for day of week from/to time region with daylight saving time', ctx => { + const regions = [{ fromDayOfWeek: 7, from: '20:00', toDayOfWeek: 7, to: '23:00', fill: true, colorMode: 'red' }]; + const from = moment('2018-03-17T06:00:00+01:00'); + const to = moment('2018-04-03T06:00:00+02:00'); + ctx.setup(regions, from, to); + + it('should add 3 markings', () => { + expect(ctx.options.grid.markings.length).toBe(3); + }); + + it('should add one fill at each sunday between 20:00 and 23:00', () => { + const markings = ctx.options.grid.markings; + + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-03-18T21:00:00+01:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-03-19T00:00:00+01:00').format()); + + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-03-25T22:00:00+02:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-03-26T01:00:00+02:00').format()); + + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-04-01T22:00:00+02:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-04-02T01:00:00+02:00').format()); + }); + }); + + plotOptionsScenario('for each day of week with winter time', ctx => { + const regions = [{ fromDayOfWeek: 7, toDayOfWeek: 7, fill: true, colorMode: 'red' }]; + const from = moment('2018-10-20T14:50:11+02:00'); + const to = moment('2018-11-07T12:56:23+01:00'); + ctx.setup(regions, from, to); + + it('should add 3 markings', () => { + expect(ctx.options.grid.markings.length).toBe(3); + }); + + it('should add one fill at each sunday', () => { + const markings = ctx.options.grid.markings; + + expect(moment(markings[0].xaxis.from).format()).toBe(moment('2018-10-21T02:00:00+02:00').format()); + expect(moment(markings[0].xaxis.to).format()).toBe(moment('2018-10-22T01:59:59+02:00').format()); + + expect(moment(markings[1].xaxis.from).format()).toBe(moment('2018-10-28T02:00:00+02:00').format()); + expect(moment(markings[1].xaxis.to).format()).toBe(moment('2018-10-29T00:59:59+01:00').format()); + + expect(moment(markings[2].xaxis.from).format()).toBe(moment('2018-11-04T01:00:00+01:00').format()); + expect(moment(markings[2].xaxis.to).format()).toBe(moment('2018-11-05T00:59:59+01:00').format()); + }); + }); }); }); diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts index c3c6aadaa31..1475ae6040a 100644 --- a/public/app/plugins/panel/graph/time_region_manager.ts +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -82,9 +82,7 @@ export class TimeRegionManager { return; } - const tRange = this.panelCtrl.dashboard.isTimezoneUtc() - ? { from: this.panelCtrl.range.from, to: this.panelCtrl.range.to } - : { from: this.panelCtrl.range.from.local(), to: this.panelCtrl.range.to.local() }; + const tRange = { from: moment(this.panelCtrl.range.from).utc(), to: moment(this.panelCtrl.range.to).utc() }; let i, hRange, timeRegion, regions, fromStart, fromEnd, timeRegionColor; @@ -188,7 +186,14 @@ export class TimeRegionManager { fromEnd.add(24, 'hours'); } - regions.push({ from: fromStart.valueOf(), to: fromEnd.valueOf() }); + 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'); } } From 2f65b061355fc6871174c43fd9b18a74b2a83dad Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 14 Nov 2018 17:22:34 +0100 Subject: [PATCH 04/10] devenv: graph time regions test dashboard --- .../panel_tests_graph_time_regions.json | 100 +++++++++++++++++- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/devenv/dev-dashboards/panel_tests_graph_time_regions.json b/devenv/dev-dashboards/panel_tests_graph_time_regions.json index a72d7d24c2a..4cace512741 100644 --- a/devenv/dev-dashboards/panel_tests_graph_time_regions.json +++ b/devenv/dev-dashboards/panel_tests_graph_time_regions.json @@ -167,7 +167,7 @@ "fillColor": "rgba(255, 255, 255, 0.03)", "from": "20:00", "fromDayOfWeek": 7, - "line": false, + "line": true, "lineColor": "rgba(255, 255, 255, 0.2)", "op": "time", "to": "23:00", @@ -369,6 +369,100 @@ "align": false, "alignLevel": null } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-testdata", + "fill": 2, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "", + "format": "time_series", + "intervalFactor": 1, + "refId": "A", + "scenarioId": "random_walk", + "target": "" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [ + { + "colorMode": "red", + "fill": true, + "from": "05:00", + "line": true, + "op": "time" + } + ], + "timeShift": null, + "title": "05:00", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } } ], "refresh": false, @@ -410,8 +504,8 @@ "30d" ] }, - "timezone": "utc", + "timezone": "browser", "title": "Panel Tests - Graph (Time Regions)", "uid": "XMjIZPmik", - "version": 43 + "version": 1 } \ No newline at end of file From dea953003ce464e580ee3173ccc6cc71316ccf87 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 14 Nov 2018 18:47:35 +0100 Subject: [PATCH 05/10] docs: description about graph panel time regions feature --- docs/sources/features/panels/graph.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/sources/features/panels/graph.md b/docs/sources/features/panels/graph.md index 5a010ceca40..44fa0e7c0db 100644 --- a/docs/sources/features/panels/graph.md +++ b/docs/sources/features/panels/graph.md @@ -186,6 +186,14 @@ There is an option under Series overrides to draw lines as dashes. Set Dashes to Thresholds allow you to add arbitrary lines or sections to the graph to make it easier to see when the graph crosses a particular threshold. +### Time Regions + +> Only available in Grafana v5.4 and above. + +{{< docs-imagebox img="/img/docs/v54/graph_time_regions.png" max-width= "800px" >}} + +Time regions allow you to highlight certain time regions of the graph to make it easier to see for example weekends, business hours and/or off work hours. + ## Time Range {{< docs-imagebox img="/img/docs/v51/graph-time-range.png" max-width= "900px" >}} From 81efc00adf7a7b0a979799e9dff40ce8037900af Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 15 Nov 2018 11:21:30 +0100 Subject: [PATCH 06/10] set default color mode --- public/app/plugins/panel/graph/time_regions_form.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/time_regions_form.ts b/public/app/plugins/panel/graph/time_regions_form.ts index e01ec4acd0e..5dc9c4016eb 100644 --- a/public/app/plugins/panel/graph/time_regions_form.ts +++ b/public/app/plugins/panel/graph/time_regions_form.ts @@ -32,7 +32,7 @@ export class TimeRegionFormCtrl { from: undefined, toDayOfWeek: undefined, to: undefined, - colorMode: 'critical', + colorMode: 'background6', fill: true, line: false, }); From 116e367e7153f34c14e27b9d24842e4707a9a5b3 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 15 Nov 2018 11:30:49 +0100 Subject: [PATCH 07/10] fix time regions mutable bug --- public/app/plugins/panel/graph/time_region_manager.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts index 1475ae6040a..b8ab9a856be 100644 --- a/public/app/plugins/panel/graph/time_region_manager.ts +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -86,8 +86,10 @@ export class TimeRegionManager { let i, hRange, timeRegion, regions, fromStart, fromEnd, timeRegionColor; - for (i = 0; i < panel.timeRegions.length; i++) { - timeRegion = panel.timeRegions[i]; + const timeRegionsCopy = panel.timeRegions.map(a => ({ ...a })); + + for (i = 0; i < timeRegionsCopy.length; i++) { + timeRegion = timeRegionsCopy[i]; if (!(timeRegion.fromDayOfWeek || timeRegion.from) && !(timeRegion.toDayOfWeek || timeRegion.to)) { continue; From bd6dc01e6b86aa6aade055851ad7ce5cd8aca7c8 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 15 Nov 2018 11:32:27 +0100 Subject: [PATCH 08/10] devenv: graph time regions test dashboard --- devenv/dev-dashboards/panel_tests_graph_time_regions.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/devenv/dev-dashboards/panel_tests_graph_time_regions.json b/devenv/dev-dashboards/panel_tests_graph_time_regions.json index 4cace512741..52818ca7aa1 100644 --- a/devenv/dev-dashboards/panel_tests_graph_time_regions.json +++ b/devenv/dev-dashboards/panel_tests_graph_time_regions.json @@ -167,7 +167,7 @@ "fillColor": "rgba(255, 255, 255, 0.03)", "from": "20:00", "fromDayOfWeek": 7, - "line": true, + "line": false, "lineColor": "rgba(255, 255, 255, 0.2)", "op": "time", "to": "23:00", @@ -420,7 +420,7 @@ "timeRegions": [ { "colorMode": "red", - "fill": true, + "fill": false, "from": "05:00", "line": true, "op": "time" From a8e6b241d67ce98f6505f00a13864580358f0ce1 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 15 Nov 2018 17:07:42 +0100 Subject: [PATCH 09/10] changed time region color modes --- .../panel_tests_graph_time_regions.json | 2 +- .../panel/graph/time_region_manager.ts | 34 +++++++------------ 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/devenv/dev-dashboards/panel_tests_graph_time_regions.json b/devenv/dev-dashboards/panel_tests_graph_time_regions.json index 52818ca7aa1..8d0bae1221c 100644 --- a/devenv/dev-dashboards/panel_tests_graph_time_regions.json +++ b/devenv/dev-dashboards/panel_tests_graph_time_regions.json @@ -63,7 +63,7 @@ "timeFrom": null, "timeRegions": [ { - "colorMode": "background6", + "colorMode": "gray", "fill": true, "fillColor": "rgba(255, 255, 255, 0.03)", "from": "08:30", diff --git a/public/app/plugins/panel/graph/time_region_manager.ts b/public/app/plugins/panel/graph/time_region_manager.ts index b8ab9a856be..95987e40dbe 100644 --- a/public/app/plugins/panel/graph/time_region_manager.ts +++ b/public/app/plugins/panel/graph/time_region_manager.ts @@ -4,37 +4,29 @@ import moment from 'moment'; import config from 'app/core/config'; export const colorModes = { - custom: { title: 'Custom' }, + gray: { + themeDependent: true, + title: 'Gray', + darkColor: { fill: 'rgba(255, 255, 255, 0.09)', line: 'rgba(255, 255, 255, 0.2)' }, + lightColor: { fill: 'rgba(0, 0, 0, 0.09)', line: 'rgba(0, 0, 0, 0.2)' }, + }, red: { title: 'Red', color: { fill: 'rgba(234, 112, 112, 0.12)', line: 'rgba(237, 46, 24, 0.60)' }, }, - yellow: { - title: 'Yellow', - color: { fill: 'rgba(235, 138, 14, 0.12)', line: 'rgba(247, 149, 32, 0.60)' }, - }, green: { title: 'Green', color: { fill: 'rgba(11, 237, 50, 0.090)', line: 'rgba(6,163,69, 0.60)' }, }, - background3: { - themeDependent: true, - title: 'Background (3%)', - darkColor: { fill: 'rgba(255, 255, 255, 0.03)', line: 'rgba(255, 255, 255, 0.1)' }, - lightColor: { fill: 'rgba(0, 0, 0, 0.03)', line: 'rgba(0, 0, 0, 0.1)' }, + blue: { + title: 'Blue', + color: { fill: 'rgba(11, 125, 238, 0.12)', line: 'rgba(11, 125, 238, 0.60)' }, }, - background6: { - themeDependent: true, - title: 'Background (6%)', - darkColor: { fill: 'rgba(255, 255, 255, 0.06)', line: 'rgba(255, 255, 255, 0.15)' }, - lightColor: { fill: 'rgba(0, 0, 0, 0.06)', line: 'rgba(0, 0, 0, 0.15)' }, - }, - background9: { - themeDependent: true, - title: 'Background (9%)', - darkColor: { fill: 'rgba(255, 255, 255, 0.09)', line: 'rgba(255, 255, 255, 0.2)' }, - lightColor: { fill: 'rgba(0, 0, 0, 0.09)', line: 'rgba(0, 0, 0, 0.2)' }, + yellow: { + title: 'Yellow', + color: { fill: 'rgba(235, 138, 14, 0.12)', line: 'rgba(247, 149, 32, 0.60)' }, }, + custom: { title: 'Custom' }, }; export function getColorModes() { From 3b4a224a57aceab5419ae596c1b114302303d15a Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Thu, 15 Nov 2018 17:25:58 +0100 Subject: [PATCH 10/10] Add tooltip --- public/app/plugins/panel/graph/time_regions_form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/plugins/panel/graph/time_regions_form.html b/public/app/plugins/panel/graph/time_regions_form.html index 66bf4352aa5..7292c53ec80 100644 --- a/public/app/plugins/panel/graph/time_regions_form.html +++ b/public/app/plugins/panel/graph/time_regions_form.html @@ -1,5 +1,5 @@
    -
    Time regions
    +
    Time regions All configured time regions refers to UTC time