graph: Time region support

This commit is contained in:
Marcus Efraimsson
2018-06-21 14:41:47 +02:00
parent aa47f80fd8
commit 63be43e3b2
9 changed files with 698 additions and 81 deletions

View File

@@ -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);

View File

@@ -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 */

View File

@@ -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);
});
});
});
});

View File

@@ -14,6 +14,11 @@
Thresholds <span class="muted">({{ctrl.panel.thresholds.length}})</span>
</a>
</li>
<li ng-class="{active: ctrl.subTabIndex === 3}">
<a ng-click="ctrl.subTabIndex = 3">
Time regions <span class="muted">({{ctrl.panel.timeRegions.length}})</span>
</a>
</li>
</ul>
</aside>
@@ -132,4 +137,8 @@
<graph-threshold-form panel-ctrl="ctrl"></graph-threshold-form>
</div>
<div class="edit-tab-content" ng-if="ctrl.subTabIndex === 3">
<graph-time-region-form panel-ctrl="ctrl"></graph-time-region-form>
</div>
</div>

View File

@@ -0,0 +1,77 @@
<div class="gf-form-group">
<h5>Thresholds</h5>
<p class="muted" ng-show="ctrl.disabled">
Visual thresholds options <strong>disabled.</strong>
Visit the Alert tab update your thresholds. <br>
To re-enable thresholds, the alert rule must be deleted from this panel.
</p>
<div ng-class="{'thresholds-form-disabled': ctrl.disabled}">
<div class="gf-form-inline" ng-repeat="threshold in ctrl.panel.thresholds">
<div class="gf-form">
<label class="gf-form-label">T{{$index+1}}</label>
</div>
<div class="gf-form">
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="threshold.op"
ng-options="f for f in ['gt', 'lt']" ng-change="ctrl.render()" ng-disabled="ctrl.disabled"></select>
</div>
<input type="number" ng-model="threshold.value" class="gf-form-input width-8"
ng-change="ctrl.render()" placeholder="value" ng-disabled="ctrl.disabled">
</div>
<div class="gf-form">
<label class="gf-form-label">Color</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="threshold.colorMode"
ng-options="f for f in ['custom', 'critical', 'warning', 'ok']" ng-change="ctrl.render()" ng-disabled="ctrl.disabled">
</select>
</div>
</div>
<gf-form-switch class="gf-form" label="Fill" checked="threshold.fill"
on-change="ctrl.render()" ng-disabled="ctrl.disabled"></gf-form-switch>
<div class="gf-form" ng-if="threshold.fill && threshold.colorMode === 'custom'">
<label class="gf-form-label">Fill color</label>
<span class="gf-form-label">
<color-picker color="threshold.fillColor" onChange="ctrl.onFillColorChange($index)"></color-picker>
</span>
</div>
<gf-form-switch class="gf-form" label="Line" checked="threshold.line"
on-change="ctrl.render()" ng-disabled="ctrl.disabled"></gf-form-switch>
<div class="gf-form" ng-if="threshold.line && threshold.colorMode === 'custom'">
<label class="gf-form-label">Line color</label>
<span class="gf-form-label">
<color-picker color="threshold.lineColor" onChange="ctrl.onLineColorChange($index)"></color-picker>
</span>
</div>
<div class="gf-form">
<label class="gf-form-label">Y-Axis</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="threshold.yaxis"
ng-init="threshold.yaxis = threshold.yaxis === 'left' || threshold.yaxis === 'right' ? threshold.yaxis : 'left'"
ng-options="f for f in ['left', 'right']" ng-change="ctrl.render()" ng-disabled="ctrl.disabled">
</select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" ng-click="ctrl.removeThreshold($index)" ng-disabled="ctrl.disabled">
<i class="fa fa-trash"></i>
</a>
</label>
</div>
</div>
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="ctrl.addThreshold()" ng-disabled="ctrl.disabled">
<i class="fa fa-plus"></i>&nbsp;Add Threshold
</button>
</div>
</div>
</div>

View File

@@ -58,90 +58,10 @@ export class ThresholdFormCtrl {
}
}
const template = `
<div class="gf-form-group">
<h5>Thresholds</h5>
<p class="muted" ng-show="ctrl.disabled">
Visual thresholds options <strong>disabled.</strong>
Visit the Alert tab update your thresholds. <br>
To re-enable thresholds, the alert rule must be deleted from this panel.
</p>
<div ng-class="{'thresholds-form-disabled': ctrl.disabled}">
<div class="gf-form-inline" ng-repeat="threshold in ctrl.panel.thresholds">
<div class="gf-form">
<label class="gf-form-label">T{{$index+1}}</label>
</div>
<div class="gf-form">
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="threshold.op"
ng-options="f for f in ['gt', 'lt']" ng-change="ctrl.render()" ng-disabled="ctrl.disabled"></select>
</div>
<input type="number" ng-model="threshold.value" class="gf-form-input width-8"
ng-change="ctrl.render()" placeholder="value" ng-disabled="ctrl.disabled">
</div>
<div class="gf-form">
<label class="gf-form-label">Color</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="threshold.colorMode"
ng-options="f for f in ['custom', 'critical', 'warning', 'ok']" ng-change="ctrl.render()" ng-disabled="ctrl.disabled">
</select>
</div>
</div>
<gf-form-switch class="gf-form" label="Fill" checked="threshold.fill"
on-change="ctrl.render()" ng-disabled="ctrl.disabled"></gf-form-switch>
<div class="gf-form" ng-if="threshold.fill && threshold.colorMode === 'custom'">
<label class="gf-form-label">Fill color</label>
<span class="gf-form-label">
<color-picker color="threshold.fillColor" onChange="ctrl.onFillColorChange($index)"></color-picker>
</span>
</div>
<gf-form-switch class="gf-form" label="Line" checked="threshold.line"
on-change="ctrl.render()" ng-disabled="ctrl.disabled"></gf-form-switch>
<div class="gf-form" ng-if="threshold.line && threshold.colorMode === 'custom'">
<label class="gf-form-label">Line color</label>
<span class="gf-form-label">
<color-picker color="threshold.lineColor" onChange="ctrl.onLineColorChange($index)"></color-picker>
</span>
</div>
<div class="gf-form">
<label class="gf-form-label">Y-Axis</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="threshold.yaxis"
ng-init="threshold.yaxis = threshold.yaxis === 'left' || threshold.yaxis === 'right' ? threshold.yaxis : 'left'"
ng-options="f for f in ['left', 'right']" ng-change="ctrl.render()" ng-disabled="ctrl.disabled">
</select>
</div>
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" ng-click="ctrl.removeThreshold($index)" ng-disabled="ctrl.disabled">
<i class="fa fa-trash"></i>
</a>
</label>
</div>
</div>
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="ctrl.addThreshold()" ng-disabled="ctrl.disabled">
<i class="fa fa-plus"></i>&nbsp;Add Threshold
</button>
</div>
</div>
</div>
`;
coreModule.directive('graphThresholdForm', () => {
return {
restrict: 'E',
template: template,
templateUrl: 'public/app/plugins/panel/graph/thresholds_form.html',
controller: ThresholdFormCtrl,
bindToController: true,
controllerAs: 'ctrl',

View File

@@ -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;
}
}

View File

@@ -0,0 +1,64 @@
<div class="gf-form-group">
<h5>Time regions</h5>
<div class="gf-form-inline" ng-repeat="timeRegion in ctrl.panel.timeRegions">
<div class="gf-form">
<label class="gf-form-label">T{{$index+1}}</label>
</div>
<div class="gf-form">
<label class="gf-form-label">From</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-6" ng-model="timeRegion.fromDayOfWeek" ng-options="f.d as f.value for f in [{d: undefined, value: 'Any'}, {d:1, value: 'Mon'}, {d:2, value: 'Tue'}, {d:3, value: 'Wed'}, {d:4, value: 'Thu'}, {d:5, value: 'Fri'}, {d:6, value: 'Sat'}, {d:7, value: 'Sun'}]"
ng-change="ctrl.render()"></select>
</div>
<input type="text" ng-maxlength="5" ng-model="timeRegion.from" class="gf-form-input width-5" ng-change="ctrl.render()" placeholder="hh:mm">
<label class="gf-form-label">To</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input width-6" ng-model="timeRegion.toDayOfWeek" ng-options="f.d as f.value for f in [{d: undefined, value: 'Any'}, {d:1, value: 'Mon'}, {d:2, value: 'Tue'}, {d:3, value: 'Wed'}, {d:4, value: 'Thu'}, {d:5, value: 'Fri'}, {d:6, value: 'Sat'}, {d:7, value: 'Sun'}]"
ng-change="ctrl.render()"></select>
</div>
<input type="text" ng-maxlength="5" ng-model="timeRegion.to" class="gf-form-input width-5" ng-change="ctrl.render()" placeholder="hh:mm"
>
</div>
<div class="gf-form">
<label class="gf-form-label">Color</label>
<div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="timeRegion.colorMode" ng-options="f.key as f.value for f in ctrl.colorModes" ng-change="ctrl.render()">
</select>
</div>
</div>
<gf-form-switch class="gf-form" label="Fill" checked="timeRegion.fill" on-change="ctrl.render()"></gf-form-switch>
<div class="gf-form" ng-if="timeRegion.fill && timeRegion.colorMode === 'custom'">
<label class="gf-form-label">Fill color</label>
<span class="gf-form-label">
<color-picker color="timeRegion.fillColor" onChange="ctrl.onFillColorChange($index)"></color-picker>
</span>
</div>
<gf-form-switch class="gf-form" label="Line" checked="timeRegion.line" on-change="ctrl.render()"></gf-form-switch>
<div class="gf-form" ng-if="timeRegion.line && timeRegion.colorMode === 'custom'">
<label class="gf-form-label">Line color</label>
<span class="gf-form-label">
<color-picker color="timeRegion.lineColor" onChange="ctrl.onLineColorChange($index)"></color-picker>
</span>
</div>
<div class="gf-form">
<label class="gf-form-label">
<a class="pointer" ng-click="ctrl.removeTimeRegion($index)">
<i class="fa fa-trash"></i>
</a>
</label>
</div>
</div>
<div class="gf-form-button-row">
<button class="btn btn-inverse" ng-click="ctrl.addTimeRegion()">
<i class="fa fa-plus"></i>&nbsp;Add time region
</button>
</div>
</div>

View File

@@ -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: '=',
},
};
});