Commit 20fcffb7 by Torkel Ödegaard

feat(alerting): working on alerting conditions model

parent f38c9546
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"strconv" "strconv"
"strings"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/alerting/transformers" "github.com/grafana/grafana/pkg/services/alerting/transformers"
...@@ -31,6 +30,19 @@ type AlertRule struct { ...@@ -31,6 +30,19 @@ type AlertRule struct {
NotificationGroups []int64 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 ( var (
ValueFormatRegex = regexp.MustCompile("^\\d+") ValueFormatRegex = regexp.MustCompile("^\\d+")
UnitFormatRegex = regexp.MustCompile("\\w{1}$") UnitFormatRegex = regexp.MustCompile("\\w{1}$")
...@@ -56,7 +68,11 @@ func getTimeDurationStringToSeconds(str string) int64 { ...@@ -56,7 +68,11 @@ func getTimeDurationStringToSeconds(str string) int64 {
} }
func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) { 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.Id = ruleDef.Id
model.OrgId = ruleDef.OrgId model.OrgId = ruleDef.OrgId
model.Name = ruleDef.Name model.Name = ruleDef.Name
...@@ -64,55 +80,26 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) { ...@@ -64,55 +80,26 @@ func NewAlertRuleFromDBModel(ruleDef *m.Alert) (*AlertRule, error) {
model.State = ruleDef.State model.State = ruleDef.State
model.Frequency = ruleDef.Frequency model.Frequency = ruleDef.Frequency
ngs := ruleDef.Settings.Get("notificationGroups").MustString() for _, v := range ruleDef.Settings.Get("notifications").MustArray() {
var ids []int64 if id, ok := v.(int64); ok {
for _, v := range strings.Split(ngs, ",") { model.Notifications = append(model.Notifications, int64(id))
id, err := strconv.Atoi(v)
if err == nil {
ids = append(ids, int64(id))
} }
} }
model.NotificationGroups = ids for _, condition := range ruleDef.Settings.Get("conditions").MustArray() {
conditionModel := simplejson.NewFromAny(condition)
critical := ruleDef.Settings.Get("crit") switch conditionModel.Get("type").MustString() {
model.Critical = Level{ case "query":
Operator: critical.Get("op").MustString(), queryCondition, err := NewQueryCondition(conditionModel)
Value: critical.Get("value").MustFloat64(), if err != nil {
} return nil, err
}
warning := ruleDef.Settings.Get("warn") model.Conditions = append(model.Conditions, queryCondition)
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")
} }
if model.Query.DatasourceId == 0 { if len(model.Conditions) == 0 {
return nil, fmt.Errorf("missing query.datasourceId") return nil, fmt.Errorf("Alert is missing conditions")
} }
return model, nil return model, nil
......
...@@ -38,26 +38,19 @@ func TestAlertRuleModel(t *testing.T) { ...@@ -38,26 +38,19 @@ func TestAlertRuleModel(t *testing.T) {
"description": "desc2", "description": "desc2",
"handler": 0, "handler": 0,
"enabled": true, "enabled": true,
"crit": {
"value": 20,
"op": ">"
},
"warn": {
"value": 10,
"op": ">"
},
"frequency": "60s", "frequency": "60s",
"query": { "conditions": [
"from": "5m", {
"refId": "A", "type": "query",
"to": "now", "query": {
"query": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)", "params": ["A", "5m", "now"],
"datasourceId": 1 "datasourceId": 1,
}, "query": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"
"transform": { },
"type": "avg", "reducer": {"type": "avg", "params": []},
"name": "aggregation" "evaluator": {"type": ">", "params": [100]}
} }
]
} }
` `
...@@ -72,15 +65,11 @@ func TestAlertRuleModel(t *testing.T) { ...@@ -72,15 +65,11 @@ func TestAlertRuleModel(t *testing.T) {
Settings: alertJSON, Settings: alertJSON,
} }
alertRule, err := NewAlertRuleFromDBModel(alert)
alertRule, err := NewAlertRuleFromDBModel2(alert)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(alertRule.Warning.Operator, ShouldEqual, ">") So(alertRule.Conditions, ShouldHaveLength, 1)
So(alertRule.Warning.Value, ShouldEqual, 10)
So(alertRule.Critical.Operator, ShouldEqual, ">")
So(alertRule.Critical.Value, ShouldEqual, 20)
}) })
}) })
} }
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
}
...@@ -38,7 +38,7 @@ func TestAlertResultHandler(t *testing.T) { ...@@ -38,7 +38,7 @@ func TestAlertResultHandler(t *testing.T) {
Convey("alert state have changed", func() { Convey("alert state have changed", func() {
mockAlertState = &m.AlertState{ mockAlertState = &m.AlertState{
NewState: alertstates.Critical, State: alertstates.Critical,
} }
mockResult.State = alertstates.Ok mockResult.State = alertstates.Ok
So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue) So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue)
...@@ -47,11 +47,11 @@ func TestAlertResultHandler(t *testing.T) { ...@@ -47,11 +47,11 @@ func TestAlertResultHandler(t *testing.T) {
Convey("last alert state was 15min ago", func() { Convey("last alert state was 15min ago", func() {
now := time.Now() now := time.Now()
mockAlertState = &m.AlertState{ mockAlertState = &m.AlertState{
NewState: alertstates.Critical, State: alertstates.Critical,
Created: now.Add(time.Minute * -30), Created: now.Add(time.Minute * -30),
} }
mockResult.State = alertstates.Critical mockResult.State = alertstates.Critical
mockResult.ExeuctionTime = time.Now() mockResult.StartTime = time.Now()
So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue) So(resultHandler.shouldUpdateState(mockResult), ShouldBeTrue)
}) })
}) })
......
...@@ -38,11 +38,15 @@ export class AlertTabCtrl { ...@@ -38,11 +38,15 @@ export class AlertTabCtrl {
]; ];
alert: any; alert: any;
conditionModels: any; conditionModels: any;
levelOpList = [ evalFunctions = [
{text: '>', value: '>'}, {text: '>', value: '>'},
{text: '<', value: '<'}, {text: '<', value: '<'},
{text: '=', value: '='},
]; ];
severityLevels = [
{text: 'Critical', value: 'critical'},
{text: 'Warning', value: 'warning'},
];
/** @ngInject */ /** @ngInject */
constructor($scope, private $timeout) { constructor($scope, private $timeout) {
...@@ -60,21 +64,15 @@ export class AlertTabCtrl { ...@@ -60,21 +64,15 @@ export class AlertTabCtrl {
}); });
} }
getThresholdWithDefaults(threshold) {
threshold = threshold || {};
threshold.op = threshold.op || '>';
threshold.value = threshold.value || undefined;
return threshold;
}
initModel() { initModel() {
var alert = this.alert = this.panel.alert = this.panel.alert || {}; var alert = this.alert = this.panel.alert = this.panel.alert || {};
alert.conditions = alert.conditions || []; alert.conditions = [];
if (alert.conditions.length === 0) { if (alert.conditions.length === 0) {
alert.conditions.push(this.buildDefaultCondition()); alert.conditions.push(this.buildDefaultCondition());
} }
alert.severity = alert.severity || 'critical';
alert.frequency = alert.frequency || '60s'; alert.frequency = alert.frequency || '60s';
alert.handler = alert.handler || 1; alert.handler = alert.handler || 1;
alert.notifications = alert.notifications || []; alert.notifications = alert.notifications || [];
...@@ -95,32 +93,23 @@ export class AlertTabCtrl { ...@@ -95,32 +93,23 @@ export class AlertTabCtrl {
buildDefaultCondition() { buildDefaultCondition() {
return { return {
type: 'query', type: 'query',
refId: 'A', query: {params: ['A', '5m', 'now']},
from: '5m', reducer: {type: 'avg', params: []},
to: 'now', evaluator: {type: '>', params: [null]},
reducer: 'avg',
reducerParams: [],
warn: this.getThresholdWithDefaults({}),
crit: this.getThresholdWithDefaults({}),
}; };
} }
buildConditionModel(source) { buildConditionModel(source) {
var cm: any = {source: source, type: source.type}; var cm: any = {source: source, type: source.type};
var queryPartModel = { cm.queryPart = new QueryPart(source.query, alertQueryDef);
params: [source.refId, source.from, source.to]
};
cm.queryPart = new QueryPart(queryPartModel, alertQueryDef);
cm.reducerPart = new QueryPart({params: []}, reducerAvgDef); cm.reducerPart = new QueryPart({params: []}, reducerAvgDef);
cm.evaluator = source.evaluator;
return cm; return cm;
} }
queryPartUpdated(conditionModel) { 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) { addCondition(type) {
...@@ -138,10 +127,6 @@ export class AlertTabCtrl { ...@@ -138,10 +127,6 @@ export class AlertTabCtrl {
delete() { delete() {
this.alert.enabled = false; this.alert.enabled = false;
this.alert.warn.value = undefined;
this.alert.crit.value = undefined;
// reset model but keep thresholds instance
this.initModel(); this.initModel();
} }
......
...@@ -27,31 +27,42 @@ ...@@ -27,31 +27,42 @@
<div class="gf-form-group"> <div class="gf-form-group">
<h5 class="section-heading">Alert Rule</h5> <h5 class="section-heading">Alert Rule</h5>
<div class="gf-form-inline"> <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> <span class="gf-form-label width-8">Name</span>
<input type="text" class="gf-form-input width-22" ng-model="ctrl.alert.name"> <input type="text" class="gf-form-input width-22" ng-model="ctrl.alert.name">
</div> </div>
<div class="gf-form"> <!-- <div class="gf&#45;form"> -->
<span class="gf-form-label">Handler</span> <!-- <span class="gf&#45;form&#45;label width&#45;6">Handler</span> -->
<div class="gf-form-select-wrapper"> <!-- <div class="gf&#45;form&#45;select&#45;wrapper"> -->
<select class="gf-form-input" <!-- <select class="gf&#45;form&#45;input" -->
ng-model="ctrl.alert.handler" <!-- ng&#45;model="ctrl.alert.handler" -->
ng-options="f.value as f.text for f in ctrl.handlers"> <!-- ng&#45;options="f.value as f.text for f in ctrl.handlers"> -->
</select> <!-- </select> -->
</div> <!-- </div> -->
</div> <!-- </div> -->
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-8">Evaluate every</span> <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> <input class="gf-form-input max-width-7" type="text" ng-model="ctrl.alert.frequency"></input>
</div> </div>
</div> </div>
<div class="gf-form"> <div class="gf-form-inline">
<span class="gf-form-label width-8">Notifications</span> <div class="gf-form max-width-30">
<input class="gf-form-input max-width-22" type="text" ng-model="ctrl.alert.notify"></input> <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 ng-model="ctrl.alert.notify" tagclass="label label-tag" placeholder="add tags">
</bootstrap-tagsinput> </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>
</div> </div>
...@@ -59,7 +70,7 @@ ...@@ -59,7 +70,7 @@
<h5 class="section-heading">Conditions</h5> <h5 class="section-heading">Conditions</h5>
<div class="gf-form-inline" ng-repeat="conditionModel in ctrl.conditionModels"> <div class="gf-form-inline" ng-repeat="conditionModel in ctrl.conditionModels">
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label">{{$index+1}}</span> <span class="gf-form-label query-keyword">AND</span>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<query-part-editor <query-part-editor
...@@ -77,21 +88,11 @@ ...@@ -77,21 +88,11 @@
</query-part-editor> </query-part-editor>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label"> <span class="gf-form-label">When Value</span>
<i class="icon-gf icon-gf-warn alert-icon-critical"></i> <metric-segment-model property="conditionModel.evaluator.type" options="ctrl.evalFunctions" custom="false" css-class="query-segment-operator" on-change="ctrl.thresholdUpdated()"></metric-segment-model>
Critcal if <input class="gf-form-input max-width-7" type="number" ng-model="conditionModel.evaluator.params[0]" ng-change="ctrl.thresholdsUpdated()"></input>
</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>
</div> </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.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"> <label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.removeCondition($index)"> <a class="pointer" tabindex="1" ng-click="ctrl.removeCondition($index)">
<i class="fa fa-trash"></i> <i class="fa fa-trash"></i>
......
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