mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(thresholds): more work thresholds
This commit is contained in:
parent
6881db87bb
commit
72a67b39f1
@ -10,6 +10,49 @@ function getSeverityIconClass(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 {
|
||||
getSeverityIconClass,
|
||||
alertQueryDef: alertQueryDef,
|
||||
reducerAvgDef: reducerAvgDef,
|
||||
getSeverityIconClass: getSeverityIconClass,
|
||||
conditionTypes: conditionTypes,
|
||||
evalFunctions: evalFunctions,
|
||||
severityLevels: severityLevels,
|
||||
};
|
||||
|
@ -1,27 +1,9 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
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: []
|
||||
});
|
||||
import {ThresholdMapper} from './threshold_mapper';
|
||||
import {QueryPart} from 'app/core/components/query_part/query_part';
|
||||
import alertDef from './alert_def';
|
||||
|
||||
export class AlertTabCtrl {
|
||||
panel: any;
|
||||
@ -29,21 +11,11 @@ export class AlertTabCtrl {
|
||||
testing: boolean;
|
||||
testResult: any;
|
||||
subTabIndex: number;
|
||||
|
||||
handlers = [{text: 'Grafana', value: 1}, {text: 'External', value: 0}];
|
||||
conditionTypes = [
|
||||
{text: 'Query', value: 'query'},
|
||||
];
|
||||
conditionTypes: any;
|
||||
alert: any;
|
||||
conditionModels: any;
|
||||
evalFunctions = [
|
||||
{text: '>', value: '>'},
|
||||
{text: '<', value: '<'},
|
||||
];
|
||||
severityLevels = [
|
||||
{text: 'Critical', value: 'critical'},
|
||||
{text: 'Warning', value: 'warning'},
|
||||
];
|
||||
evalFunctions: any;
|
||||
severityLevels: any;
|
||||
addNotificationSegment;
|
||||
notifications;
|
||||
alertNotifications;
|
||||
@ -54,6 +26,9 @@ export class AlertTabCtrl {
|
||||
this.panel = this.panelCtrl.panel;
|
||||
this.$scope.ctrl = this;
|
||||
this.subTabIndex = 0;
|
||||
this.evalFunctions = alertDef.evalFunctions;
|
||||
this.conditionTypes = alertDef.conditionTypes;
|
||||
this.severityLevels = alertDef.severityLevels;
|
||||
}
|
||||
|
||||
$onInit() {
|
||||
@ -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() {
|
||||
var model = _.findWhere(this.notifications, {name: this.addNotificationSegment.value});
|
||||
if (!model) {
|
||||
@ -146,45 +142,10 @@ export class AlertTabCtrl {
|
||||
this.panelCtrl.editingThresholds = true;
|
||||
}
|
||||
|
||||
this.syncThresholds();
|
||||
ThresholdMapper.alertToGraphThresholds(this.panel);
|
||||
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) {
|
||||
for (var condition of this.alert.conditions) {
|
||||
if (condition.type === 'query') {
|
||||
@ -206,8 +167,8 @@ export class AlertTabCtrl {
|
||||
buildConditionModel(source) {
|
||||
var cm: any = {source: source, type: source.type};
|
||||
|
||||
cm.queryPart = new QueryPart(source.query, alertQueryDef);
|
||||
cm.reducerPart = new QueryPart({params: []}, reducerAvgDef);
|
||||
cm.queryPart = new QueryPart(source.query, alertDef.alertQueryDef);
|
||||
cm.reducerPart = new QueryPart({params: []}, alertDef.reducerAvgDef);
|
||||
cm.evaluator = source.evaluator;
|
||||
|
||||
return cm;
|
||||
@ -240,7 +201,7 @@ export class AlertTabCtrl {
|
||||
}
|
||||
|
||||
thresholdUpdated() {
|
||||
if (this.syncThresholds()) {
|
||||
if (ThresholdMapper.alertToGraphThresholds(this.panel)) {
|
||||
this.panelCtrl.render();
|
||||
}
|
||||
}
|
||||
|
@ -58,9 +58,10 @@
|
||||
</query-part-editor>
|
||||
</div>
|
||||
<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-segment-operator" on-change="ctrl.thresholdUpdated()"></metric-segment-model>
|
||||
<input class="gf-form-input max-width-7" type="number" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.thresholdUpdated()"></input>
|
||||
<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>
|
||||
<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>
|
||||
<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 class="gf-form">
|
||||
<label class="gf-form-label">
|
||||
|
78
public/app/features/alerting/specs/threshold_mapper_specs.ts
Normal file
78
public/app/features/alerting/specs/threshold_mapper_specs.ts
Normal file
@ -0,0 +1,78 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
72
public/app/features/alerting/threshold_mapper.ts
Normal file
72
public/app/features/alerting/threshold_mapper.ts
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
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) {
|
||||
|
||||
var limit;
|
||||
switch(threshold.op) {
|
||||
case '>': {
|
||||
case 'gt': {
|
||||
limit = gtLimit;
|
||||
gtLimit = threshold.value;
|
||||
break;
|
||||
}
|
||||
case '<': {
|
||||
case 'lt': {
|
||||
limit = ltLimit;
|
||||
ltLimit = threshold.value;
|
||||
break;
|
||||
|
@ -115,8 +115,8 @@ describe('grafanaGraph', function() {
|
||||
graphScenario('grid thresholds 100, 200', function(ctx) {
|
||||
ctx.setup(function(ctrl) {
|
||||
ctrl.panel.thresholds = [
|
||||
{op: ">", value: 300, fillColor: 'red', lineColor: 'blue', fill: true, line: true},
|
||||
{op: ">", value: 200, fillColor: '#ed2e18', fill: true}
|
||||
{op: "gt", value: 300, fillColor: 'red', lineColor: 'blue', fill: true, line: true},
|
||||
{op: "gt", value: 200, fillColor: '#ed2e18', fill: true}
|
||||
];
|
||||
});
|
||||
|
||||
|
@ -140,7 +140,7 @@
|
||||
|
||||
<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 ['>', '<']" 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>
|
||||
<input type="number" ng-model="threshold.value" class="gf-form-input width-8" ng-change="ctrl.render()" placeholder="value">
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user