From e7be7d2835ae362435ad2dceebce4f6f2a0d8f87 Mon Sep 17 00:00:00 2001 From: bergquist Date: Thu, 28 Apr 2016 08:23:50 +0200 Subject: [PATCH] feat(alerting): add api endpoint for alert state --- pkg/api/alerting.go | 34 ++++++-- pkg/api/api.go | 3 +- pkg/models/alerting_state.go | 32 +++++++ pkg/models/alerts.go | 55 ++++++------ pkg/models/alerts_test.go | 3 - pkg/services/sqlstore/alert_rule.go | 5 +- pkg/services/sqlstore/alert_rule_test.go | 26 +++--- pkg/services/sqlstore/alert_state.go | 39 +++++++++ pkg/services/sqlstore/alert_state_test.go | 85 +++++++++++++++++++ pkg/services/sqlstore/migrations/alert_mig.go | 2 +- public/app/features/alerts/alerts_ctrl.ts | 2 +- 11 files changed, 230 insertions(+), 56 deletions(-) create mode 100644 pkg/models/alerting_state.go create mode 100644 pkg/services/sqlstore/alert_state.go create mode 100644 pkg/services/sqlstore/alert_state_test.go diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 29ca7a6758f..c435b52fdd0 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -9,7 +9,7 @@ import ( func ValidateOrgAlert(c *middleware.Context) { id := c.ParamsInt64(":id") - query := models.GetAlertById{Id: id} + query := models.GetAlertByIdQuery{Id: id} if err := bus.Dispatch(&query); err != nil { c.JsonApiErr(404, "Alert not found", nil) @@ -22,7 +22,7 @@ func ValidateOrgAlert(c *middleware.Context) { } } -// GET /api/alert_rule/changes +// GET /api/alerts/changes func GetAlertChanges(c *middleware.Context) Response { query := models.GetAlertChangesQuery{ OrgId: c.OrgId, @@ -35,7 +35,7 @@ func GetAlertChanges(c *middleware.Context) Response { return Json(200, query.Result) } -// GET /api/alert_rule +// GET /api/alerts func GetAlerts(c *middleware.Context) Response { query := models.GetAlertsQuery{ OrgId: c.OrgId, @@ -85,10 +85,10 @@ func GetAlerts(c *middleware.Context) Response { return Json(200, alertDTOs) } -// GET /api/alert_rule/:id +// GET /api/alerts/:id func GetAlert(c *middleware.Context) Response { id := c.ParamsInt64(":id") - query := models.GetAlertById{Id: id} + query := models.GetAlertByIdQuery{Id: id} if err := bus.Dispatch(&query); err != nil { return ApiError(500, "List alerts failed", err) @@ -96,3 +96,27 @@ func GetAlert(c *middleware.Context) Response { return Json(200, &query.Result) } + +// PUT /api/alerts/state/:id +func PutAlertState(c *middleware.Context, cmd models.UpdateAlertStateCommand) Response { + alertId := c.ParamsInt64(":alertId") + + if alertId != cmd.AlertId { + return ApiError(401, "Bad Request", nil) + } + + query := models.GetAlertByIdQuery{Id: alertId} + if err := bus.Dispatch(&query); err != nil { + return ApiError(500, "Failed to get alertstate", err) + } + + if query.Result.OrgId != 0 && query.Result.OrgId != c.OrgId { + return ApiError(500, "Alert not found", nil) + } + + if err := bus.Dispatch(&cmd); err != nil { + return ApiError(500, "Failed to set new state", err) + } + + return Json(200, cmd.Result) +} diff --git a/pkg/api/api.go b/pkg/api/api.go index 081a85adba0..fb88f4e3f9f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -238,7 +238,8 @@ func Register(r *macaron.Macaron) { // metrics r.Get("/metrics/test", GetTestMetrics) - r.Group("/alert_rule", func() { + r.Group("/alerts", func() { + r.Put("/state/:alertId", bind(m.UpdateAlertStateCommand{}), wrap(PutAlertState)) r.Get("/changes", wrap(GetAlertChanges)) r.Get("/", wrap(GetAlerts)) r.Get("/:id", ValidateOrgAlert, wrap(GetAlert)) diff --git a/pkg/models/alerting_state.go b/pkg/models/alerting_state.go new file mode 100644 index 00000000000..63f1e5db777 --- /dev/null +++ b/pkg/models/alerting_state.go @@ -0,0 +1,32 @@ +package models + +import "time" + +type AlertStateLog struct { + Id int64 `json:"id"` + OrgId int64 `json:"-"` + AlertId int64 `json:"alertId"` + State string `json:"type"` + Created time.Time `json:"created"` + Acknowledged time.Time `json:"acknowledged"` + Deleted time.Time `json:"deleted"` +} + +var ( + ALERT_STATE_OK = "OK" + ALERT_STATE_ALERT = "ALERT" + ALERT_STATE_WARN = "WARN" + ALERT_STATE_ACKNOWLEDGED = "ACKNOWLEDGED" +) + +func (this *UpdateAlertStateCommand) IsValidState() bool { + return this.NewState == ALERT_STATE_OK || this.NewState == ALERT_STATE_WARN || this.NewState == ALERT_STATE_ALERT || this.NewState == ALERT_STATE_ACKNOWLEDGED +} + +type UpdateAlertStateCommand struct { + AlertId int64 `json:"alertId" binding:"Required"` + NewState string `json:"newState" binding:"Required"` + Info string `json:"info"` + + Result *AlertRule +} diff --git a/pkg/models/alerts.go b/pkg/models/alerts.go index 2b61e044e8d..f9c8a151bc2 100644 --- a/pkg/models/alerts.go +++ b/pkg/models/alerts.go @@ -6,20 +6,20 @@ import ( ) type AlertRule struct { - Id int64 `json:"id"` - OrgId int64 `json:"-"` - DashboardId int64 `json:"dashboardId"` - PanelId int64 `json:"panelId"` - Query string `json:"query"` - QueryRefId string `json:"queryRefId"` - WarnLevel string `json:"warnLevel"` - CritLevel string `json:"critLevel"` - Interval string `json:"interval"` - Title string `json:"title"` - Description string `json:"description"` - QueryRange string `json:"queryRange"` - Aggregator string `json:"aggregator"` - DatasourceName string `json:"-"` + Id int64 `json:"id"` + OrgId int64 `json:"-"` + DashboardId int64 `json:"dashboardId"` + PanelId int64 `json:"panelId"` + Query string `json:"query"` + QueryRefId string `json:"queryRefId"` + WarnLevel string `json:"warnLevel"` + CritLevel string `json:"critLevel"` + Interval string `json:"interval"` + Title string `json:"title"` + Description string `json:"description"` + QueryRange string `json:"queryRange"` + Aggregator string `json:"aggregator"` + State string `json:"state"` } type AlertRuleChange struct { @@ -40,19 +40,18 @@ func (cmd *SaveDashboardCommand) GetAlertModels() *[]AlertRule { alerting := panel.Get("alerting") alert := AlertRule{ - DashboardId: cmd.Result.Id, - OrgId: cmd.Result.OrgId, - PanelId: panel.Get("id").MustInt64(), - DatasourceName: panel.Get("datasource").MustString(), - Id: alerting.Get("id").MustInt64(), - QueryRefId: alerting.Get("queryRef").MustString(), - WarnLevel: alerting.Get("warnLevel").MustString(), - CritLevel: alerting.Get("critLevel").MustString(), - Interval: alerting.Get("interval").MustString(), - Title: alerting.Get("title").MustString(), - Description: alerting.Get("description").MustString(), - QueryRange: alerting.Get("queryRange").MustString(), - Aggregator: alerting.Get("aggregator").MustString(), + DashboardId: cmd.Result.Id, + OrgId: cmd.Result.OrgId, + PanelId: panel.Get("id").MustInt64(), + Id: alerting.Get("id").MustInt64(), + QueryRefId: alerting.Get("queryRef").MustString(), + WarnLevel: alerting.Get("warnLevel").MustString(), + CritLevel: alerting.Get("critLevel").MustString(), + Interval: alerting.Get("interval").MustString(), + Title: alerting.Get("title").MustString(), + Description: alerting.Get("description").MustString(), + QueryRange: alerting.Get("queryRange").MustString(), + Aggregator: alerting.Get("aggregator").MustString(), } for _, targetsObj := range panel.Get("targets").MustArray() { @@ -92,7 +91,7 @@ type GetAlertsQuery struct { Result []AlertRule } -type GetAlertById struct { +type GetAlertByIdQuery struct { Id int64 Result AlertRule diff --git a/pkg/models/alerts_test.go b/pkg/models/alerts_test.go index a2978c2ddc2..e661d65f017 100644 --- a/pkg/models/alerts_test.go +++ b/pkg/models/alerts_test.go @@ -371,9 +371,6 @@ func TestAlertModel(t *testing.T) { So(alerts[0].Query, ShouldEqual, `{"refId":"A","target":"aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"}`) So(alerts[1].Query, ShouldEqual, `{"refId":"A","target":"aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}`) - - So(alerts[0].DatasourceName, ShouldEqual, "") - So(alerts[1].DatasourceName, ShouldEqual, "graphite2") }) }) } diff --git a/pkg/services/sqlstore/alert_rule.go b/pkg/services/sqlstore/alert_rule.go index e2729431337..3b26acc741d 100644 --- a/pkg/services/sqlstore/alert_rule.go +++ b/pkg/services/sqlstore/alert_rule.go @@ -13,7 +13,7 @@ func init() { bus.AddHandler("sql", GetAlertById) } -func GetAlertById(query *m.GetAlertById) error { +func GetAlertById(query *m.GetAlertByIdQuery) error { alert := m.AlertRule{} has, err := x.Id(query.Id).Get(&alert) @@ -68,7 +68,7 @@ func alertIsDifferent(rule1, rule2 m.AlertRule) bool { result = result || rule1.Title != rule2.Title result = result || rule1.Description != rule2.Description result = result || rule1.QueryRange != rule2.QueryRange - result = result || rule1.DatasourceName != rule2.DatasourceName + //don't compare .State! That would be insane. return result } @@ -81,7 +81,6 @@ func SaveAlerts(cmd *m.SaveAlertsCommand) error { } upsertAlerts(alerts, cmd.Alerts, sess) - deleteMissingAlerts(alerts, cmd.Alerts, sess) return nil diff --git a/pkg/services/sqlstore/alert_rule_test.go b/pkg/services/sqlstore/alert_rule_test.go index aab7b883ffa..dec3a94d9a4 100644 --- a/pkg/services/sqlstore/alert_rule_test.go +++ b/pkg/services/sqlstore/alert_rule_test.go @@ -16,19 +16,18 @@ func TestAlertingDataAccess(t *testing.T) { items := []m.AlertRule{ { - PanelId: 1, - DashboardId: testDash.Id, - OrgId: testDash.OrgId, - Query: "Query", - QueryRefId: "A", - WarnLevel: "> 30", - CritLevel: "> 50", - Interval: "10", - Title: "Alerting title", - Description: "Alerting description", - QueryRange: "5m", - Aggregator: "avg", - DatasourceName: "graphite", + PanelId: 1, + DashboardId: testDash.Id, + OrgId: testDash.OrgId, + Query: "Query", + QueryRefId: "A", + WarnLevel: "> 30", + CritLevel: "> 50", + Interval: "10", + Title: "Alerting title", + Description: "Alerting description", + QueryRange: "5m", + Aggregator: "avg", }, } @@ -63,7 +62,6 @@ func TestAlertingDataAccess(t *testing.T) { So(alert.Description, ShouldEqual, "Alerting description") So(alert.QueryRange, ShouldEqual, "5m") So(alert.Aggregator, ShouldEqual, "avg") - So(alert.DatasourceName, ShouldEqual, "graphite") }) Convey("Alerts with same dashboard id and panel id should update", func() { diff --git a/pkg/services/sqlstore/alert_state.go b/pkg/services/sqlstore/alert_state.go new file mode 100644 index 00000000000..49be27b0d0a --- /dev/null +++ b/pkg/services/sqlstore/alert_state.go @@ -0,0 +1,39 @@ +package sqlstore + +import ( + "fmt" + "github.com/go-xorm/xorm" + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" +) + +func init() { + bus.AddHandler("sql", SetNewAlertState) +} + +func SetNewAlertState(cmd *m.UpdateAlertStateCommand) error { + return inTransaction(func(sess *xorm.Session) error { + if !cmd.IsValidState() { + return fmt.Errorf("new state is invalid") + } + + alert := m.AlertRule{} + has, err := sess.Id(cmd.AlertId).Get(&alert) + if !has { + return fmt.Errorf("Could not find alert") + } + + if err != nil { + return err + } + + alert.State = cmd.NewState + sess.Id(alert.Id).Update(&alert) + //update alert + + //insert alert state log + + cmd.Result = &alert + return nil + }) +} diff --git a/pkg/services/sqlstore/alert_state_test.go b/pkg/services/sqlstore/alert_state_test.go new file mode 100644 index 00000000000..68024755edb --- /dev/null +++ b/pkg/services/sqlstore/alert_state_test.go @@ -0,0 +1,85 @@ +package sqlstore + +import ( + "testing" + + m "github.com/grafana/grafana/pkg/models" + . "github.com/smartystreets/goconvey/convey" +) + +func TestAlertingStateAccess(t *testing.T) { + Convey("Test alerting state changes", t, func() { + InitTestDB(t) + + //setup alert + testDash := insertTestDashboard("dashboard with alerts", 1, "alert") + + items := []m.AlertRule{ + { + PanelId: 1, + DashboardId: testDash.Id, + OrgId: testDash.OrgId, + Query: "Query", + QueryRefId: "A", + WarnLevel: "> 30", + CritLevel: "> 50", + Interval: "10", + Title: "Alerting title", + Description: "Alerting description", + QueryRange: "5m", + Aggregator: "avg", + }, + } + + cmd := m.SaveAlertsCommand{ + Alerts: &items, + DashboardId: testDash.Id, + OrgId: 1, + UserId: 1, + } + + err := SaveAlerts(&cmd) + So(err, ShouldBeNil) + + Convey("Cannot insert invalid states", func() { + err = SetNewAlertState(&m.UpdateAlertStateCommand{ + AlertId: 1, + NewState: "maybe ok", + Info: "Shit just hit the fan", + }) + + So(err, ShouldNotBeNil) + }) + + Convey("Changes state to alert", func() { + + err = SetNewAlertState(&m.UpdateAlertStateCommand{ + AlertId: 1, + NewState: "ALERT", + Info: "Shit just hit the fan", + }) + + Convey("can get new state for alert", func() { + query := &m.GetAlertByIdQuery{Id: 1} + err := GetAlertById(query) + So(err, ShouldBeNil) + So(query.Result.State, ShouldEqual, "ALERT") + }) + + Convey("Changes state to ok", func() { + err = SetNewAlertState(&m.UpdateAlertStateCommand{ + AlertId: 1, + NewState: "OK", + Info: "Shit just hit the fan", + }) + + Convey("get ok state for alert", func() { + query := &m.GetAlertByIdQuery{Id: 1} + err := GetAlertById(query) + So(err, ShouldBeNil) + So(query.Result.State, ShouldEqual, "OK") + }) + }) + }) + }) +} diff --git a/pkg/services/sqlstore/migrations/alert_mig.go b/pkg/services/sqlstore/migrations/alert_mig.go index 74ed633091e..3ddfe2ee400 100644 --- a/pkg/services/sqlstore/migrations/alert_mig.go +++ b/pkg/services/sqlstore/migrations/alert_mig.go @@ -19,7 +19,7 @@ func addAlertMigrations(mg *Migrator) { {Name: "description", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "query_range", Type: DB_NVarchar, Length: 255, Nullable: false}, {Name: "aggregator", Type: DB_NVarchar, Length: 255, Nullable: false}, - {Name: "datasource_name", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "state", Type: DB_NVarchar, Length: 255, Nullable: false}, }, } diff --git a/public/app/features/alerts/alerts_ctrl.ts b/public/app/features/alerts/alerts_ctrl.ts index 7a3fc032281..9a6ba927f01 100644 --- a/public/app/features/alerts/alerts_ctrl.ts +++ b/public/app/features/alerts/alerts_ctrl.ts @@ -15,7 +15,7 @@ export class AlertPageCtrl { } loadAlerts() { - this.backendSrv.get('/api/alert_rule').then(result => { + this.backendSrv.get('/api/alerts').then(result => { console.log(result); this.alerts = result; });