Просмотр исходного кода

feat(alerting): working on alerting conditions model

Torkel Ödegaard 9 лет назад
Родитель
Сommit
20fcffb71e

+ 33 - 46
pkg/services/alerting/alert_rule.go

@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"regexp"
 	"strconv"
-	"strings"
 
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/services/alerting/transformers"
@@ -31,6 +30,19 @@ type AlertRule struct {
 	NotificationGroups []int64
 }
 
+type AlertRule2 struct {
+	Id            int64
+	OrgId         int64
+	DashboardId   int64
+	PanelId       int64
+	Frequency     int64
+	Name          string
+	Description   string
+	State         string
+	Conditions    []AlertCondition
+	Notifications []int64
+}
+
 var (
 	ValueFormatRegex = regexp.MustCompile("^\\d+")
 	UnitFormatRegex  = regexp.MustCompile("\\w{1}$")
@@ -56,7 +68,11 @@ func getTimeDurationStringToSeconds(str string) int64 {
 }
 
 func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
-	model := &AlertRule{}
+	return nil, nil
+}
+
+func NewAlertRuleFromDBModel2(ruleDef *m.Alert) (*AlertRule2, error) {
+	model := &AlertRule2{}
 	model.Id = ruleDef.Id
 	model.OrgId = ruleDef.OrgId
 	model.Name = ruleDef.Name
@@ -64,55 +80,26 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
 	model.State = ruleDef.State
 	model.Frequency = ruleDef.Frequency
 
-	ngs := ruleDef.Settings.Get("notificationGroups").MustString()
-	var ids []int64
-	for _, v := range strings.Split(ngs, ",") {
-		id, err := strconv.Atoi(v)
-		if err == nil {
-			ids = append(ids, int64(id))
+	for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
+		if id, ok := v.(int64); ok {
+			model.Notifications = append(model.Notifications, int64(id))
 		}
 	}
 
-	model.NotificationGroups = ids
-
-	critical := ruleDef.Settings.Get("crit")
-	model.Critical = Level{
-		Operator: critical.Get("op").MustString(),
-		Value:    critical.Get("value").MustFloat64(),
-	}
-
-	warning := ruleDef.Settings.Get("warn")
-	model.Warning = Level{
-		Operator: warning.Get("op").MustString(),
-		Value:    warning.Get("value").MustFloat64(),
-	}
-
-	model.Transform = ruleDef.Settings.Get("transform").Get("type").MustString()
-	if model.Transform == "" {
-		return nil, fmt.Errorf("missing transform")
-	}
-
-	model.TransformParams = *ruleDef.Settings.Get("transform")
-
-	if model.Transform == "aggregation" {
-		method := ruleDef.Settings.Get("transform").Get("method").MustString()
-		model.Transformer = transformers.NewAggregationTransformer(method)
-	}
-
-	query := ruleDef.Settings.Get("query")
-	model.Query = AlertQuery{
-		Query:        query.Get("query").MustString(),
-		DatasourceId: query.Get("datasourceId").MustInt64(),
-		From:         query.Get("from").MustString(),
-		To:           query.Get("to").MustString(),
-	}
-
-	if model.Query.Query == "" {
-		return nil, fmt.Errorf("missing query.query")
+	for _, condition := range ruleDef.Settings.Get("conditions").MustArray() {
+		conditionModel := simplejson.NewFromAny(condition)
+		switch conditionModel.Get("type").MustString() {
+		case "query":
+			queryCondition, err := NewQueryCondition(conditionModel)
+			if err != nil {
+				return nil, err
+			}
+			model.Conditions = append(model.Conditions, queryCondition)
+		}
 	}
 
-	if model.Query.DatasourceId == 0 {
-		return nil, fmt.Errorf("missing query.datasourceId")
+	if len(model.Conditions) == 0 {
+		return nil, fmt.Errorf("Alert is missing conditions")
 	}
 
 	return model, nil

+ 14 - 25
pkg/services/alerting/alert_rule_test.go

@@ -38,26 +38,19 @@ func TestAlertRuleModel(t *testing.T) {
 				"description": "desc2",
 				"handler": 0,
 				"enabled": true,
-				"crit": {
-					"value": 20,
-					"op": ">"
-				},
-				"warn": {
-					"value": 10,
-					"op": ">"
-				},
 				"frequency": "60s",
-				"query": {
-					"from": "5m",
-					"refId": "A",
-					"to": "now",
-					"query": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)",
-					"datasourceId": 1
-				},
-				"transform": {
-					"type": "avg",
-					"name": "aggregation"
-				}
+        "conditions": [
+          {
+            "type": "query",
+            "query":  {
+              "params": ["A", "5m", "now"],
+              "datasourceId": 1,
+              "query":  "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"
+            },
+            "reducer": {"type": "avg", "params": []},
+            "evaluator": {"type": ">", "params": [100]}
+          }
+        ]
 			}
 			`
 
@@ -72,15 +65,11 @@ func TestAlertRuleModel(t *testing.T) {
 
 				Settings: alertJSON,
 			}
-			alertRule, err := NewAlertRuleFromDBModel(alert)
 
+			alertRule, err := NewAlertRuleFromDBModel2(alert)
 			So(err, ShouldBeNil)
 
-			So(alertRule.Warning.Operator, ShouldEqual, ">")
-			So(alertRule.Warning.Value, ShouldEqual, 10)
-
-			So(alertRule.Critical.Operator, ShouldEqual, ">")
-			So(alertRule.Critical.Value, ShouldEqual, 20)
+			So(alertRule.Conditions, ShouldHaveLength, 1)
 		})
 	})
 }

+ 32 - 0
pkg/services/alerting/conditions.go

@@ -0,0 +1,32 @@
+package alerting
+
+import "github.com/grafana/grafana/pkg/components/simplejson"
+
+type AlertCondition interface {
+	Eval()
+}
+
+type QueryCondition struct {
+	Query     AlertQuery
+	Reducer   AlertReducerModel
+	Evaluator AlertEvaluatorModel
+}
+
+func (c *QueryCondition) Eval() {
+}
+
+type AlertReducerModel struct {
+	Type   string
+	Params []interface{}
+}
+
+type AlertEvaluatorModel struct {
+	Type   string
+	Params []interface{}
+}
+
+func NewQueryCondition(model *simplejson.Json) (*QueryCondition, error) {
+	condition := QueryCondition{}
+
+	return &condition, nil
+}

+ 4 - 4
pkg/services/alerting/result_handler_test.go

@@ -38,7 +38,7 @@ func TestAlertResultHandler(t *testing.T) {
 
 			Convey("alert state have changed", func() {
 				mockAlertState = &m.AlertState{
-					NewState: alertstates.Critical,
+					State: alertstates.Critical,
 				}
 				mockResult.State = alertstates.Ok
 				So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue)
@@ -47,11 +47,11 @@ func TestAlertResultHandler(t *testing.T) {
 			Convey("last alert state was 15min ago", func() {
 				now := time.Now()
 				mockAlertState = &m.AlertState{
-					NewState: alertstates.Critical,
-					Created:  now.Add(time.Minute * -30),
+					State:   alertstates.Critical,
+					Created: now.Add(time.Minute * -30),
 				}
 				mockResult.State = alertstates.Critical
-				mockResult.ExeuctionTime = time.Now()
+				mockResult.StartTime = time.Now()
 				So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue)
 			})
 		})

+ 14 - 29
public/app/plugins/panel/graph/alert_tab_ctrl.ts

@@ -38,11 +38,15 @@ export class AlertTabCtrl {
   ];
   alert: any;
   conditionModels: any;
-  levelOpList = [
+  evalFunctions = [
     {text: '>', value: '>'},
     {text: '<', value: '<'},
-    {text: '=', value: '='},
   ];
+  severityLevels = [
+    {text: 'Critical', value: 'critical'},
+    {text: 'Warning', value: 'warning'},
+  ];
+
 
   /** @ngInject */
   constructor($scope, private $timeout) {
@@ -60,21 +64,15 @@ export class AlertTabCtrl {
     });
   }
 
-  getThresholdWithDefaults(threshold) {
-    threshold = threshold || {};
-    threshold.op = threshold.op || '>';
-    threshold.value = threshold.value || undefined;
-    return threshold;
-  }
-
   initModel() {
     var alert = this.alert = this.panel.alert = this.panel.alert || {};
 
-    alert.conditions = alert.conditions || [];
+    alert.conditions = [];
     if (alert.conditions.length === 0) {
       alert.conditions.push(this.buildDefaultCondition());
     }
 
+    alert.severity = alert.severity || 'critical';
     alert.frequency = alert.frequency || '60s';
     alert.handler = alert.handler || 1;
     alert.notifications = alert.notifications || [];
@@ -95,32 +93,23 @@ export class AlertTabCtrl {
   buildDefaultCondition() {
     return {
       type: 'query',
-      refId: 'A',
-      from: '5m',
-      to: 'now',
-      reducer: 'avg',
-      reducerParams: [],
-      warn: this.getThresholdWithDefaults({}),
-      crit: this.getThresholdWithDefaults({}),
+      query: {params: ['A', '5m', 'now']},
+      reducer: {type: 'avg', params: []},
+      evaluator: {type: '>', params: [null]},
     };
   }
 
   buildConditionModel(source) {
     var cm: any = {source: source, type: source.type};
 
-    var queryPartModel = {
-      params: [source.refId, source.from, source.to]
-    };
-
-    cm.queryPart = new QueryPart(queryPartModel, alertQueryDef);
+    cm.queryPart = new QueryPart(source.query, alertQueryDef);
     cm.reducerPart = new QueryPart({params: []}, reducerAvgDef);
+    cm.evaluator = source.evaluator;
+
     return cm;
   }
 
   queryPartUpdated(conditionModel) {
-    conditionModel.source.refId = conditionModel.queryPart.params[0];
-    conditionModel.source.from = conditionModel.queryPart.params[1];
-    conditionModel.source.to = conditionModel.queryPart.params[2];
   }
 
   addCondition(type) {
@@ -138,10 +127,6 @@ export class AlertTabCtrl {
 
   delete() {
     this.alert.enabled = false;
-    this.alert.warn.value = undefined;
-    this.alert.crit.value = undefined;
-
-    // reset model but keep thresholds instance
     this.initModel();
   }
 

+ 28 - 27
public/app/plugins/panel/graph/partials/tab_alerting.html

@@ -27,31 +27,42 @@
   <div class="gf-form-group">
     <h5 class="section-heading">Alert Rule</h5>
     <div class="gf-form-inline">
-      <div class="gf-form">
+      <div class="gf-form max-width-30">
         <span class="gf-form-label width-8">Name</span>
         <input type="text" class="gf-form-input width-22" ng-model="ctrl.alert.name">
       </div>
-      <div class="gf-form">
-        <span class="gf-form-label">Handler</span>
-        <div class="gf-form-select-wrapper">
-          <select   class="gf-form-input"
-                    ng-model="ctrl.alert.handler"
-                    ng-options="f.value as f.text for f in ctrl.handlers">
-          </select>
-        </div>
-      </div>
+      <!-- <div class="gf&#45;form"> -->
+      <!--   <span class="gf&#45;form&#45;label width&#45;6">Handler</span> -->
+      <!--   <div class="gf&#45;form&#45;select&#45;wrapper"> -->
+      <!--     <select   class="gf&#45;form&#45;input" -->
+      <!--               ng&#45;model="ctrl.alert.handler" -->
+      <!--               ng&#45;options="f.value as f.text for f in ctrl.handlers"> -->
+      <!--     </select> -->
+      <!--   </div> -->
+      <!-- </div> -->
       <div class="gf-form">
         <span class="gf-form-label width-8">Evaluate every</span>
         <input class="gf-form-input max-width-7" type="text" ng-model="ctrl.alert.frequency"></input>
       </div>
     </div>
-    <div class="gf-form">
-      <span class="gf-form-label width-8">Notifications</span>
-      <input class="gf-form-input max-width-22" type="text" ng-model="ctrl.alert.notify"></input>
+    <div class="gf-form-inline">
+      <div class="gf-form max-width-30">
+        <span class="gf-form-label width-8">Notifications</span>
+        <input class="gf-form-input max-width-22" type="text" ng-model="ctrl.alert.notifications"></input>
+      </div>
       <!--
         <bootstrap-tagsinput ng-model="ctrl.alert.notify" tagclass="label label-tag" placeholder="add tags">
         </bootstrap-tagsinput>
       -->
+      <div class="gf-form">
+        <span class="gf-form-label width-8">Severity</span>
+        <div class="gf-form-select-wrapper">
+          <select   class="gf-form-input"
+                    ng-model="ctrl.alert.severity"
+                    ng-options="f.value as f.text for f in ctrl.severityLevels">
+          </select>
+        </div>
+      </div>
     </div>
   </div>
 
@@ -59,7 +70,7 @@
     <h5 class="section-heading">Conditions</h5>
     <div class="gf-form-inline" ng-repeat="conditionModel in ctrl.conditionModels">
       <div class="gf-form">
-        <span class="gf-form-label">{{$index+1}}</span>
+        <span class="gf-form-label query-keyword">AND</span>
       </div>
       <div class="gf-form">
         <query-part-editor
@@ -77,21 +88,11 @@
         </query-part-editor>
       </div>
       <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.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.crit.value" ng-change="ctrl.thresholdsUpdated()"></input>
+        <span class="gf-form-label">When 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.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.alert.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.warn.value" ng-change="ctrl.thresholdsUpdated()"></input>
-
         <label class="gf-form-label">
           <a class="pointer" tabindex="1" ng-click="ctrl.removeCondition($index)">
             <i class="fa fa-trash"></i>