Commit 20fcffb7 by Torkel Ödegaard

feat(alerting): working on alerting conditions model

parent f38c9546
......@@ -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
......
......@@ -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)
})
})
}
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) {
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)
})
})
......
......@@ -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();
}
......
......@@ -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>
......
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