Commit 938541be by Torkel Ödegaard Committed by GitHub

Merge pull request #13660 from grafana/alerting-validation-messages

Alerting: Propagate alert validation issues to the api/user
parents 70945850 ba67dc76
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"os" "os"
"path" "path"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
...@@ -251,8 +252,8 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response { ...@@ -251,8 +252,8 @@ func PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand) Response {
return Error(403, err.Error(), err) return Error(403, err.Error(), err)
} }
if err == m.ErrDashboardContainsInvalidAlertData { if validationErr, ok := err.(alerting.ValidationError); ok {
return Error(500, "Invalid alert data. Cannot save dashboard", err) return Error(422, validationErr.Error(), nil)
} }
if err != nil { if err != nil {
......
...@@ -9,6 +9,7 @@ import ( ...@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
...@@ -725,7 +726,7 @@ func TestDashboardApiEndpoint(t *testing.T) { ...@@ -725,7 +726,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
{SaveError: m.ErrDashboardVersionMismatch, ExpectedStatusCode: 412}, {SaveError: m.ErrDashboardVersionMismatch, ExpectedStatusCode: 412},
{SaveError: m.ErrDashboardTitleEmpty, ExpectedStatusCode: 400}, {SaveError: m.ErrDashboardTitleEmpty, ExpectedStatusCode: 400},
{SaveError: m.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400}, {SaveError: m.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400},
{SaveError: m.ErrDashboardContainsInvalidAlertData, ExpectedStatusCode: 500}, {SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422},
{SaveError: m.ErrDashboardFailedToUpdateAlertData, ExpectedStatusCode: 500}, {SaveError: m.ErrDashboardFailedToUpdateAlertData, ExpectedStatusCode: 500},
{SaveError: m.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500}, {SaveError: m.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500},
{SaveError: m.ErrDashboardTypeMismatch, ExpectedStatusCode: 400}, {SaveError: m.ErrDashboardTypeMismatch, ExpectedStatusCode: 400},
......
...@@ -21,7 +21,6 @@ var ( ...@@ -21,7 +21,6 @@ var (
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else") ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty") ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder") ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
ErrDashboardContainsInvalidAlertData = errors.New("Invalid alert data. Cannot save dashboard")
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data") ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists") ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id") ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
......
...@@ -2,6 +2,7 @@ package conditions ...@@ -2,6 +2,7 @@ package conditions
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
...@@ -31,12 +32,12 @@ type ThresholdEvaluator struct { ...@@ -31,12 +32,12 @@ type ThresholdEvaluator struct {
func newThresholdEvaluator(typ string, model *simplejson.Json) (*ThresholdEvaluator, error) { func newThresholdEvaluator(typ string, model *simplejson.Json) (*ThresholdEvaluator, error) {
params := model.Get("params").MustArray() params := model.Get("params").MustArray()
if len(params) == 0 { if len(params) == 0 {
return nil, alerting.ValidationError{Reason: "Evaluator missing threshold parameter"} return nil, fmt.Errorf("Evaluator missing threshold parameter")
} }
firstParam, ok := params[0].(json.Number) firstParam, ok := params[0].(json.Number)
if !ok { if !ok {
return nil, alerting.ValidationError{Reason: "Evaluator has invalid parameter"} return nil, fmt.Errorf("Evaluator has invalid parameter")
} }
defaultEval := &ThresholdEvaluator{Type: typ} defaultEval := &ThresholdEvaluator{Type: typ}
...@@ -107,7 +108,7 @@ func (e *RangedEvaluator) Eval(reducedValue null.Float) bool { ...@@ -107,7 +108,7 @@ func (e *RangedEvaluator) Eval(reducedValue null.Float) bool {
func NewAlertEvaluator(model *simplejson.Json) (AlertEvaluator, error) { func NewAlertEvaluator(model *simplejson.Json) (AlertEvaluator, error) {
typ := model.Get("type").MustString() typ := model.Get("type").MustString()
if typ == "" { if typ == "" {
return nil, alerting.ValidationError{Reason: "Evaluator missing type property"} return nil, fmt.Errorf("Evaluator missing type property")
} }
if inSlice(typ, defaultTypes) { if inSlice(typ, defaultTypes) {
...@@ -122,7 +123,7 @@ func NewAlertEvaluator(model *simplejson.Json) (AlertEvaluator, error) { ...@@ -122,7 +123,7 @@ func NewAlertEvaluator(model *simplejson.Json) (AlertEvaluator, error) {
return &NoValueEvaluator{}, nil return &NoValueEvaluator{}, nil
} }
return nil, alerting.ValidationError{Reason: "Evaluator invalid evaluator type: " + typ} return nil, fmt.Errorf("Evaluator invalid evaluator type: %s", typ)
} }
func inSlice(a string, list []string) bool { func inSlice(a string, list []string) bool {
......
...@@ -82,8 +82,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, ...@@ -82,8 +82,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
if collapsed && collapsedJSON.MustBool() { if collapsed && collapsedJSON.MustBool() {
// extract alerts from sub panels for collapsed panels // extract alerts from sub panels for collapsed panels
alertSlice, err := e.getAlertFromPanels(panel, alertSlice, err := e.getAlertFromPanels(panel, validateAlertFunc)
validateAlertFunc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -100,7 +99,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, ...@@ -100,7 +99,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
panelID, err := panel.Get("id").Int64() panelID, err := panel.Get("id").Int64()
if err != nil { if err != nil {
return nil, fmt.Errorf("panel id is required. err %v", err) return nil, ValidationError{Reason: "A numeric panel id property is missing"}
} }
// backward compatibility check, can be removed later // backward compatibility check, can be removed later
...@@ -146,7 +145,8 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, ...@@ -146,7 +145,8 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
datasource, err := e.lookupDatasourceID(dsName) datasource, err := e.lookupDatasourceID(dsName)
if err != nil { if err != nil {
return nil, err e.log.Debug("Error looking up datasource", "error", err)
return nil, ValidationError{Reason: fmt.Sprintf("Data source used by alert rule not found, alertName=%v, datasource=%s", alert.Name, dsName)}
} }
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id) jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
...@@ -167,8 +167,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, ...@@ -167,8 +167,7 @@ func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json,
} }
if !validateAlertFunc(alert) { if !validateAlertFunc(alert) {
e.log.Debug("Invalid Alert Data. Dashboard, Org or Panel ID is not correct", "alertName", alert.Name, "panelId", alert.PanelId) return nil, ValidationError{Reason: fmt.Sprintf("Panel id is not correct, alertName=%v, panelId=%v", alert.Name, alert.PanelId)}
return nil, m.ErrDashboardContainsInvalidAlertData
} }
alerts = append(alerts, alert) alerts = append(alerts, alert)
......
...@@ -258,7 +258,7 @@ func TestAlertRuleExtraction(t *testing.T) { ...@@ -258,7 +258,7 @@ func TestAlertRuleExtraction(t *testing.T) {
Convey("Should fail on save", func() { Convey("Should fail on save", func() {
_, err := extractor.GetAlerts() _, err := extractor.GetAlerts()
So(err, ShouldEqual, m.ErrDashboardContainsInvalidAlertData) So(err.Error(), ShouldEqual, "Alert validation error: Panel id is not correct, alertName=Influxdb, panelId=1")
}) })
}) })
}) })
......
...@@ -36,13 +36,13 @@ type ValidationError struct { ...@@ -36,13 +36,13 @@ type ValidationError struct {
} }
func (e ValidationError) Error() string { func (e ValidationError) Error() string {
extraInfo := "" extraInfo := e.Reason
if e.Alertid != 0 { if e.Alertid != 0 {
extraInfo = fmt.Sprintf("%s AlertId: %v", extraInfo, e.Alertid) extraInfo = fmt.Sprintf("%s AlertId: %v", extraInfo, e.Alertid)
} }
if e.PanelId != 0 { if e.PanelId != 0 {
extraInfo = fmt.Sprintf("%s PanelId: %v ", extraInfo, e.PanelId) extraInfo = fmt.Sprintf("%s PanelId: %v", extraInfo, e.PanelId)
} }
if e.DashboardId != 0 { if e.DashboardId != 0 {
...@@ -50,10 +50,10 @@ func (e ValidationError) Error() string { ...@@ -50,10 +50,10 @@ func (e ValidationError) Error() string {
} }
if e.Err != nil { if e.Err != nil {
return fmt.Sprintf("%s %s%s", e.Err.Error(), e.Reason, extraInfo) return fmt.Sprintf("Alert validation error: %s%s", e.Err.Error(), extraInfo)
} }
return fmt.Sprintf("Failed to extract alert.Reason: %s %s", e.Reason, extraInfo) return fmt.Sprintf("Alert validation error: %s", extraInfo)
} }
var ( var (
...@@ -128,7 +128,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) { ...@@ -128,7 +128,7 @@ func NewRuleFromDBAlert(ruleDef *m.Alert) (*Rule, error) {
} }
if len(model.Conditions) == 0 { if len(model.Conditions) == 0 {
return nil, fmt.Errorf("Alert is missing conditions") return nil, ValidationError{Reason: "Alert is missing conditions"}
} }
return model, nil return model, nil
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
...@@ -25,7 +26,9 @@ type DashboardProvisioningService interface { ...@@ -25,7 +26,9 @@ type DashboardProvisioningService interface {
// NewService factory for creating a new dashboard service // NewService factory for creating a new dashboard service
var NewService = func() DashboardService { var NewService = func() DashboardService {
return &dashboardServiceImpl{} return &dashboardServiceImpl{
log: log.New("dashboard-service"),
}
} }
// NewProvisioningService factory for creating a new dashboard provisioning service // NewProvisioningService factory for creating a new dashboard provisioning service
...@@ -45,6 +48,7 @@ type SaveDashboardDTO struct { ...@@ -45,6 +48,7 @@ type SaveDashboardDTO struct {
type dashboardServiceImpl struct { type dashboardServiceImpl struct {
orgId int64 orgId int64
user *models.SignedInUser user *models.SignedInUser
log log.Logger
} }
func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) { func (dr *dashboardServiceImpl) GetProvisionedDashboardData(name string) ([]*models.DashboardProvisioning, error) {
...@@ -89,7 +93,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO, ...@@ -89,7 +93,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
} }
if err := bus.Dispatch(&validateAlertsCmd); err != nil { if err := bus.Dispatch(&validateAlertsCmd); err != nil {
return nil, models.ErrDashboardContainsInvalidAlertData return nil, err
} }
} }
......
...@@ -117,12 +117,12 @@ func TestDashboardService(t *testing.T) { ...@@ -117,12 +117,12 @@ func TestDashboardService(t *testing.T) {
}) })
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error { bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return errors.New("error") return errors.New("Alert validation error")
}) })
dto.Dashboard = models.NewDashboard("Dash") dto.Dashboard = models.NewDashboard("Dash")
_, err := service.SaveDashboard(dto) _, err := service.SaveDashboard(dto)
So(err, ShouldEqual, models.ErrDashboardContainsInvalidAlertData) So(err.Error(), ShouldEqual, "Alert validation error")
}) })
}) })
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment