feat(alerting): thresholds rethink

This commit is contained in:
Torkel Ödegaard
2016-06-14 16:45:51 +02:00
parent dac8b35a1a
commit ed7a539ddb
9 changed files with 197 additions and 177 deletions
@@ -22,7 +22,7 @@ var alertQueryDef = new QueryPartDef({
export class AlertTabCtrl {
panel: any;
panelCtrl: any;
metricTargets = [{ refId: '- select query -' } ];
metricTargets;
handlers = [{text: 'Grafana', value: 1}, {text: 'External', value: 0}];
transforms = [
{
@@ -36,6 +36,7 @@ export class AlertTabCtrl {
];
aggregators = ['avg', 'sum', 'min', 'max', 'last'];
alert: any;
thresholds: any;
query: any;
queryParams: any;
transformDef: any;
@@ -45,24 +46,6 @@ export class AlertTabCtrl {
{text: '=', value: '='},
];
defaultValues = {
frequency: '60s',
notify: [],
enabled: false,
handler: 1,
warn: { op: '>', level: undefined },
critical: { op: '>', level: undefined },
query: {
refId: 'A',
from: '5m',
to: 'now',
},
transform: {
type: 'aggregation',
method: 'avg'
}
};
/** @ngInject */
constructor($scope, private $timeout) {
this.panelCtrl = $scope.ctrl;
@@ -70,7 +53,7 @@ export class AlertTabCtrl {
$scope.ctrl = this;
this.metricTargets = this.panel.targets.map(val => val);
this.initAlertModel();
this.initModel();
// set panel alert edit mode
$scope.$on("$destroy", () => {
@@ -79,32 +62,63 @@ export class AlertTabCtrl {
});
}
initAlertModel() {
if (!this.panel.alert) {
getThresholdWithDefaults(thresholds, type, copyFrom) {
var threshold = thresholds[type] || {};
var defaultValue = (copyFrom[type] || {}).value || undefined;
threshold.op = threshold.op || '>';
threshold.value = threshold.value || defaultValue;
return threshold;
}
initThresholdsOnlyMode() {
if (!this.panel.thresholds) {
return;
}
this.alert = this.panel.alert;
this.thresholds = this.panel.thresholds;
// set defaults
_.defaults(this.alert, this.defaultValues);
// set threshold defaults
this.thresholds.warn = this.getThresholdWithDefaults(this.thresholds, 'warn', {});
this.thresholds.crit = this.getThresholdWithDefaults(this.thresholds, 'crit', {});
var defaultName = (this.panelCtrl.dashboard.title + ' ' + this.panel.title + ' alert');
this.alert.name = this.alert.name || defaultName;
this.alert.description = this.alert.description || defaultName;
this.panelCtrl.editingAlert = true;
this.panelCtrl.render();
}
initModel() {
var alert = this.alert = this.panel.alert = this.panel.alert || {};
// set threshold defaults
alert.thresholds = alert.thresholds || {};
alert.thresholds.warn = this.getThresholdWithDefaults(alert.thresholds, 'warn', this.panel.thresholds);
alert.thresholds.crit = this.getThresholdWithDefaults(alert.thresholds, 'crit', this.panel.thresholds);
alert.frequency = alert.frequency || '60s';
alert.handler = alert.handler || 1;
alert.notifications = alert.notifications || [];
alert.query = alert.query || {};
alert.query.refId = alert.query.refId || 'A';
alert.query.from = alert.query.from || '5m';
alert.query.to = alert.query.to || 'now';
alert.transform = alert.transform || {};
alert.transform.type = alert.transform.type || 'aggregation';
alert.transform.method = alert.transform.method || 'avg';
var defaultName = this.panel.title + ' alert';
alert.name = alert.name || defaultName;
alert.description = alert.description || defaultName;
// great temp working model
this.queryParams = {
params: [
this.alert.query.refId,
this.alert.query.from,
this.alert.query.to
]
params: [alert.query.refId, alert.query.from, alert.query.to]
};
// init the query part components model
this.query = new QueryPart(this.queryParams, alertQueryDef);
this.transformDef = _.findWhere(this.transforms, {type: this.alert.transform.type});
this.transformDef = _.findWhere(this.transforms, {type: alert.transform.type});
this.panelCtrl.editingAlert = true;
this.panelCtrl.render();
@@ -135,22 +149,26 @@ export class AlertTabCtrl {
}
}
operatorChanged() {
this.panelCtrl.render();
}
delete() {
delete this.alert;
delete this.panel.alert;
this.panelCtrl.editingAlert = false;
this.panelCtrl.render();
// clear thresholds
if (this.panel.thresholds) {
this.panel.thresholds = {};
}
this.initModel();
}
enable() {
if (this.thresholds) {
delete this.thresholds;
this.panelCtrl.
}
this.panel.alert = {};
this.initAlertModel();
this.initModel();
}
levelsUpdated() {
thresholdsUpdated() {
this.panelCtrl.render();
}
}
+71 -68
View File
@@ -5,7 +5,7 @@ define([
'lodash',
'app/core/utils/kbn',
'./graph_tooltip',
'./alert_handle',
'./thresholds',
'jquery.flot',
'jquery.flot.selection',
'jquery.flot.time',
@@ -15,7 +15,7 @@ define([
'jquery.flot.crosshair',
'./jquery.flot.events',
],
function (angular, $, moment, _, kbn, GraphTooltip, AlertHandle) {
function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
'use strict';
var module = angular.module('grafana.directives');
@@ -23,7 +23,7 @@ function (angular, $, moment, _, kbn, GraphTooltip, AlertHandle) {
var panelWidthCache = {};
// systemjs export
var AlertHandleManager = AlertHandle.AlertHandleManager;
var ThresholdControls = thresholds.ThresholdControls;
module.directive('grafanaGraph', function($rootScope, timeSrv) {
return {
@@ -38,7 +38,7 @@ function (angular, $, moment, _, kbn, GraphTooltip, AlertHandle) {
var legendSideLastValue = null;
var rootScope = scope.$root;
var panelWidth = 0;
var alertHandles;
var thresholdControls;
rootScope.onAppEvent('setCrosshair', function(event, info) {
// do not need to to this if event is from this panel
@@ -167,8 +167,8 @@ function (angular, $, moment, _, kbn, GraphTooltip, AlertHandle) {
rightLabel[0].style.marginTop = (getLabelWidth(panel.yaxes[1].label, rightLabel) / 2) + 'px';
}
if (alertHandles) {
alertHandles.draw(plot);
if (thresholdControls) {
thresholdControls.draw(plot);
}
}
@@ -192,14 +192,14 @@ function (angular, $, moment, _, kbn, GraphTooltip, AlertHandle) {
// give space to alert editing
if (ctrl.editingAlert) {
if (!alertHandles) {
if (!thresholdControls) {
elem.css('margin-right', '220px');
alertHandles = new AlertHandleManager(ctrl);
thresholdControls = new ThresholdControls(ctrl);
}
} else if (alertHandles) {
} else if (thresholdControls) {
elem.css('margin-right', '0');
alertHandles.cleanUp();
alertHandles = null;
thresholdControls.cleanUp();
thresholdControls = null;
}
var stack = panel.stack ? true : null;
@@ -333,70 +333,73 @@ function (angular, $, moment, _, kbn, GraphTooltip, AlertHandle) {
}
function addGridThresholds(options, panel) {
var thresholds = panel.thresholds;
// use alert thresholds if there are any
if (panel.alert) {
var crit = panel.alert.critical;
var warn = panel.alert.warn;
var critEdge = Infinity;
var warnEdge = crit.level;
if (_.isNumber(crit.level)) {
if (crit.op === '<') {
critEdge = -Infinity;
}
// fill
options.grid.markings.push({
yaxis: {from: crit.level, to: critEdge},
color: 'rgba(234, 112, 112, 0.10)',
});
// line
options.grid.markings.push({
yaxis: {from: crit.level, to: crit.level},
color: '#ed2e18'
});
}
if (_.isNumber(warn.level)) {
// if (warn.op === '<') {
// }
// fill
options.grid.markings.push({
yaxis: {from: warn.level, to: warnEdge},
color: 'rgba(216, 200, 27, 0.10)',
});
// line
options.grid.markings.push({
yaxis: {from: warn.level, to: warn.level},
color: '#F79520'
});
}
return;
thresholds = panel.alert.thresholds;
}
if (_.isNumber(panel.grid.threshold1)) {
var limit1 = panel.grid.thresholdLine ? panel.grid.threshold1 : (panel.grid.threshold2 || null);
var crit = thresholds.crit;
var warn = thresholds.warn;
var critEdge = Infinity;
var warnEdge = crit.value;
if (_.isNumber(crit.value)) {
if (crit.op === '<') {
critEdge = -Infinity;
}
// fill
options.grid.markings.push({
yaxis: { from: panel.grid.threshold1, to: limit1 },
color: panel.grid.threshold1Color
yaxis: {from: crit.value, to: critEdge},
color: 'rgba(234, 112, 112, 0.10)',
});
if (_.isNumber(panel.grid.threshold2)) {
var limit2;
if (panel.grid.thresholdLine) {
limit2 = panel.grid.threshold2;
} else {
limit2 = panel.grid.threshold1 > panel.grid.threshold2 ? -Infinity : +Infinity;
}
options.grid.markings.push({
yaxis: { from: panel.grid.threshold2, to: limit2 },
color: panel.grid.threshold2Color
});
}
// line
options.grid.markings.push({
yaxis: {from: crit.value, to: crit.value},
color: '#ed2e18'
});
}
if (_.isNumber(warn.value)) {
// if (warn.op === '<') {
// }
// fill
options.grid.markings.push({
yaxis: {from: warn.value, to: warnEdge},
color: 'rgba(216, 200, 27, 0.10)',
});
// line
options.grid.markings.push({
yaxis: {from: warn.value, to: warn.value},
color: '#F79520'
});
}
// if (_.isNumber(panel.grid.threshold1)) {
// var limit1 = panel.grid.thresholdLine ? panel.grid.threshold1 : (panel.grid.threshold2 || null);
// options.grid.markings.push({
// yaxis: { from: panel.grid.threshold1, to: limit1 },
// color: panel.grid.threshold1Color
// });
//
// if (_.isNumber(panel.grid.threshold2)) {
// var limit2;
// if (panel.grid.thresholdLine) {
// limit2 = panel.grid.threshold2;
// } else {
// limit2 = panel.grid.threshold1 > panel.grid.threshold2 ? -Infinity : +Infinity;
// }
// options.grid.markings.push({
// yaxis: { from: panel.grid.threshold2, to: limit2 },
// color: panel.grid.threshold2Color
// });
// }
// }
}
function addAnnotations(options) {
+5 -6
View File
@@ -54,11 +54,9 @@ class GraphCtrl extends MetricsPanelCtrl {
xaxis: {
show: true
},
grid : {
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
threshold2Color: 'rgba(234, 112, 112, 0.22)'
thresholds: {
warn: {op: '>', level: undefined},
crit: {op: '>', level: undefined},
},
// show/hide lines
lines : true,
@@ -115,7 +113,7 @@ class GraphCtrl extends MetricsPanelCtrl {
_.defaults(this.panel, this.panelDefaults);
_.defaults(this.panel.tooltip, this.panelDefaults.tooltip);
_.defaults(this.panel.grid, this.panelDefaults.grid);
_.defaults(this.panel.thresholds, this.panelDefaults.thresholds);
_.defaults(this.panel.legend, this.panelDefaults.legend);
this.colors = $scope.$root.colors;
@@ -132,6 +130,7 @@ class GraphCtrl extends MetricsPanelCtrl {
this.addEditorTab('Axes', 'public/app/plugins/panel/graph/tab_axes.html', 2);
this.addEditorTab('Legend', 'public/app/plugins/panel/graph/tab_legend.html', 3);
this.addEditorTab('Display', 'public/app/plugins/panel/graph/tab_display.html', 4);
if (config.alertingEnabled) {
this.addEditorTab('Alerting', graphAlertEditor, 5);
}
@@ -1,5 +1,29 @@
<div ng-if="ctrl.panel.alert">
<div ng-if="ctrl.thresholds">
<div class="gf-form-group">
<h5 class="section-heading">Visual Thresholds</h5>
<div class="gf-form-inline">
<div class="gf-form">
<span class="gf-form-label">
<i class="icon-gf icon-gf-warn alert-icon-critical"></i>
Critcal if
</span>
<metric-segment-model property="ctrl.thresholds.crit.op" options="ctrl.operatorList" custom="false" css-class="query-segment-operator" on-change="ctrl.thresholdsUpdated()"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.thresholds.crit.value" ng-change="ctrl.thresholdsUpdated()"></input>
</div>
<div class="gf-form">
<span class="gf-form-label">
<i class="icon-gf icon-gf-warn alert-icon-warn"></i>
Warn if
</span>
<metric-segment-model property="ctrl.thresholds.warn.op" options="ctrl.operatorList" custom="false" css-class="query-segment-operator" on-change="ctrl.thresholdsUpdated()"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.thresholds.warn.value" ng-change="ctrl.thresholdsUpdated()"></input>
</div>
</div>
</div>
</div>
<div ng-if="ctrl.alert">
<div class="editor-row">
<div class="gf-form-group section" >
<h5 class="section-heading">Alert Query</h5>
@@ -39,23 +63,23 @@
</div>
<div class="gf-form-group section">
<h5 class="section-heading">Levels</h5>
<h5 class="section-heading">Thresholds</h5>
<div class="gf-form-inline">
<div class="gf-form">
<div class="gf-form">
<span class="gf-form-label">
<i class="icon-gf icon-gf-warn alert-icon-critical"></i>
Critcal if
</span>
<metric-segment-model property="ctrl.alert.critical.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator" on-change="ctrl.operatorChanged()"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.critical.level" ng-change="ctrl.levelsUpdated()"></input>
<metric-segment-model property="ctrl.alert.thresholds.crit.op" options="ctrl.operatorList" custom="false" css-class="query-segment-operator" on-change="ctrl.thresholdsUpdated()"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.thresholds.crit.value" ng-change="ctrl.thresholdsUpdated()"></input>
</div>
<div class="gf-form">
<div class="gf-form">
<span class="gf-form-label">
<i class="icon-gf icon-gf-warn alert-icon-warn"></i>
Warn if
</span>
<metric-segment-model property="ctrl.alert.warn.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator" on-change="ctrl.operatorChanged()"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.warn.level" ng-change="ctrl.levelsUpdated()"></input>
<metric-segment-model property="ctrl.alert.thresholds.warn.op" options="ctrl.operatorList" custom="false" css-class="query-segment-operator" on-change="ctrl.thresholdsUpdated()"></metric-segment-model>
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.alert.thresholds.warn.value" ng-change="ctrl.thresholdsUpdated()"></input>
</div>
</div>
</div>
@@ -111,10 +135,10 @@
<div class="editor-row">
<div class="gf-form-button-row">
<button class="btn btn-danger" ng-click="ctrl.delete()" ng-show="ctrl.panel.alert">Delete</button>
<button class="btn btn-inverse" ng-click="ctrl.enable()" ng-hide="ctrl.panel.alert">
<button class="btn btn-danger" ng-click="ctrl.delete()" ng-show="ctrl.alert">Delete</button>
<button class="btn btn-inverse" ng-click="ctrl.enable()" ng-hide="ctrl.alert">
<i class="icon-gf icon-gf-alert"></i>
Add Alert
Create Alert
</button>
</div>
</div>
@@ -42,33 +42,4 @@
<gf-form-switch class="gf-form" label="Show" label-class="width-5" checked="ctrl.panel.xaxis.show" on-change="ctrl.render()"></gf-form-switch>
</div>
<div class="section gf-form-group">
<h5 class="section-heading">Thresholds</h5>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-6">Level 1</label>
<input type="number" class="gf-form-input max-width-5" ng-model="ctrl.panel.grid.threshold1" ng-change="ctrl.render()" ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label width-5">Color</label>
<div class="gf-form-label">
<spectrum-picker ng-model="ctrl.panel.grid.threshold1Color" ng-change="ctrl.render()" ></spectrum-picker>
</div>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form">
<label class="gf-form-label width-6">Level 2</label>
<input type="number" class="gf-form-input max-width-5" ng-model="ctrl.panel.grid.threshold2" ng-change="ctrl.render()" ng-model-onblur>
</div>
<div class="gf-form">
<label class="gf-form-label width-5">Color</label>
<div class="gf-form-label">
<spectrum-picker ng-model="ctrl.panel.grid.threshold2Color" ng-change="ctrl.render()" ></spectrum-picker>
</div>
</div>
</div>
<gf-form-switch class="gf-form" label="Line Mode" label-class="width-6" checked="ctrl.panel.grid.thresholdLine" on-change="ctrl.render()"></gf-form-switch>
</div>
</div>
@@ -4,14 +4,14 @@ import 'jquery.flot';
import $ from 'jquery';
import _ from 'lodash';
export class AlertHandleManager {
export class ThresholdControls {
plot: any;
placeholder: any;
height: any;
alert: any;
thresholds: any;
constructor(private panelCtrl) {
this.alert = panelCtrl.panel.alert;
this.thresholds = this.panelCtrl.thresholds;
}
getHandleInnerHtml(type, op, value) {
@@ -36,7 +36,7 @@ export class AlertHandleManager {
`;
}
setupDragging(handleElem, levelModel) {
setupDragging(handleElem, threshold) {
var isMoving = false;
var lastY = null;
var posTop;
@@ -57,11 +57,11 @@ export class AlertHandleManager {
function stopped() {
isMoving = false;
// calculate graph level
var graphLevel = plot.c2p({left: 0, top: posTop}).y;
graphLevel = parseInt(graphLevel.toFixed(0));
levelModel.level = graphLevel;
var graphValue = plot.c2p({left: 0, top: posTop}).y;
graphValue = parseInt(graphValue.toFixed(0));
threshold.value = graphValue;
var levelCanvasPos = plot.p2c({x: 0, y: graphLevel});
var valueCanvasPos = plot.p2c({x: 0, y: graphValue});
handleElem.off("mousemove", dragging);
handleElem.off("mouseup", dragging);
@@ -90,28 +90,28 @@ export class AlertHandleManager {
renderHandle(type, model, defaultHandleTopPos) {
var handleElem = this.placeholder.find(`.alert-handle-wrapper--${type}`);
var level = model.level;
var levelStr = level;
var value = model.value;
var valueStr = value;
var handleTopPos = 0;
// handle no value
if (!_.isNumber(level)) {
levelStr = '';
if (!_.isNumber(value)) {
valueStr = '';
handleTopPos = defaultHandleTopPos;
} else {
var levelCanvasPos = this.plot.p2c({x: 0, y: level});
handleTopPos = Math.min(Math.max(levelCanvasPos.top, 0), this.height) - 6;
var valueCanvasPos = this.plot.p2c({x: 0, y: value});
handleTopPos = Math.min(Math.max(valueCanvasPos.top, 0), this.height) - 6;
}
if (handleElem.length === 0) {
handleElem = $(this.getFullHandleHtml(type, model.op, levelStr));
handleElem = $(this.getFullHandleHtml(type, model.op, valueStr));
this.placeholder.append(handleElem);
this.setupDragging(handleElem, model);
} else {
handleElem.html(this.getHandleInnerHtml(type, model.op, levelStr));
handleElem.html(this.getHandleInnerHtml(type, model.op, valueStr));
}
handleElem.toggleClass('alert-handle-wrapper--no-value', levelStr === '');
handleElem.toggleClass('alert-handle-wrapper--no-value', valueStr === '');
handleElem.css({top: handleTopPos});
}
@@ -120,9 +120,10 @@ export class AlertHandleManager {
this.placeholder = plot.getPlaceholder();
this.height = plot.height();
this.renderHandle('critical', this.alert.critical, 10);
this.renderHandle('warn', this.alert.warn, this.height-30);
this.renderHandle('crit', this.thresholds.crit, 10);
this.renderHandle('warn', this.thresholds.warn, this.height-30);
}
debugger;
}
+4 -1
View File
@@ -31,7 +31,7 @@
.icon-gf-raintank_wordmark:before {
content: "\e600";
}
.icon-gf-raintank_icn:before {
.micon-gf-raintank_icn:before {
content: "\e601";
}
.icon-gf-raintank_r-icn:before {
@@ -88,6 +88,9 @@
.icon-gf-critical:before {
content: "\e610";
}
.icon-gf-crit:before {
content: "\e610";
}
.icon-gf-online:before {
content: "\e611";
}
+1
View File
@@ -15,6 +15,7 @@
color: $warn;
}
.alert-icon-crit,
.alert-icon-critical {
color: $critical;
}
+1 -1
View File
@@ -363,7 +363,7 @@
}
}
&--critical {
&--crit{
right: -105px;
width: 123px;