浏览代码

feat(thresholds): more work thresholds

Torkel Ödegaard 9 年之前
父节点
当前提交
72a67b39f1

+ 44 - 1
public/app/features/alerting/alert_def.ts

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

+ 34 - 73
public/app/features/alerting/alert_tab_ctrl.ts

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

+ 4 - 3
public/app/features/alerting/partials/alert_tab.html

@@ -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 - 0
public/app/features/alerting/specs/threshold_mapper_specs.ts

@@ -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 - 0
public/app/features/alerting/threshold_mapper.ts

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

+ 2 - 2
public/app/plugins/panel/graph/graph.js

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

+ 2 - 2
public/app/plugins/panel/graph/specs/graph_specs.ts

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

+ 1 - 1
public/app/plugins/panel/graph/tab_display.html

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