mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
feat(alerting): add api endpoint for alert state
This commit is contained in:
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
func ValidateOrgAlert(c *middleware.Context) {
|
func ValidateOrgAlert(c *middleware.Context) {
|
||||||
id := c.ParamsInt64(":id")
|
id := c.ParamsInt64(":id")
|
||||||
query := models.GetAlertById{Id: id}
|
query := models.GetAlertByIdQuery{Id: id}
|
||||||
|
|
||||||
if err := bus.Dispatch(&query); err != nil {
|
if err := bus.Dispatch(&query); err != nil {
|
||||||
c.JsonApiErr(404, "Alert not found", 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 {
|
func GetAlertChanges(c *middleware.Context) Response {
|
||||||
query := models.GetAlertChangesQuery{
|
query := models.GetAlertChangesQuery{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
@@ -35,7 +35,7 @@ func GetAlertChanges(c *middleware.Context) Response {
|
|||||||
return Json(200, query.Result)
|
return Json(200, query.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/alert_rule
|
// GET /api/alerts
|
||||||
func GetAlerts(c *middleware.Context) Response {
|
func GetAlerts(c *middleware.Context) Response {
|
||||||
query := models.GetAlertsQuery{
|
query := models.GetAlertsQuery{
|
||||||
OrgId: c.OrgId,
|
OrgId: c.OrgId,
|
||||||
@@ -85,10 +85,10 @@ func GetAlerts(c *middleware.Context) Response {
|
|||||||
return Json(200, alertDTOs)
|
return Json(200, alertDTOs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /api/alert_rule/:id
|
// GET /api/alerts/:id
|
||||||
func GetAlert(c *middleware.Context) Response {
|
func GetAlert(c *middleware.Context) Response {
|
||||||
id := c.ParamsInt64(":id")
|
id := c.ParamsInt64(":id")
|
||||||
query := models.GetAlertById{Id: id}
|
query := models.GetAlertByIdQuery{Id: id}
|
||||||
|
|
||||||
if err := bus.Dispatch(&query); err != nil {
|
if err := bus.Dispatch(&query); err != nil {
|
||||||
return ApiError(500, "List alerts failed", err)
|
return ApiError(500, "List alerts failed", err)
|
||||||
@@ -96,3 +96,27 @@ func GetAlert(c *middleware.Context) Response {
|
|||||||
|
|
||||||
return Json(200, &query.Result)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -238,7 +238,8 @@ func Register(r *macaron.Macaron) {
|
|||||||
// metrics
|
// metrics
|
||||||
r.Get("/metrics/test", GetTestMetrics)
|
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("/changes", wrap(GetAlertChanges))
|
||||||
r.Get("/", wrap(GetAlerts))
|
r.Get("/", wrap(GetAlerts))
|
||||||
r.Get("/:id", ValidateOrgAlert, wrap(GetAlert))
|
r.Get("/:id", ValidateOrgAlert, wrap(GetAlert))
|
||||||
|
|||||||
32
pkg/models/alerting_state.go
Normal file
32
pkg/models/alerting_state.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -6,20 +6,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AlertRule struct {
|
type AlertRule struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
OrgId int64 `json:"-"`
|
OrgId int64 `json:"-"`
|
||||||
DashboardId int64 `json:"dashboardId"`
|
DashboardId int64 `json:"dashboardId"`
|
||||||
PanelId int64 `json:"panelId"`
|
PanelId int64 `json:"panelId"`
|
||||||
Query string `json:"query"`
|
Query string `json:"query"`
|
||||||
QueryRefId string `json:"queryRefId"`
|
QueryRefId string `json:"queryRefId"`
|
||||||
WarnLevel string `json:"warnLevel"`
|
WarnLevel string `json:"warnLevel"`
|
||||||
CritLevel string `json:"critLevel"`
|
CritLevel string `json:"critLevel"`
|
||||||
Interval string `json:"interval"`
|
Interval string `json:"interval"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
QueryRange string `json:"queryRange"`
|
QueryRange string `json:"queryRange"`
|
||||||
Aggregator string `json:"aggregator"`
|
Aggregator string `json:"aggregator"`
|
||||||
DatasourceName string `json:"-"`
|
State string `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertRuleChange struct {
|
type AlertRuleChange struct {
|
||||||
@@ -40,19 +40,18 @@ func (cmd *SaveDashboardCommand) GetAlertModels() *[]AlertRule {
|
|||||||
|
|
||||||
alerting := panel.Get("alerting")
|
alerting := panel.Get("alerting")
|
||||||
alert := AlertRule{
|
alert := AlertRule{
|
||||||
DashboardId: cmd.Result.Id,
|
DashboardId: cmd.Result.Id,
|
||||||
OrgId: cmd.Result.OrgId,
|
OrgId: cmd.Result.OrgId,
|
||||||
PanelId: panel.Get("id").MustInt64(),
|
PanelId: panel.Get("id").MustInt64(),
|
||||||
DatasourceName: panel.Get("datasource").MustString(),
|
Id: alerting.Get("id").MustInt64(),
|
||||||
Id: alerting.Get("id").MustInt64(),
|
QueryRefId: alerting.Get("queryRef").MustString(),
|
||||||
QueryRefId: alerting.Get("queryRef").MustString(),
|
WarnLevel: alerting.Get("warnLevel").MustString(),
|
||||||
WarnLevel: alerting.Get("warnLevel").MustString(),
|
CritLevel: alerting.Get("critLevel").MustString(),
|
||||||
CritLevel: alerting.Get("critLevel").MustString(),
|
Interval: alerting.Get("interval").MustString(),
|
||||||
Interval: alerting.Get("interval").MustString(),
|
Title: alerting.Get("title").MustString(),
|
||||||
Title: alerting.Get("title").MustString(),
|
Description: alerting.Get("description").MustString(),
|
||||||
Description: alerting.Get("description").MustString(),
|
QueryRange: alerting.Get("queryRange").MustString(),
|
||||||
QueryRange: alerting.Get("queryRange").MustString(),
|
Aggregator: alerting.Get("aggregator").MustString(),
|
||||||
Aggregator: alerting.Get("aggregator").MustString(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, targetsObj := range panel.Get("targets").MustArray() {
|
for _, targetsObj := range panel.Get("targets").MustArray() {
|
||||||
@@ -92,7 +91,7 @@ type GetAlertsQuery struct {
|
|||||||
Result []AlertRule
|
Result []AlertRule
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetAlertById struct {
|
type GetAlertByIdQuery struct {
|
||||||
Id int64
|
Id int64
|
||||||
|
|
||||||
Result AlertRule
|
Result AlertRule
|
||||||
|
|||||||
@@ -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[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[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")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func init() {
|
|||||||
bus.AddHandler("sql", GetAlertById)
|
bus.AddHandler("sql", GetAlertById)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAlertById(query *m.GetAlertById) error {
|
func GetAlertById(query *m.GetAlertByIdQuery) error {
|
||||||
alert := m.AlertRule{}
|
alert := m.AlertRule{}
|
||||||
has, err := x.Id(query.Id).Get(&alert)
|
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.Title != rule2.Title
|
||||||
result = result || rule1.Description != rule2.Description
|
result = result || rule1.Description != rule2.Description
|
||||||
result = result || rule1.QueryRange != rule2.QueryRange
|
result = result || rule1.QueryRange != rule2.QueryRange
|
||||||
result = result || rule1.DatasourceName != rule2.DatasourceName
|
//don't compare .State! That would be insane.
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,6 @@ func SaveAlerts(cmd *m.SaveAlertsCommand) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
upsertAlerts(alerts, cmd.Alerts, sess)
|
upsertAlerts(alerts, cmd.Alerts, sess)
|
||||||
|
|
||||||
deleteMissingAlerts(alerts, cmd.Alerts, sess)
|
deleteMissingAlerts(alerts, cmd.Alerts, sess)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -16,19 +16,18 @@ func TestAlertingDataAccess(t *testing.T) {
|
|||||||
|
|
||||||
items := []m.AlertRule{
|
items := []m.AlertRule{
|
||||||
{
|
{
|
||||||
PanelId: 1,
|
PanelId: 1,
|
||||||
DashboardId: testDash.Id,
|
DashboardId: testDash.Id,
|
||||||
OrgId: testDash.OrgId,
|
OrgId: testDash.OrgId,
|
||||||
Query: "Query",
|
Query: "Query",
|
||||||
QueryRefId: "A",
|
QueryRefId: "A",
|
||||||
WarnLevel: "> 30",
|
WarnLevel: "> 30",
|
||||||
CritLevel: "> 50",
|
CritLevel: "> 50",
|
||||||
Interval: "10",
|
Interval: "10",
|
||||||
Title: "Alerting title",
|
Title: "Alerting title",
|
||||||
Description: "Alerting description",
|
Description: "Alerting description",
|
||||||
QueryRange: "5m",
|
QueryRange: "5m",
|
||||||
Aggregator: "avg",
|
Aggregator: "avg",
|
||||||
DatasourceName: "graphite",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +62,6 @@ func TestAlertingDataAccess(t *testing.T) {
|
|||||||
So(alert.Description, ShouldEqual, "Alerting description")
|
So(alert.Description, ShouldEqual, "Alerting description")
|
||||||
So(alert.QueryRange, ShouldEqual, "5m")
|
So(alert.QueryRange, ShouldEqual, "5m")
|
||||||
So(alert.Aggregator, ShouldEqual, "avg")
|
So(alert.Aggregator, ShouldEqual, "avg")
|
||||||
So(alert.DatasourceName, ShouldEqual, "graphite")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Alerts with same dashboard id and panel id should update", func() {
|
Convey("Alerts with same dashboard id and panel id should update", func() {
|
||||||
|
|||||||
39
pkg/services/sqlstore/alert_state.go
Normal file
39
pkg/services/sqlstore/alert_state.go
Normal file
@@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
85
pkg/services/sqlstore/alert_state_test.go
Normal file
85
pkg/services/sqlstore/alert_state_test.go
Normal file
@@ -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")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ func addAlertMigrations(mg *Migrator) {
|
|||||||
{Name: "description", Type: DB_NVarchar, Length: 255, Nullable: false},
|
{Name: "description", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||||
{Name: "query_range", 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: "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},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class AlertPageCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadAlerts() {
|
loadAlerts() {
|
||||||
this.backendSrv.get('/api/alert_rule').then(result => {
|
this.backendSrv.get('/api/alerts').then(result => {
|
||||||
console.log(result);
|
console.log(result);
|
||||||
this.alerts = result;
|
this.alerts = result;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user