diff --git a/pkg/models/alerts.go b/pkg/models/alerts.go index a99ebe24105..1035509b23c 100644 --- a/pkg/models/alerts.go +++ b/pkg/models/alerts.go @@ -2,10 +2,12 @@ package models import ( "github.com/grafana/grafana/pkg/components/simplejson" + "time" ) type AlertRule struct { Id int64 + OrgId int64 DashboardId int64 PanelId int64 Query string @@ -19,6 +21,13 @@ type AlertRule struct { Aggregator string } +type AlertRuleChange struct { + OrgId int64 + AlertId int64 + Type string + Created time.Time +} + func (cmd *SaveDashboardCommand) GetAlertModels() *[]AlertRule { alerts := make([]AlertRule, 0) @@ -31,6 +40,7 @@ func (cmd *SaveDashboardCommand) GetAlertModels() *[]AlertRule { alerting := panel.Get("alerting") alert := AlertRule{ DashboardId: cmd.Result.Id, + OrgId: cmd.Result.OrgId, PanelId: panel.Get("id").MustInt64(), Id: alerting.Get("id").MustInt64(), QueryRefId: alerting.Get("query_ref").MustString(), diff --git a/pkg/services/sqlstore/alerting.go b/pkg/services/sqlstore/alerting.go index 45dfc72d2ec..bd353a0e0dc 100644 --- a/pkg/services/sqlstore/alerting.go +++ b/pkg/services/sqlstore/alerting.go @@ -2,14 +2,49 @@ package sqlstore import ( "fmt" + "github.com/go-xorm/xorm" "github.com/grafana/grafana/pkg/bus" m "github.com/grafana/grafana/pkg/models" + "time" ) func init() { bus.AddHandler("sql", SaveAlerts) } +func SaveAlertChange(change string, alert m.AlertRule) error { + return inTransaction(func(sess *xorm.Session) error { + _, err := sess.Insert(&m.AlertRuleChange{ + OrgId: alert.OrgId, + Type: change, + Created: time.Now(), + AlertId: alert.Id, + }) + + if err != nil { + return err + } + + return nil + }) +} + +func alertIsDifferent(rule1, rule2 m.AlertRule) bool { + result := false + + result = result || rule1.Aggregator != rule2.Aggregator + result = result || rule1.CritLevel != rule2.CritLevel + result = result || rule1.WarnLevel != rule2.WarnLevel + result = result || rule1.Query != rule2.Query + result = result || rule1.QueryRefId != rule2.QueryRefId + result = result || rule1.Interval != rule2.Interval + result = result || rule1.Title != rule2.Title + result = result || rule1.Description != rule2.Description + result = result || rule1.QueryRange != rule2.QueryRange + + return result +} + func SaveAlerts(cmd *m.SaveAlertsCommand) error { //this function should be refactored @@ -22,24 +57,33 @@ func SaveAlerts(cmd *m.SaveAlertsCommand) error { for _, alert := range *cmd.Alerts { update := false + var alertToUpdate m.AlertRule for _, k := range alerts { if alert.PanelId == k.PanelId { update = true alert.Id = k.Id + alertToUpdate = k } } if update { - _, err = x.Id(alert.Id).Update(&alert) - if err != nil { - return err + + if alertIsDifferent(alertToUpdate, alert) { + _, err = x.Id(alert.Id).Update(&alert) + if err != nil { + return err + } + + SaveAlertChange("UPDATED", alert) } + } else { _, err = x.Insert(&alert) if err != nil { return err } + SaveAlertChange("CREATED", alert) } } @@ -58,6 +102,7 @@ func SaveAlerts(cmd *m.SaveAlertsCommand) error { return err } + err = SaveAlertChange("DELETED", missingAlert) if err != nil { return err } @@ -95,3 +140,14 @@ func GetAlertsByDashboardAndPanelId(dashboardId, panelId int64) (m.AlertRule, er return alerts[0], nil } + +func GetAlertRuleChanges(orgid int64) ([]m.AlertRuleChange, error) { + alertChanges := make([]m.AlertRuleChange, 0) + err := x.Where("org_id = ?", orgid).Find(&alertChanges) + + if err != nil { + return []m.AlertRuleChange{}, err + } + + return alertChanges, nil +} diff --git a/pkg/services/sqlstore/alerting_test.go b/pkg/services/sqlstore/alerting_test.go index 86b6c889858..48e11ea50cf 100644 --- a/pkg/services/sqlstore/alerting_test.go +++ b/pkg/services/sqlstore/alerting_test.go @@ -18,6 +18,7 @@ func TestAlertingDataAccess(t *testing.T) { { PanelId: 1, DashboardId: testDash.Id, + OrgId: testDash.OrgId, Query: "Query", QueryRefId: "A", WarnLevel: "> 30", @@ -41,6 +42,10 @@ func TestAlertingDataAccess(t *testing.T) { Convey("Can create one alert", func() { So(err, ShouldBeNil) + + alertChanges, er := GetAlertRuleChanges(1) + So(er, ShouldBeNil) + So(len(alertChanges), ShouldEqual, 1) }) Convey("Can read properties", func() { @@ -82,6 +87,15 @@ func TestAlertingDataAccess(t *testing.T) { So(len(alerts), ShouldEqual, 1) So(alerts[0].Query, ShouldEqual, "Updated Query") }) + + Convey("Updates without changes should be ignored", func() { + err3 := SaveAlerts(&modifiedCmd) + So(err3, ShouldBeNil) + + alertChanges, er := GetAlertRuleChanges(1) + So(er, ShouldBeNil) + So(len(alertChanges), ShouldEqual, 2) + }) }) Convey("Multiple alerts per dashboard", func() { @@ -90,16 +104,19 @@ func TestAlertingDataAccess(t *testing.T) { DashboardId: testDash.Id, PanelId: 1, Query: "1", + OrgId: 1, }, { DashboardId: testDash.Id, PanelId: 2, Query: "2", + OrgId: 1, }, { DashboardId: testDash.Id, PanelId: 3, Query: "3", + OrgId: 1, }, } @@ -112,6 +129,9 @@ func TestAlertingDataAccess(t *testing.T) { alerts, err2 := GetAlertsByDashboardId(testDash.Id) So(err2, ShouldBeNil) So(len(alerts), ShouldEqual, 3) + alertChanges, er := GetAlertRuleChanges(1) + So(er, ShouldBeNil) + So(len(alertChanges), ShouldEqual, 4) }) Convey("should updated two dashboards and delete one", func() { @@ -125,6 +145,12 @@ func TestAlertingDataAccess(t *testing.T) { So(err2, ShouldBeNil) So(len(alerts), ShouldEqual, 2) }) + + Convey("should add one more alert_rule_change", func() { + alertChanges, er := GetAlertRuleChanges(1) + So(er, ShouldBeNil) + So(len(alertChanges), ShouldEqual, 6) + }) }) }) diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go index 8b9f9aa8254..204e7a855a0 100644 --- a/pkg/services/sqlstore/migrations/alert_mig.go +++ b/pkg/services/sqlstore/migrations/alert_mig.go @@ -9,6 +9,7 @@ func addAlertMigrations(mg *Migrator) { {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "dashboard_id", Type: DB_BigInt, Nullable: false}, {Name: "panel_id", Type: DB_BigInt, Nullable: false}, + {Name: "org_id", Type: DB_BigInt, Nullable: false}, {Name: "query", Type: DB_Text, Nullable: false}, {Name: "query_ref_id", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "warn_level", Type: DB_NVarchar, Length: 255, Nullable: false}, @@ -22,14 +23,15 @@ func addAlertMigrations(mg *Migrator) { } // create table - mg.AddMigration("create alert table v1", NewAddTableMigration(alertV1)) + mg.AddMigration("create alert_rule table v1", NewAddTableMigration(alertV1)) alert_changes := Table{ - Name: "alert_rule_updates", + Name: "alert_rule_change", Columns: []*Column{ {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, {Name: "alert_id", Type: DB_BigInt, Nullable: false}, {Name: "org_id", Type: DB_BigInt, Nullable: false}, + {Name: "type", Type: DB_NVarchar, Length: 50, Nullable: false}, {Name: "created", Type: DB_DateTime, Nullable: false}, }, }