Commit 72a67b39 by Torkel Ödegaard

feat(thresholds): more work thresholds

parent 6881db87
...@@ -10,6 +10,49 @@ function getSeverityIconClass(alertState) { ...@@ -10,6 +10,49 @@ function getSeverityIconClass(alertState) {
return alertSeverityIconMap[alertState]; return alertSeverityIconMap[alertState];
} }
import {
QueryPartDef,
QueryPart,
} from 'app/core/components/query_part/query_part';
var alertQueryDef = new QueryPartDef({
type: 'query',
params: [
{name: "queryRefId", type: 'string', options: ['A', 'B', 'C', 'D', 'E', 'F']},
{name: "from", type: "string", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']},
{name: "to", type: "string", options: ['now']},
],
defaultParams: ['#A', '5m', 'now', 'avg']
});
var reducerAvgDef = new QueryPartDef({
type: 'avg',
params: [],
defaultParams: []
});
var conditionTypes = [
{text: 'Query', value: 'query'},
];
var evalFunctions = [
{text: 'IS ABOVE', value: 'gt'},
{text: 'IS BELOW', value: 'lt'},
{text: 'IS OUTSIDE RANGE', value: 'outside_range'},
{text: 'IS WITHIN RANGE', value: 'within_range'},
{text: 'HAS NO VALUE' , value: 'no_value'}
];
var severityLevels = [
{text: 'Critical', value: 'critical'},
{text: 'Warning', value: 'warning'},
];
export default { export default {
getSeverityIconClass, alertQueryDef: alertQueryDef,
reducerAvgDef: reducerAvgDef,
getSeverityIconClass: getSeverityIconClass,
conditionTypes: conditionTypes,
evalFunctions: evalFunctions,
severityLevels: severityLevels,
}; };
///<reference path="../../headers/common.d.ts" /> ///<reference path="../../headers/common.d.ts" />
import _ from 'lodash'; import _ from 'lodash';
import {ThresholdMapper} from './threshold_mapper';
import { import {QueryPart} from 'app/core/components/query_part/query_part';
QueryPartDef, import alertDef from './alert_def';
QueryPart,
} from 'app/core/components/query_part/query_part';
var alertQueryDef = new QueryPartDef({
type: 'query',
params: [
{name: "queryRefId", type: 'string', options: ['A', 'B', 'C', 'D', 'E', 'F']},
{name: "from", type: "string", options: ['1s', '10s', '1m', '5m', '10m', '15m', '1h']},
{name: "to", type: "string", options: ['now']},
],
defaultParams: ['#A', '5m', 'now', 'avg']
});
var reducerAvgDef = new QueryPartDef({
type: 'avg',
params: [],
defaultParams: []
});
export class AlertTabCtrl { export class AlertTabCtrl {
panel: any; panel: any;
...@@ -29,21 +11,11 @@ export class AlertTabCtrl { ...@@ -29,21 +11,11 @@ export class AlertTabCtrl {
testing: boolean; testing: boolean;
testResult: any; testResult: any;
subTabIndex: number; subTabIndex: number;
conditionTypes: any;
handlers = [{text: 'Grafana', value: 1}, {text: 'External', value: 0}];
conditionTypes = [
{text: 'Query', value: 'query'},
];
alert: any; alert: any;
conditionModels: any; conditionModels: any;
evalFunctions = [ evalFunctions: any;
{text: '>', value: '>'}, severityLevels: any;
{text: '<', value: '<'},
];
severityLevels = [
{text: 'Critical', value: 'critical'},
{text: 'Warning', value: 'warning'},
];
addNotificationSegment; addNotificationSegment;
notifications; notifications;
alertNotifications; alertNotifications;
...@@ -54,6 +26,9 @@ export class AlertTabCtrl { ...@@ -54,6 +26,9 @@ export class AlertTabCtrl {
this.panel = this.panelCtrl.panel; this.panel = this.panelCtrl.panel;
this.$scope.ctrl = this; this.$scope.ctrl = this;
this.subTabIndex = 0; this.subTabIndex = 0;
this.evalFunctions = alertDef.evalFunctions;
this.conditionTypes = alertDef.conditionTypes;
this.severityLevels = alertDef.severityLevels;
} }
$onInit() { $onInit() {
...@@ -101,6 +76,27 @@ export class AlertTabCtrl { ...@@ -101,6 +76,27 @@ export class AlertTabCtrl {
})); }));
} }
evaluatorTypeChanged(evaluator) {
// ensure params array is correct length
switch (evaluator.type) {
case "lt":
case "gt": {
evaluator.params = [evaluator.params[0]];
break;
}
case "within_range":
case "outside_range": {
evaluator.params = [evaluator.params[0], evaluator.params[1]];
break;
}
case "no_value": {
evaluator.params = [];
}
}
this.thresholdUpdated();
}
notificationAdded() { notificationAdded() {
var model = _.findWhere(this.notifications, {name: this.addNotificationSegment.value}); var model = _.findWhere(this.notifications, {name: this.addNotificationSegment.value});
if (!model) { if (!model) {
...@@ -146,45 +142,10 @@ export class AlertTabCtrl { ...@@ -146,45 +142,10 @@ export class AlertTabCtrl {
this.panelCtrl.editingThresholds = true; this.panelCtrl.editingThresholds = true;
} }
this.syncThresholds(); ThresholdMapper.alertToGraphThresholds(this.panel);
this.panelCtrl.render(); this.panelCtrl.render();
} }
syncThresholds() {
if (this.panel.type !== 'graph') {
return;
}
var threshold: any = {};
if (this.panel.thresholds && this.panel.thresholds.length > 0) {
threshold = this.panel.thresholds[0];
} else {
this.panel.thresholds = [threshold];
}
var updated = false;
for (var condition of this.conditionModels) {
if (condition.type === 'query') {
var value = condition.evaluator.params[0];
if (!_.isNumber(value)) {
continue;
}
if (value !== threshold.value) {
threshold.value = value;
updated = true;
}
if (condition.evaluator.type !== threshold.op) {
threshold.op = condition.evaluator.type;
updated = true;
}
}
}
return updated;
}
graphThresholdChanged(evt) { graphThresholdChanged(evt) {
for (var condition of this.alert.conditions) { for (var condition of this.alert.conditions) {
if (condition.type === 'query') { if (condition.type === 'query') {
...@@ -206,8 +167,8 @@ export class AlertTabCtrl { ...@@ -206,8 +167,8 @@ export class AlertTabCtrl {
buildConditionModel(source) { buildConditionModel(source) {
var cm: any = {source: source, type: source.type}; var cm: any = {source: source, type: source.type};
cm.queryPart = new QueryPart(source.query, alertQueryDef); cm.queryPart = new QueryPart(source.query, alertDef.alertQueryDef);
cm.reducerPart = new QueryPart({params: []}, reducerAvgDef); cm.reducerPart = new QueryPart({params: []}, alertDef.reducerAvgDef);
cm.evaluator = source.evaluator; cm.evaluator = source.evaluator;
return cm; return cm;
...@@ -240,7 +201,7 @@ export class AlertTabCtrl { ...@@ -240,7 +201,7 @@ export class AlertTabCtrl {
} }
thresholdUpdated() { thresholdUpdated() {
if (this.syncThresholds()) { if (ThresholdMapper.alertToGraphThresholds(this.panel)) {
this.panelCtrl.render(); this.panelCtrl.render();
} }
} }
......
...@@ -58,9 +58,10 @@ ...@@ -58,9 +58,10 @@
</query-part-editor> </query-part-editor>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label">Value</span> <metric-segment-model property="conditionModel.evaluator.type" options="ctrl.evalFunctions" custom="false" css-class="query-keyword" on-change="ctrl.evaluatorTypeChanged(conditionModel.evaluator)"></metric-segment-model>
<metric-segment-model property="conditionModel.evaluator.type" options="ctrl.evalFunctions" custom="false" css-class="query-segment-operator" on-change="ctrl.thresholdUpdated()"></metric-segment-model> <input class="gf-form-input max-width-7" type="number" ng-hide="conditionModel.evaluator.params.length === 0" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.thresholdUpdated()"></input>
<input class="gf-form-input max-width-7" type="number" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.thresholdUpdated()"></input> <label class="gf-form-label query-keyword" ng-show="conditionModel.evaluator.params.length === 2">TO</label>
<input class="gf-form-input max-width-7" type="number" ng-if="conditionModel.evaluator.params.length === 2" ng-model="conditionModel.evaluator.params[1]" ng-change="ctrl.thresholdUpdated()"></input>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label"> <label class="gf-form-label">
......
import {describe, beforeEach, it, sinon, expect} from 'test/lib/common';
import {ThresholdMapper} from '../threshold_mapper';
describe('ThresholdMapper', () => {
describe('with greater than evaluator', () => {
it('can mapp query conditions to thresholds', () => {
var panel: any = {
type: 'graph',
alert: {
conditions: [
{
type: 'query',
evaluator: { type: 'gt', params: [100], }
}
]
}
};
var updated = ThresholdMapper.alertToGraphThresholds(panel);
expect(updated).to.be(true);
expect(panel.thresholds[0].op).to.be('gt');
expect(panel.thresholds[0].value).to.be(100);
});
});
describe('with outside range evaluator', () => {
it('can mapp query conditions to thresholds', () => {
var panel: any = {
type: 'graph',
alert: {
conditions: [
{
type: 'query',
evaluator: { type: 'outside_range', params: [100, 200], }
}
]
}
};
var updated = ThresholdMapper.alertToGraphThresholds(panel);
expect(updated).to.be(true);
expect(panel.thresholds[0].op).to.be('lt');
expect(panel.thresholds[0].value).to.be(100);
expect(panel.thresholds[1].op).to.be('gt');
expect(panel.thresholds[1].value).to.be(200);
});
});
describe('with inside range evaluator', () => {
it('can mapp query conditions to thresholds', () => {
var panel: any = {
type: 'graph',
alert: {
conditions: [
{
type: 'query',
evaluator: { type: 'within_range', params: [100, 200], }
}
]
}
};
var updated = ThresholdMapper.alertToGraphThresholds(panel);
expect(updated).to.be(true);
expect(panel.thresholds[0].op).to.be('gt');
expect(panel.thresholds[0].value).to.be(100);
expect(panel.thresholds[1].op).to.be('lt');
expect(panel.thresholds[1].value).to.be(200);
});
});
});
export class ThresholdMapper {
static alertToGraphThresholds(panel) {
var alert = panel.alert;
if (panel.type !== 'graph') {
return false;
}
for (var i = 0; i < panel.alert.conditions.length; i++) {
let condition = panel.alert.conditions[i];
if (condition.type !== 'query') {
continue;
}
var evaluator = condition.evaluator;
var thresholds = panel.thresholds = [];
switch (evaluator.type) {
case "gt": {
let value = evaluator.params[0];
thresholds.push({value: value, op: 'gt'});
break;
}
case "lt": {
let value = evaluator.params[0];
thresholds.push({value: value, op: 'lt'});
break;
}
case "outside_range": {
let value1 = evaluator.params[0];
let value2 = evaluator.params[1];
if (value1 > value2) {
thresholds.push({value: value1, op: 'gt'});
thresholds.push({value: value2, op: 'lt'});
} else {
thresholds.push({value: value1, op: 'lt'});
thresholds.push({value: value2, op: 'gt'});
}
break;
}
case "within_range": {
let value1 = evaluator.params[0];
let value2 = evaluator.params[1];
if (value1 > value2) {
thresholds.push({value: value1, op: 'lt'});
thresholds.push({value: value2, op: 'gt'});
} else {
thresholds.push({value: value1, op: 'gt'});
thresholds.push({value: value2, op: 'lt'});
}
break;
}
}
}
for (var t of panel.thresholds) {
t.fill = true;
t.line = true;
t.colorMode = panel.alert.severity;
}
var updated = true;
return updated;
}
}
...@@ -343,12 +343,12 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) { ...@@ -343,12 +343,12 @@ function (angular, $, moment, _, kbn, GraphTooltip, thresholds) {
var limit; var limit;
switch(threshold.op) { switch(threshold.op) {
case '>': { case 'gt': {
limit = gtLimit; limit = gtLimit;
gtLimit = threshold.value; gtLimit = threshold.value;
break; break;
} }
case '<': { case 'lt': {
limit = ltLimit; limit = ltLimit;
ltLimit = threshold.value; ltLimit = threshold.value;
break; break;
......
...@@ -115,8 +115,8 @@ describe('grafanaGraph', function() { ...@@ -115,8 +115,8 @@ describe('grafanaGraph', function() {
graphScenario('grid thresholds 100, 200', function(ctx) { graphScenario('grid thresholds 100, 200', function(ctx) {
ctx.setup(function(ctrl) { ctx.setup(function(ctrl) {
ctrl.panel.thresholds = [ ctrl.panel.thresholds = [
{op: ">", value: 300, fillColor: 'red', lineColor: 'blue', fill: true, line: true}, {op: "gt", value: 300, fillColor: 'red', lineColor: 'blue', fill: true, line: true},
{op: ">", value: 200, fillColor: '#ed2e18', fill: true} {op: "gt", value: 200, fillColor: '#ed2e18', fill: true}
]; ];
}); });
......
...@@ -140,7 +140,7 @@ ...@@ -140,7 +140,7 @@
<div class="gf-form"> <div class="gf-form">
<div class="gf-form-select-wrapper"> <div class="gf-form-select-wrapper">
<select class="gf-form-input" ng-model="threshold.op" ng-options="f for f in ['>', '<']" ng-change="ctrl.render()"></select> <select class="gf-form-input" ng-model="threshold.op" ng-options="f for f in ['gt', 'lt']" ng-change="ctrl.render()"></select>
</div> </div>
<input type="number" ng-model="threshold.value" class="gf-form-input width-8" ng-change="ctrl.render()" placeholder="value"> <input type="number" ng-model="threshold.value" class="gf-form-input width-8" ng-change="ctrl.render()" placeholder="value">
</div> </div>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment