mirror of
https://github.com/grafana/grafana.git
synced 2024-11-24 09:50:29 -06:00
AlertingNG: Temp endpoint to translate dashboard alert into rule group (#32694)
* Set NoData and ExecErr states * make save an option * TODOs * adjust interval * FOR and alertRuleTags not done yet
This commit is contained in:
parent
61d3900b54
commit
d519913843
1
go.sum
1
go.sum
@ -1430,6 +1430,7 @@ github.com/prometheus/prometheus v1.8.2-0.20210217141258-a6be548dbc17 h1:VN3p3Nb
|
||||
github.com/prometheus/prometheus v1.8.2-0.20210217141258-a6be548dbc17/go.mod h1:dv3B1syqmkrkmo665MPCU6L8PbTXIiUeg/OEQULLNxA=
|
||||
github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/quasilyte/go-ruleguard/dsl/fluent v0.0.0-20201222093424-5d7e62a465d3 h1:eL7x4/zMnlquMxYe7V078BD7MGskZ0daGln+SJCVzuY=
|
||||
github.com/quasilyte/go-ruleguard/dsl/fluent v0.0.0-20201222093424-5d7e62a465d3/go.mod h1:P7JlQWFT7jDcFZMtUPQbtGzzzxva3rBn6oIF+LPwFcM=
|
||||
github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY=
|
||||
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
|
||||
|
@ -98,6 +98,9 @@ func (api *API) RegisterAPIEndpoints() {
|
||||
api.RouteRegister.Group("/api/alert-definitions", func(alertDefinitions routing.RouteRegister) {
|
||||
alertDefinitions.Get("/oldByID/:id", middleware.ReqSignedIn, routing.Wrap(api.conditionOldEndpointByID))
|
||||
})
|
||||
api.RouteRegister.Group("/api/alert-definitions", func(alertDefinitions routing.RouteRegister) {
|
||||
alertDefinitions.Get("/ruleGroupByOldID/:id", middleware.ReqSignedIn, routing.Wrap(api.ruleGroupByOldID))
|
||||
})
|
||||
}
|
||||
|
||||
api.RouteRegister.Group("/api/ngalert/", func(schedulerRouter routing.RouteRegister) {
|
||||
|
@ -214,3 +214,17 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule) apimodels.GettableExtended
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func toPostableExtendedRuleNode(r ngmodels.AlertRule) apimodels.PostableExtendedRuleNode {
|
||||
return apimodels.PostableExtendedRuleNode{
|
||||
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
||||
OrgID: r.OrgID,
|
||||
Title: r.Title,
|
||||
Condition: r.Condition,
|
||||
Data: r.Data,
|
||||
UID: r.UID,
|
||||
NoDataState: apimodels.NoDataState(r.NoDataState),
|
||||
ExecErrState: apimodels.ExecutionErrorState(r.ExecErrState),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -2,14 +2,19 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/expr/translate"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/prometheus/common/model"
|
||||
)
|
||||
|
||||
// conditionEvalEndpoint handles POST /api/alert-definitions/evalOld.
|
||||
@ -98,7 +103,7 @@ func (api *API) conditionEvalOldEndpointByID(c *models.ReqContext) response.Resp
|
||||
})
|
||||
}
|
||||
|
||||
// conditionEvalEndpoint handles POST /api/alert-definitions/evalOld.
|
||||
// conditionEvalEndpoint handles POST /api/alert-definitions/oldByID.
|
||||
func (api *API) conditionOldEndpointByID(c *models.ReqContext) response.Response {
|
||||
id := c.ParamsInt64("id")
|
||||
if id == 0 {
|
||||
@ -131,3 +136,200 @@ func (api *API) conditionOldEndpointByID(c *models.ReqContext) response.Response
|
||||
|
||||
return response.JSON(200, evalCond)
|
||||
}
|
||||
|
||||
// ruleGroupByOldID handles POST /api/alert-definitions/ruleGroupByOldID.
|
||||
func (api *API) ruleGroupByOldID(c *models.ReqContext) response.Response {
|
||||
id := c.ParamsInt64("id")
|
||||
if id == 0 {
|
||||
return response.Error(400, "missing id", nil)
|
||||
}
|
||||
|
||||
save := c.Query("save") == "true"
|
||||
|
||||
// Get dashboard alert definition from database.
|
||||
oldAlert, status, err := transGetAlertById(id, *c.SignedInUser)
|
||||
if err != nil {
|
||||
return response.Error(status, "failed to get alert", fmt.Errorf("failed to get alert for alert id %v: %w", id, err))
|
||||
}
|
||||
|
||||
// Translate the dashboard's alerts conditions into SSE queries and conditions.
|
||||
sseCond, err := transToSSECondition(oldAlert, *c.SignedInUser)
|
||||
if err != nil {
|
||||
return response.Error(400, "failed to translate alert conditions",
|
||||
fmt.Errorf("failed to translate alert conditions for alert id %v: %w", id, err))
|
||||
}
|
||||
|
||||
// Get the dashboard that contains the dashboard Alert.
|
||||
oldAlertsDash, status, err := transGetAlertsDashById(oldAlert.DashboardId, *c.SignedInUser)
|
||||
if err != nil {
|
||||
return response.Error(status, "failed to get alert's dashboard", fmt.Errorf("failed to get dashboard for alert id %v, %w", id, err))
|
||||
}
|
||||
|
||||
isGeneralFolder := oldAlertsDash.FolderId == 0 && !oldAlertsDash.IsFolder
|
||||
|
||||
var namespaceUID string
|
||||
|
||||
if isGeneralFolder {
|
||||
namespaceUID = "General"
|
||||
} else {
|
||||
// Get the folder that contains the dashboard that contains the dashboard alert.
|
||||
getFolder := &models.GetDashboardQuery{
|
||||
Id: oldAlertsDash.FolderId,
|
||||
OrgId: oldAlertsDash.OrgId,
|
||||
}
|
||||
if err := bus.Dispatch(getFolder); err != nil {
|
||||
return response.Error(400, fmt.Sprintf("could find folder %v for alert with id %v", getFolder.Id, id), err)
|
||||
}
|
||||
|
||||
namespaceUID = getFolder.Result.Uid
|
||||
}
|
||||
|
||||
noDataSetting, execErrSetting, err := transNoDataExecSettings(oldAlert, *c.SignedInUser)
|
||||
if err != nil {
|
||||
return response.Error(400, "unable to translate nodata/exec error settings",
|
||||
fmt.Errorf("unable to translate nodata/exec error settings for alert id %v: %w", id, err))
|
||||
}
|
||||
|
||||
// TODO: What to do with Rule Tags
|
||||
// ruleTags := map[string]string{}
|
||||
|
||||
// for k, v := range oldAlert.Settings.Get("alertRuleTags").MustMap() {
|
||||
// sV, ok := v.(string)
|
||||
// if !ok {
|
||||
// return response.Error(400, "unable to unmarshal rule tags",
|
||||
// fmt.Errorf("unexpected type %T for tag %v", v, k))
|
||||
// }
|
||||
// ruleTags[k] = sV
|
||||
// }
|
||||
|
||||
// TODO: Need place to put FOR duration
|
||||
|
||||
rule := ngmodels.AlertRule{
|
||||
Title: oldAlert.Name,
|
||||
Data: sseCond.Data,
|
||||
Condition: sseCond.Condition,
|
||||
NoDataState: *noDataSetting,
|
||||
ExecErrState: *execErrSetting,
|
||||
}
|
||||
|
||||
rgc := apimodels.PostableRuleGroupConfig{
|
||||
// TODO? Generate new name on conflict?
|
||||
Name: oldAlert.Name,
|
||||
Interval: transAdjustInterval(oldAlert.Frequency),
|
||||
Rules: []apimodels.PostableExtendedRuleNode{
|
||||
toPostableExtendedRuleNode(rule),
|
||||
},
|
||||
}
|
||||
|
||||
cmd := store.UpdateRuleGroupCmd{
|
||||
OrgID: oldAlert.OrgId,
|
||||
NamespaceUID: namespaceUID,
|
||||
RuleGroupConfig: rgc,
|
||||
}
|
||||
|
||||
if !save {
|
||||
return response.JSON(200, cmd)
|
||||
}
|
||||
|
||||
// note: Update rule group will set the Interval within the grafana_alert from
|
||||
// the interval of the group.
|
||||
err = api.RuleStore.UpdateRuleGroup(cmd)
|
||||
|
||||
if err != nil {
|
||||
return response.JSON(400, util.DynMap{
|
||||
"message:": "failed to save alert rule",
|
||||
"error": err.Error(),
|
||||
"cmd": cmd,
|
||||
})
|
||||
}
|
||||
|
||||
return response.JSON(200, cmd)
|
||||
}
|
||||
|
||||
func transAdjustInterval(freq int64) model.Duration {
|
||||
// 10 corresponds to the SchedulerCfg, but TODO not worrying about fetching for now.
|
||||
var baseFreq int64 = 10
|
||||
if freq <= baseFreq {
|
||||
return model.Duration(time.Second * 10)
|
||||
}
|
||||
return model.Duration(time.Duration((freq - (freq % baseFreq))) * time.Second)
|
||||
}
|
||||
|
||||
func transGetAlertById(id int64, user models.SignedInUser) (*models.Alert, int, error) {
|
||||
getAlert := &models.GetAlertByIdQuery{
|
||||
Id: id,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(getAlert); err != nil {
|
||||
return nil, 400, fmt.Errorf("could find alert with id %v: %w", id, err)
|
||||
}
|
||||
|
||||
if getAlert.Result.OrgId != user.OrgId {
|
||||
return nil, 403, fmt.Errorf("alert does not match organization of user")
|
||||
}
|
||||
|
||||
return getAlert.Result, 0, nil
|
||||
}
|
||||
|
||||
func transGetAlertsDashById(dashboardId int64, user models.SignedInUser) (*models.Dashboard, int, error) {
|
||||
getDash := &models.GetDashboardQuery{
|
||||
Id: dashboardId,
|
||||
OrgId: user.OrgId,
|
||||
}
|
||||
if err := bus.Dispatch(getDash); err != nil {
|
||||
return nil, 400, fmt.Errorf("could find dashboard with id %v: %w", dashboardId, err)
|
||||
}
|
||||
return getDash.Result, 0, nil
|
||||
}
|
||||
|
||||
func transToSSECondition(m *models.Alert, user models.SignedInUser) (*ngmodels.Condition, error) {
|
||||
sb, err := m.Settings.ToDB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal alert settings: %w", err)
|
||||
}
|
||||
|
||||
evalCond, err := translate.DashboardAlertConditions(sb, user.OrgId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to translate dashboard alert to SSE conditions: %w", err)
|
||||
}
|
||||
return evalCond, nil
|
||||
}
|
||||
|
||||
func transNoDataExecSettings(m *models.Alert, user models.SignedInUser) (*ngmodels.NoDataState, *ngmodels.ExecutionErrorState, error) {
|
||||
oldNoData := m.Settings.Get("noDataState").MustString()
|
||||
noDataSetting, err := transNoData(oldNoData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
oldExecErr := m.Settings.Get("executionErrorState").MustString()
|
||||
execErrSetting, err := transExecErr(oldExecErr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &noDataSetting, &execErrSetting, nil
|
||||
}
|
||||
|
||||
func transNoData(s string) (ngmodels.NoDataState, error) {
|
||||
switch s {
|
||||
case "ok":
|
||||
return ngmodels.OK, nil
|
||||
case "no_data":
|
||||
return ngmodels.NoData, nil
|
||||
case "alerting":
|
||||
return ngmodels.Alerting, nil
|
||||
case "keep_state":
|
||||
return ngmodels.KeepLastState, nil
|
||||
}
|
||||
return ngmodels.NoData, fmt.Errorf("unrecognized No Data setting %v", s)
|
||||
}
|
||||
|
||||
func transExecErr(s string) (ngmodels.ExecutionErrorState, error) {
|
||||
switch s {
|
||||
case "alerting":
|
||||
return ngmodels.AlertingErrState, nil
|
||||
case "KeepLastState":
|
||||
return ngmodels.KeepLastStateErrState, nil
|
||||
}
|
||||
return ngmodels.AlertingErrState, fmt.Errorf("unrecognized Execution Error setting %v", s)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user