mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(alerting): working on alerting conditions model
This commit is contained in:
parent
f38c954639
commit
20fcffb71e
pkg/services/alerting
public/app/plugins/panel/graph
@ -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(),
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
32
pkg/services/alerting/conditions.go
Normal file
32
pkg/services/alerting/conditions.go
Normal file
@ -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
|
||||
}
|
@ -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-form"> -->
|
||||
<!-- <span class="gf-form-label width-6">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-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>
|
||||
|
Loading…
Reference in New Issue
Block a user