mirror of
https://github.com/grafana/grafana.git
synced 2025-02-03 20:21:01 -06:00
feat(alerting): progress on alerting UI and model, refactoring of dashboard parser and tests into extractor component, moved tests from sqlstore to alerting package
This commit is contained in:
parent
1fa9ae810b
commit
2b4a9954b1
@ -151,15 +151,13 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
|
||||
}
|
||||
|
||||
if setting.AlertingEnabled {
|
||||
saveAlertCommand := m.SaveAlertsCommand{
|
||||
DashboardId: cmd.Result.Id,
|
||||
alertCmd := alerting.UpdateDashboardAlertsCommand{
|
||||
OrgId: c.OrgId,
|
||||
UserId: c.UserId,
|
||||
Alerts: alerting.ParseAlertsFromDashboard(&cmd),
|
||||
Dashboard: cmd.Result,
|
||||
}
|
||||
|
||||
err = bus.Dispatch(&saveAlertCommand)
|
||||
if err != nil {
|
||||
if err := bus.Dispatch(&alertCmd); err != nil {
|
||||
c.JsonApiErr(500, "Failed to save alerts", err)
|
||||
return
|
||||
}
|
||||
|
@ -14,6 +14,9 @@ type AlertRuleModel struct {
|
||||
Name string
|
||||
Description string
|
||||
State string
|
||||
Scheduler int64
|
||||
Enabled bool
|
||||
Frequency int
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
@ -21,6 +24,8 @@ type AlertRuleModel struct {
|
||||
Expression *simplejson.Json
|
||||
}
|
||||
|
||||
type AlertRules []*AlertRuleModel
|
||||
|
||||
func (this AlertRuleModel) TableName() string {
|
||||
return "alert_rule"
|
||||
}
|
||||
@ -83,7 +88,7 @@ type SaveAlertsCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
|
||||
Alerts []*AlertRuleModel
|
||||
Alerts AlertRules
|
||||
}
|
||||
|
||||
type DeleteAlertCommand struct {
|
||||
|
76
pkg/services/alerting/alert_rule.go
Normal file
76
pkg/services/alerting/alert_rule.go
Normal file
@ -0,0 +1,76 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type AlertRule struct {
|
||||
Id int64
|
||||
OrgId int64
|
||||
DashboardId int64
|
||||
PanelId int64
|
||||
Frequency int64
|
||||
Name string
|
||||
Description string
|
||||
State string
|
||||
Warning Level
|
||||
Critical Level
|
||||
Query AlertQuery
|
||||
Transform string
|
||||
TransformParams simplejson.Json
|
||||
Transformer Transformer
|
||||
}
|
||||
|
||||
func NewAlertRuleFromDBModel(ruleDef *m.AlertRuleModel) (*AlertRule, error) {
|
||||
model := &AlertRule{}
|
||||
model.Id = ruleDef.Id
|
||||
model.OrgId = ruleDef.OrgId
|
||||
model.Name = ruleDef.Name
|
||||
model.Description = ruleDef.Description
|
||||
model.State = ruleDef.State
|
||||
|
||||
critical := ruleDef.Expression.Get("critical")
|
||||
model.Critical = Level{
|
||||
Operator: critical.Get("op").MustString(),
|
||||
Level: critical.Get("level").MustFloat64(),
|
||||
}
|
||||
|
||||
warning := ruleDef.Expression.Get("warning")
|
||||
model.Warning = Level{
|
||||
Operator: warning.Get("op").MustString(),
|
||||
Level: warning.Get("level").MustFloat64(),
|
||||
}
|
||||
|
||||
model.Frequency = ruleDef.Expression.Get("frequency").MustInt64()
|
||||
model.Transform = ruleDef.Expression.Get("transform").Get("type").MustString()
|
||||
model.TransformParams = *ruleDef.Expression.Get("transform")
|
||||
|
||||
if model.Transform == "aggregation" {
|
||||
model.Transformer = &AggregationTransformer{
|
||||
Method: ruleDef.Expression.Get("transform").Get("method").MustString(),
|
||||
}
|
||||
}
|
||||
|
||||
query := ruleDef.Expression.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(),
|
||||
Aggregator: query.Get("agg").MustString(),
|
||||
}
|
||||
|
||||
if model.Query.Query == "" {
|
||||
return nil, fmt.Errorf("missing query.query")
|
||||
}
|
||||
|
||||
if model.Query.DatasourceId == 0 {
|
||||
return nil, fmt.Errorf("missing query.datasourceId")
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
89
pkg/services/alerting/commands.go
Normal file
89
pkg/services/alerting/commands.go
Normal file
@ -0,0 +1,89 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type UpdateDashboardAlertsCommand struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
Dashboard *m.Dashboard
|
||||
}
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("alerting", updateDashboardAlerts)
|
||||
}
|
||||
|
||||
func updateDashboardAlerts(cmd *UpdateDashboardAlertsCommand) error {
|
||||
saveRulesCmd := m.SaveAlertsCommand{
|
||||
OrgId: cmd.OrgId,
|
||||
UserId: cmd.UserId,
|
||||
}
|
||||
|
||||
extractor := NewAlertRuleExtractor(cmd.Dashboard, cmd.OrgId)
|
||||
|
||||
rules, err := extractor.GetRuleModels()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
saveRulesCmd.Alerts = rules
|
||||
if bus.Dispatch(&saveRulesCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ConvetAlertModelToAlertRule(ruleDef *m.AlertRuleModel) (*AlertRule, error) {
|
||||
model := &AlertRule{}
|
||||
model.Id = ruleDef.Id
|
||||
model.OrgId = ruleDef.OrgId
|
||||
model.Name = ruleDef.Name
|
||||
model.Description = ruleDef.Description
|
||||
model.State = ruleDef.State
|
||||
|
||||
critical := ruleDef.Expression.Get("critical")
|
||||
model.Critical = Level{
|
||||
Operator: critical.Get("op").MustString(),
|
||||
Level: critical.Get("level").MustFloat64(),
|
||||
}
|
||||
|
||||
warning := ruleDef.Expression.Get("warning")
|
||||
model.Warning = Level{
|
||||
Operator: warning.Get("op").MustString(),
|
||||
Level: warning.Get("level").MustFloat64(),
|
||||
}
|
||||
|
||||
model.Frequency = ruleDef.Expression.Get("frequency").MustInt64()
|
||||
model.Transform = ruleDef.Expression.Get("transform").Get("type").MustString()
|
||||
model.TransformParams = *ruleDef.Expression.Get("transform")
|
||||
|
||||
if model.Transform == "aggregation" {
|
||||
model.Transformer = &AggregationTransformer{
|
||||
Method: ruleDef.Expression.Get("transform").Get("method").MustString(),
|
||||
}
|
||||
}
|
||||
|
||||
query := ruleDef.Expression.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(),
|
||||
Aggregator: query.Get("agg").MustString(),
|
||||
}
|
||||
|
||||
if model.Query.Query == "" {
|
||||
return nil, fmt.Errorf("missing query.query")
|
||||
}
|
||||
|
||||
if model.Query.DatasourceId == 0 {
|
||||
return nil, fmt.Errorf("missing query.datasourceId")
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
@ -1,135 +0,0 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func ParseAlertsFromDashboard(cmd *m.SaveDashboardCommand) []*m.AlertRuleModel {
|
||||
alerts := make([]*m.AlertRuleModel, 0)
|
||||
|
||||
for _, rowObj := range cmd.Dashboard.Get("rows").MustArray() {
|
||||
row := simplejson.NewFromAny(rowObj)
|
||||
|
||||
for _, panelObj := range row.Get("panels").MustArray() {
|
||||
panel := simplejson.NewFromAny(panelObj)
|
||||
|
||||
alerting := panel.Get("alerting")
|
||||
alert := &m.AlertRuleModel{
|
||||
DashboardId: cmd.Result.Id,
|
||||
OrgId: cmd.Result.OrgId,
|
||||
PanelId: panel.Get("id").MustInt64(),
|
||||
Id: alerting.Get("id").MustInt64(),
|
||||
Name: alerting.Get("name").MustString(),
|
||||
Description: alerting.Get("description").MustString(),
|
||||
}
|
||||
|
||||
log.Info("Alertrule: %v", alert.Name)
|
||||
|
||||
valueQuery := alerting.Get("query")
|
||||
valueQueryRef := valueQuery.Get("refId").MustString()
|
||||
for _, targetsObj := range panel.Get("targets").MustArray() {
|
||||
target := simplejson.NewFromAny(targetsObj)
|
||||
|
||||
if target.Get("refId").MustString() == valueQueryRef {
|
||||
datsourceName := ""
|
||||
if target.Get("datasource").MustString() != "" {
|
||||
datsourceName = target.Get("datasource").MustString()
|
||||
} else if panel.Get("datasource").MustString() != "" {
|
||||
datsourceName = panel.Get("datasource").MustString()
|
||||
}
|
||||
|
||||
if datsourceName == "" {
|
||||
query := &m.GetDataSourcesQuery{OrgId: cmd.OrgId}
|
||||
if err := bus.Dispatch(query); err == nil {
|
||||
for _, ds := range query.Result {
|
||||
if ds.IsDefault {
|
||||
alerting.SetPath([]string{"query", "datasourceId"}, ds.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query := &m.GetDataSourceByNameQuery{
|
||||
Name: panel.Get("datasource").MustString(),
|
||||
OrgId: cmd.OrgId,
|
||||
}
|
||||
bus.Dispatch(query)
|
||||
alerting.SetPath([]string{"query", "datasourceId"}, query.Result.Id)
|
||||
}
|
||||
|
||||
targetQuery := target.Get("target").MustString()
|
||||
if targetQuery != "" {
|
||||
alerting.SetPath([]string{"query", "query"}, targetQuery)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
alert.Expression = alerting
|
||||
|
||||
_, err := ConvetAlertModelToAlertRule(alert)
|
||||
|
||||
if err == nil && alert.ValidToSave() {
|
||||
alerts = append(alerts, alert)
|
||||
} else {
|
||||
log.Error2("Failed to parse model from expression", "error", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return alerts
|
||||
}
|
||||
|
||||
func ConvetAlertModelToAlertRule(ruleDef *m.AlertRuleModel) (*AlertRule, error) {
|
||||
model := &AlertRule{}
|
||||
model.Id = ruleDef.Id
|
||||
model.OrgId = ruleDef.OrgId
|
||||
model.Name = ruleDef.Name
|
||||
model.Description = ruleDef.Description
|
||||
model.State = ruleDef.State
|
||||
|
||||
critical := ruleDef.Expression.Get("critical")
|
||||
model.Critical = Level{
|
||||
Operator: critical.Get("op").MustString(),
|
||||
Level: critical.Get("level").MustFloat64(),
|
||||
}
|
||||
|
||||
warning := ruleDef.Expression.Get("warning")
|
||||
model.Warning = Level{
|
||||
Operator: warning.Get("op").MustString(),
|
||||
Level: warning.Get("level").MustFloat64(),
|
||||
}
|
||||
|
||||
model.Frequency = ruleDef.Expression.Get("frequency").MustInt64()
|
||||
model.Transform = ruleDef.Expression.Get("transform").Get("type").MustString()
|
||||
model.TransformParams = *ruleDef.Expression.Get("transform")
|
||||
|
||||
if model.Transform == "aggregation" {
|
||||
model.Transformer = &AggregationTransformer{
|
||||
Method: ruleDef.Expression.Get("transform").Get("method").MustString(),
|
||||
}
|
||||
}
|
||||
|
||||
query := ruleDef.Expression.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(),
|
||||
Aggregator: query.Get("agg").MustString(),
|
||||
}
|
||||
|
||||
if model.Query.Query == "" {
|
||||
return nil, fmt.Errorf("missing query.query")
|
||||
}
|
||||
|
||||
if model.Query.DatasourceId == 0 {
|
||||
return nil, fmt.Errorf("missing query.datasourceId")
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
120
pkg/services/alerting/extractor.go
Normal file
120
pkg/services/alerting/extractor.go
Normal file
@ -0,0 +1,120 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type AlertRuleExtractor struct {
|
||||
Dash *m.Dashboard
|
||||
OrgId int64
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewAlertRuleExtractor(dash *m.Dashboard, orgId int64) *AlertRuleExtractor {
|
||||
return &AlertRuleExtractor{
|
||||
Dash: dash,
|
||||
OrgId: orgId,
|
||||
log: log.New("alerting.extractor"),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *AlertRuleExtractor) lookupDatasourceId(dsName string) (int64, error) {
|
||||
if dsName == "" {
|
||||
query := &m.GetDataSourcesQuery{OrgId: e.OrgId}
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
for _, ds := range query.Result {
|
||||
if ds.IsDefault {
|
||||
return ds.Id, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
query := &m.GetDataSourceByNameQuery{Name: dsName, OrgId: e.OrgId}
|
||||
if err := bus.Dispatch(query); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
return query.Result.Id, nil
|
||||
}
|
||||
}
|
||||
|
||||
return 0, errors.New("Could not find datasource id for " + dsName)
|
||||
}
|
||||
|
||||
func (e *AlertRuleExtractor) GetRuleModels() (m.AlertRules, error) {
|
||||
|
||||
rules := make(m.AlertRules, 0)
|
||||
|
||||
for _, rowObj := range e.Dash.Data.Get("rows").MustArray() {
|
||||
row := simplejson.NewFromAny(rowObj)
|
||||
|
||||
for _, panelObj := range row.Get("panels").MustArray() {
|
||||
panel := simplejson.NewFromAny(panelObj)
|
||||
jsonRule := panel.Get("alerting")
|
||||
|
||||
// check if marked for deletion
|
||||
deleted := jsonRule.Get("deleted").MustBool()
|
||||
if deleted {
|
||||
e.log.Info("Deleted alert rule found")
|
||||
continue
|
||||
}
|
||||
|
||||
ruleModel := &m.AlertRuleModel{
|
||||
DashboardId: e.Dash.Id,
|
||||
OrgId: e.OrgId,
|
||||
PanelId: panel.Get("id").MustInt64(),
|
||||
Id: jsonRule.Get("id").MustInt64(),
|
||||
Name: jsonRule.Get("name").MustString(),
|
||||
Scheduler: jsonRule.Get("scheduler").MustInt64(),
|
||||
Enabled: jsonRule.Get("enabled").MustBool(),
|
||||
Description: jsonRule.Get("description").MustString(),
|
||||
}
|
||||
|
||||
valueQuery := jsonRule.Get("query")
|
||||
valueQueryRef := valueQuery.Get("refId").MustString()
|
||||
for _, targetsObj := range panel.Get("targets").MustArray() {
|
||||
target := simplejson.NewFromAny(targetsObj)
|
||||
|
||||
if target.Get("refId").MustString() == valueQueryRef {
|
||||
dsName := ""
|
||||
if target.Get("datasource").MustString() != "" {
|
||||
dsName = target.Get("datasource").MustString()
|
||||
} else if panel.Get("datasource").MustString() != "" {
|
||||
dsName = panel.Get("datasource").MustString()
|
||||
}
|
||||
|
||||
if datasourceId, err := e.lookupDatasourceId(dsName); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
valueQuery.SetPath([]string{"datasourceId"}, datasourceId)
|
||||
}
|
||||
|
||||
targetQuery := target.Get("target").MustString()
|
||||
if targetQuery != "" {
|
||||
jsonRule.SetPath([]string{"query", "query"}, targetQuery)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ruleModel.Expression = jsonRule
|
||||
|
||||
// validate
|
||||
_, err := NewAlertRuleFromDBModel(ruleModel)
|
||||
if err == nil && ruleModel.ValidToSave() {
|
||||
rules = append(rules, ruleModel)
|
||||
} else {
|
||||
e.log.Error("Failed to extract alert rules from dashboard", "error", err)
|
||||
return nil, errors.New("Failed to extract alert rules from dashboard")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
package sqlstore
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/alerting"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestAlertModelParsing(t *testing.T) {
|
||||
func TestAlertRuleExtraction(t *testing.T) {
|
||||
|
||||
Convey("Parsing alert info from json", t, func() {
|
||||
Convey("Parsing alert rules from dashboard json", t, func() {
|
||||
Convey("Parsing and validating alerts from dashboards", func() {
|
||||
json := `{
|
||||
"id": 57,
|
||||
@ -37,13 +37,15 @@ func TestAlertModelParsing(t *testing.T) {
|
||||
],
|
||||
"datasource": null,
|
||||
"alerting": {
|
||||
"name": "Alerting Panel Title alert",
|
||||
"description": "description",
|
||||
"name": "name1",
|
||||
"description": "desc1",
|
||||
"scheduler": 1,
|
||||
"enabled": true,
|
||||
"critical": {
|
||||
"level": 20,
|
||||
"op": ">"
|
||||
},
|
||||
"frequency": 10,
|
||||
"frequency": "60s",
|
||||
"query": {
|
||||
"from": "5m",
|
||||
"refId": "A",
|
||||
@ -51,7 +53,7 @@ func TestAlertModelParsing(t *testing.T) {
|
||||
},
|
||||
"transform": {
|
||||
"method": "avg",
|
||||
"name": "aggregation"
|
||||
"type": "aggregation"
|
||||
},
|
||||
"warning": {
|
||||
"level": 10,
|
||||
@ -70,13 +72,15 @@ func TestAlertModelParsing(t *testing.T) {
|
||||
],
|
||||
"datasource": "graphite2",
|
||||
"alerting": {
|
||||
"name": "Alerting Panel Title alert",
|
||||
"description": "description",
|
||||
"name": "name2",
|
||||
"description": "desc2",
|
||||
"scheduler": 0,
|
||||
"enabled": true,
|
||||
"critical": {
|
||||
"level": 20,
|
||||
"op": ">"
|
||||
},
|
||||
"frequency": 10,
|
||||
"frequency": "60s",
|
||||
"query": {
|
||||
"from": "5m",
|
||||
"refId": "A",
|
||||
@ -145,7 +149,10 @@ func TestAlertModelParsing(t *testing.T) {
|
||||
],
|
||||
"title": "Broken influxdb panel",
|
||||
"transform": "table",
|
||||
"type": "table"
|
||||
"type": "table",
|
||||
"alerting": {
|
||||
"deleted": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "New row"
|
||||
@ -153,51 +160,62 @@ func TestAlertModelParsing(t *testing.T) {
|
||||
]
|
||||
|
||||
}`
|
||||
dashboardJSON, _ := simplejson.NewJson([]byte(json))
|
||||
cmd := &m.SaveDashboardCommand{
|
||||
Dashboard: dashboardJSON,
|
||||
UserId: 1,
|
||||
OrgId: 1,
|
||||
Overwrite: true,
|
||||
Result: &m.Dashboard{
|
||||
Id: 1,
|
||||
},
|
||||
dashJson, err := simplejson.NewJson([]byte(json))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
dash := m.NewDashboardFromJson(dashJson)
|
||||
extractor := NewAlertRuleExtractor(dash, 1)
|
||||
|
||||
// mock data
|
||||
defaultDs := &m.DataSource{Id: 12, OrgId: 2, Name: "I am default", IsDefault: true}
|
||||
graphite2Ds := &m.DataSource{Id: 15, OrgId: 2, Name: "graphite2"}
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDataSourcesQuery) error {
|
||||
query.Result = []*m.DataSource{defaultDs, graphite2Ds}
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDataSourceByNameQuery) error {
|
||||
if query.Name == defaultDs.Name {
|
||||
query.Result = defaultDs
|
||||
}
|
||||
|
||||
InitTestDB(t)
|
||||
|
||||
AddDataSource(&m.AddDataSourceCommand{
|
||||
Name: "graphite2",
|
||||
OrgId: 1,
|
||||
Type: m.DS_INFLUXDB,
|
||||
Access: m.DS_ACCESS_DIRECT,
|
||||
Url: "http://test",
|
||||
IsDefault: false,
|
||||
Database: "site",
|
||||
if query.Name == graphite2Ds.Name {
|
||||
query.Result = graphite2Ds
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
AddDataSource(&m.AddDataSourceCommand{
|
||||
Name: "InfluxDB",
|
||||
OrgId: 1,
|
||||
Type: m.DS_GRAPHITE,
|
||||
Access: m.DS_ACCESS_DIRECT,
|
||||
Url: "http://test",
|
||||
IsDefault: true,
|
||||
})
|
||||
alerts, err := extractor.GetRuleModels()
|
||||
|
||||
alerts := alerting.ParseAlertsFromDashboard(cmd)
|
||||
Convey("Get rules without error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("all properties have been set", func() {
|
||||
So(alerts, ShouldNotBeEmpty)
|
||||
So(len(alerts), ShouldEqual, 2)
|
||||
|
||||
for _, v := range alerts {
|
||||
So(v.DashboardId, ShouldEqual, 1)
|
||||
So(v.PanelId, ShouldNotEqual, 0)
|
||||
|
||||
So(v.DashboardId, ShouldEqual, 57)
|
||||
So(v.Name, ShouldNotBeEmpty)
|
||||
So(v.Description, ShouldNotBeEmpty)
|
||||
}
|
||||
|
||||
Convey("should extract scheduler property", func() {
|
||||
So(alerts[0].Scheduler, ShouldEqual, 1)
|
||||
So(alerts[1].Scheduler, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("should extract panel idc", func() {
|
||||
So(alerts[0].PanelId, ShouldEqual, 3)
|
||||
So(alerts[1].PanelId, ShouldEqual, 4)
|
||||
})
|
||||
|
||||
Convey("should extract name and desc", func() {
|
||||
So(alerts[0].Name, ShouldEqual, "name1")
|
||||
So(alerts[0].Description, ShouldEqual, "desc1")
|
||||
So(alerts[1].Name, ShouldEqual, "name2")
|
||||
So(alerts[1].Description, ShouldEqual, "desc2")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -1,9 +1,5 @@
|
||||
package alerting
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
)
|
||||
|
||||
type AlertJob struct {
|
||||
Offset int64
|
||||
Delay bool
|
||||
@ -21,23 +17,6 @@ type AlertResult struct {
|
||||
AlertJob *AlertJob
|
||||
}
|
||||
|
||||
type AlertRule struct {
|
||||
Id int64
|
||||
OrgId int64
|
||||
DashboardId int64
|
||||
PanelId int64
|
||||
Frequency int64
|
||||
Name string
|
||||
Description string
|
||||
State string
|
||||
Warning Level
|
||||
Critical Level
|
||||
Query AlertQuery
|
||||
Transform string
|
||||
TransformParams simplejson.Json
|
||||
Transformer Transformer
|
||||
}
|
||||
|
||||
type Level struct {
|
||||
Operator string
|
||||
Level float64
|
||||
|
@ -17,6 +17,9 @@ func addAlertMigrations(mg *Migrator) {
|
||||
{Name: "description", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||
{Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||
{Name: "expression", Type: DB_Text, Nullable: false},
|
||||
{Name: "scheduler", Type: DB_BigInt, Nullable: false},
|
||||
{Name: "frequency", Type: DB_BigInt, Nullable: false},
|
||||
{Name: "enabled", Type: DB_Bool, Nullable: false},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
|
@ -24,6 +24,7 @@ export class AlertTabCtrl {
|
||||
panelCtrl: any;
|
||||
alerting: any;
|
||||
metricTargets = [{ refId: '- select query -' } ];
|
||||
schedulers = [{text: 'Grafana', value: 1}, {text: 'External', value: 0}];
|
||||
transforms = [
|
||||
{
|
||||
text: 'Aggregation',
|
||||
@ -33,24 +34,23 @@ export class AlertTabCtrl {
|
||||
text: 'Linear Forecast',
|
||||
type: 'forecast',
|
||||
},
|
||||
{
|
||||
text: 'Percent Change',
|
||||
type: 'percent_change',
|
||||
},
|
||||
{
|
||||
text: 'Query diff',
|
||||
type: 'query_diff',
|
||||
},
|
||||
];
|
||||
aggregators = ['avg', 'sum', 'min', 'max', 'last'];
|
||||
rule: any;
|
||||
query: any;
|
||||
queryParams: any;
|
||||
transformDef: any;
|
||||
trasnformQuery: any;
|
||||
levelOpList = [
|
||||
{text: '>', value: '>'},
|
||||
{text: '<', value: '<'},
|
||||
{text: '=', value: '='},
|
||||
];
|
||||
|
||||
defaultValues = {
|
||||
frequency: 10,
|
||||
frequency: '60s',
|
||||
notify: [],
|
||||
enabled: false,
|
||||
scheduler: 1,
|
||||
warning: { op: '>', level: undefined },
|
||||
critical: { op: '>', level: undefined },
|
||||
query: {
|
||||
@ -139,8 +139,18 @@ export class AlertTabCtrl {
|
||||
}
|
||||
}
|
||||
|
||||
markAsDeleted() {
|
||||
this.panel.alerting = this.defaultValues;
|
||||
delete() {
|
||||
this.rule = this.panel.alerting = this.defaultValues;
|
||||
this.rule.deleted = true;
|
||||
}
|
||||
|
||||
enable() {
|
||||
delete this.rule.deleted;
|
||||
this.rule.enabled = true;
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.rule.enabled = false;
|
||||
}
|
||||
|
||||
thresholdsUpdated() {
|
||||
|
@ -1,15 +1,13 @@
|
||||
|
||||
<div class="gf-form-group" >
|
||||
<h5 class="section-heading">Alert Rule</h5>
|
||||
<div class="editor-row">
|
||||
<div class="gf-form-group section" >
|
||||
<h5 class="section-heading">Alert Query</h5>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
|
||||
<query-part-editor
|
||||
class="gf-form-label query-part"
|
||||
part="ctrl.query"
|
||||
part-updated="ctrl.queryUpdated()">
|
||||
</query-part-editor>
|
||||
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Transform using</span>
|
||||
@ -33,61 +31,68 @@
|
||||
</div>
|
||||
<div class="gf-form" ng-if="ctrl.transformDef.type === 'forecast'">
|
||||
<span class="gf-form-label">Timespan</span>
|
||||
<input class="gf-form-input max-width-7" type="text" ng-model="ctrl.rule.transform.timespan" ng-change="ctrl.ruleUpdated()"></input>
|
||||
<input class="gf-form-input max-width-5" type="text" ng-model="ctrl.rule.transform.timespan" ng-change="ctrl.ruleUpdated()"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gf-form-group" >
|
||||
<div class="gf-form-group section">
|
||||
<h5 class="section-heading">Levels</h5>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">
|
||||
<i class="icon-gf icon-gf-warn alert-icon-warn"></i>
|
||||
Warn if value
|
||||
Warn if
|
||||
</span>
|
||||
<span class="gf-form-label">
|
||||
>
|
||||
</span>
|
||||
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.rule.warnLevel" ng-change="alertTab.thresholdsUpdated()"></input>
|
||||
<metric-segment-model property="ctrl.rule.warning.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model>
|
||||
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.rule.warnLevel" ng-change="ctrl.thresholdsUpdated()"></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">
|
||||
<i class="icon-gf icon-gf-warn alert-icon-critical"></i>
|
||||
Critcal if value
|
||||
Critcal if
|
||||
</span>
|
||||
<span class="gf-form-label">
|
||||
>
|
||||
</span>
|
||||
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.rule.critLevel" ng-change="alertTab.thresholdsUpdated()"></input>
|
||||
<metric-segment-model property="ctrl.rule.critical.op" options="ctrl.levelOpList" custom="false" css-class="query-segment-operator"></metric-segment-model>
|
||||
<input class="gf-form-input max-width-7" type="number" ng-model="ctrl.rule.critLevel" ng-change="ctrl.thresholdsUpdated()"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="gf-form"> -->
|
||||
<!-- <span class="gf-form-label width-12">Aggregation method</span> -->
|
||||
<!-- <div class="gf-form-select-wrapper max-width-10"> -->
|
||||
<!-- <select class="gf-form-input" -->
|
||||
<!-- ng-model="ctrl.panel.alerting.aggregator" -->
|
||||
<!-- ng-options="oper as oper for oper in alertTab.aggregators"></select> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
<!-- -->
|
||||
<!-- <div class="gf-form"> -->
|
||||
<!-- <span class="gf-form-label width-12">Query range (seconds)</span> -->
|
||||
<!-- <input class="gf-form-input max-width-10" type="number" -->
|
||||
<!-- ng-model="ctrl.panel.alerting.queryRange" placeholder="3600"></input> -->
|
||||
<!-- </div> -->
|
||||
<!-- -->
|
||||
<!-- <div class="gf-form"> -->
|
||||
<!-- <span class="gf-form-label width-12">Frequency (seconds)</span> -->
|
||||
<!-- <input class="gf-form-input max-width-10" type="number" -->
|
||||
<!-- ng-model="ctrl.panel.alerting.frequency" placeholder="60"></input> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
<div>
|
||||
<div class="editor-row">
|
||||
<div class="gf-form-group section">
|
||||
<h5 class="section-heading">Alert info</h5>
|
||||
<h5 class="section-heading">Execution</h5>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Scheduler</span>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input"
|
||||
ng-model="ctrl.rule.scheduler"
|
||||
ng-options="f.value as f.text for f in ctrl.schedulers">
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Evaluate every</span>
|
||||
<input class="gf-form-input max-width-7" type="text" ng-model="ctrl.rule.frequency"></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form-group section">
|
||||
<h5 class="section-heading">Notifications</h5>
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label">Groups</span>
|
||||
<bootstrap-tagsinput ng-model="ctrl.rule.notify" tagclass="label label-tag" placeholder="add tags">
|
||||
</bootstrap-tagsinput>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="gf-form-group section">
|
||||
<h5 class="section-heading">Information</h5>
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label width-10">Alert name</span>
|
||||
<input type="text" class="gf-form-input width-22" ng-model="ctrl.panel.alerting.name">
|
||||
@ -100,10 +105,12 @@
|
||||
<textarea rows="5" ng-model="ctrl.panel.alerting.description" class="gf-form-input width-22"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="gf-form-button-row">
|
||||
<button class="btn btn-warning" ng-click="alertTab.markAsDeleted()">Delete Alert</button>
|
||||
<button class="btn btn-danger" ng-click="ctrl.delete()" ng-show="ctrl.rule.enabled">Delete</button>
|
||||
<button class="btn btn-success" ng-click="ctrl.enable()" ng-hide="ctrl.rule.enabled">Enable</button>
|
||||
<button class="btn btn-secondary" ng-click="ctrl.disable()" ng-show="ctrl.rule.enabled">Disable</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,6 +7,7 @@
|
||||
background-color: $input-bg;
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
border: none;
|
||||
border-right: 1px solid $tight-form-border;
|
||||
margin: 0px;
|
||||
|
@ -35,7 +35,7 @@
|
||||
this.inputSize = Math.max(1, this.placeholderText.length);
|
||||
|
||||
this.$container = $('<div class="bootstrap-tagsinput"></div>');
|
||||
this.$input = $('<input class="tight-form-input" size="' +
|
||||
this.$input = $('<input class="gf-form-input" size="' +
|
||||
this.inputSize + '" type="text" placeholder="' + this.placeholderText + '"/>').appendTo(this.$container);
|
||||
|
||||
this.$element.after(this.$container);
|
||||
|
Loading…
Reference in New Issue
Block a user