mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Aggregate save dashboard error (#26443)
* Chore: Aggregate save dashboard error * Use errors package for error detection Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com> Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
This commit is contained in:
parent
3c72b2f988
commit
f427f90a45
@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -195,9 +196,14 @@ func deleteDashboard(c *models.ReqContext) Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := dashboards.NewService().DeleteDashboard(dash.Id, c.OrgId)
|
err := dashboards.NewService().DeleteDashboard(dash.Id, c.OrgId)
|
||||||
if err == models.ErrDashboardCannotDeleteProvisionedDashboard {
|
if err != nil {
|
||||||
return Error(400, "Dashboard cannot be deleted because it was provisioned", err)
|
var dashboardErr models.DashboardErr
|
||||||
} else if err != nil {
|
if ok := errors.As(err, &dashboardErr); ok {
|
||||||
|
if errors.Is(err, models.ErrDashboardCannotDeleteProvisionedDashboard) {
|
||||||
|
return Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Error(500, "Failed to delete dashboard", err)
|
return Error(500, "Failed to delete dashboard", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,50 +273,36 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext, cmd models.SaveDashboa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func dashboardSaveErrorToApiResponse(err error) Response {
|
func dashboardSaveErrorToApiResponse(err error) Response {
|
||||||
if err == models.ErrDashboardTitleEmpty ||
|
var dashboardErr models.DashboardErr
|
||||||
err == models.ErrDashboardWithSameNameAsFolder ||
|
if ok := errors.As(err, &dashboardErr); ok {
|
||||||
err == models.ErrDashboardFolderWithSameNameAsDashboard ||
|
if body := dashboardErr.Body(); body != nil {
|
||||||
err == models.ErrDashboardTypeMismatch ||
|
return JSON(dashboardErr.StatusCode, body)
|
||||||
err == models.ErrDashboardInvalidUid ||
|
}
|
||||||
err == models.ErrDashboardUidToLong ||
|
if errors.Is(dashboardErr, models.ErrDashboardUpdateAccessDenied) {
|
||||||
err == models.ErrDashboardWithSameUIDExists ||
|
return Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
|
||||||
err == models.ErrFolderNotFound ||
|
}
|
||||||
err == models.ErrDashboardFolderCannotHaveParent ||
|
return Error(dashboardErr.StatusCode, dashboardErr.Error(), nil)
|
||||||
err == models.ErrDashboardFolderNameExists ||
|
}
|
||||||
err == models.ErrDashboardRefreshIntervalTooShort ||
|
|
||||||
err == models.ErrDashboardCannotSaveProvisionedDashboard {
|
if errors.Is(err, models.ErrFolderNotFound) {
|
||||||
return Error(400, err.Error(), nil)
|
return Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == models.ErrDashboardUpdateAccessDenied {
|
var validationErr alerting.ValidationError
|
||||||
return Error(403, err.Error(), err)
|
if ok := errors.As(err, &validationErr); ok {
|
||||||
}
|
|
||||||
|
|
||||||
if validationErr, ok := err.(alerting.ValidationError); ok {
|
|
||||||
return Error(422, validationErr.Error(), nil)
|
return Error(422, validationErr.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == models.ErrDashboardWithSameNameInFolderExists {
|
var pluginErr models.UpdatePluginDashboardError
|
||||||
return JSON(412, util.DynMap{"status": "name-exists", "message": err.Error()})
|
if ok := errors.As(err, &pluginErr); ok {
|
||||||
}
|
message := fmt.Sprintf("The dashboard belongs to plugin %s.", pluginErr.PluginId)
|
||||||
|
|
||||||
if err == models.ErrDashboardVersionMismatch {
|
|
||||||
return JSON(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
if pluginErr, ok := err.(models.UpdatePluginDashboardError); ok {
|
|
||||||
message := "The dashboard belongs to plugin " + pluginErr.PluginId + "."
|
|
||||||
// look up plugin name
|
// look up plugin name
|
||||||
if pluginDef, exist := plugins.Plugins[pluginErr.PluginId]; exist {
|
if pluginDef, exist := plugins.Plugins[pluginErr.PluginId]; exist {
|
||||||
message = "The dashboard belongs to plugin " + pluginDef.Name + "."
|
message = fmt.Sprintf("The dashboard belongs to plugin %s.", pluginDef.Name)
|
||||||
}
|
}
|
||||||
return JSON(412, util.DynMap{"status": "plugin-dashboard", "message": message})
|
return JSON(412, util.DynMap{"status": "plugin-dashboard", "message": message})
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == models.ErrDashboardNotFound {
|
|
||||||
return JSON(404, util.DynMap{"status": "not-found", "message": err.Error()})
|
|
||||||
}
|
|
||||||
|
|
||||||
return Error(500, "Failed to save dashboard", err)
|
return Error(500, "Failed to save dashboard", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,7 +788,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
|
|||||||
{SaveError: models.ErrDashboardFolderNameExists, ExpectedStatusCode: 400},
|
{SaveError: models.ErrDashboardFolderNameExists, ExpectedStatusCode: 400},
|
||||||
{SaveError: models.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403},
|
{SaveError: models.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403},
|
||||||
{SaveError: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
{SaveError: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
||||||
{SaveError: models.ErrDashboardUidToLong, ExpectedStatusCode: 400},
|
{SaveError: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
|
||||||
{SaveError: models.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400},
|
{SaveError: models.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400},
|
||||||
{SaveError: models.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412},
|
{SaveError: models.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412},
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ func toFolderError(err error) Response {
|
|||||||
err == models.ErrFolderWithSameUIDExists ||
|
err == models.ErrFolderWithSameUIDExists ||
|
||||||
err == models.ErrDashboardTypeMismatch ||
|
err == models.ErrDashboardTypeMismatch ||
|
||||||
err == models.ErrDashboardInvalidUid ||
|
err == models.ErrDashboardInvalidUid ||
|
||||||
err == models.ErrDashboardUidToLong {
|
err == models.ErrDashboardUidTooLong {
|
||||||
return Error(400, err.Error(), nil)
|
return Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ func TestFoldersApiEndpoint(t *testing.T) {
|
|||||||
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
|
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
|
||||||
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400},
|
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400},
|
||||||
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
||||||
{Error: models.ErrDashboardUidToLong, ExpectedStatusCode: 400},
|
{Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
|
||||||
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403},
|
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403},
|
||||||
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404},
|
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404},
|
||||||
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
|
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
|
||||||
@ -107,7 +107,7 @@ func TestFoldersApiEndpoint(t *testing.T) {
|
|||||||
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
|
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
|
||||||
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400},
|
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 400},
|
||||||
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
||||||
{Error: models.ErrDashboardUidToLong, ExpectedStatusCode: 400},
|
{Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
|
||||||
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403},
|
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403},
|
||||||
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404},
|
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404},
|
||||||
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
|
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
|
||||||
|
@ -2,7 +2,6 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -10,34 +9,130 @@ import (
|
|||||||
"github.com/gosimple/slug"
|
"github.com/gosimple/slug"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Typed errors
|
// Typed errors
|
||||||
var (
|
var (
|
||||||
ErrDashboardNotFound = errors.New("Dashboard not found")
|
ErrDashboardNotFound = DashboardErr{
|
||||||
ErrDashboardFolderNotFound = errors.New("Folder not found")
|
Reason: "Dashboard not found",
|
||||||
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
|
StatusCode: 404,
|
||||||
ErrDashboardWithSameUIDExists = errors.New("A dashboard with the same uid already exists")
|
Status: "not-found",
|
||||||
ErrDashboardWithSameNameInFolderExists = errors.New("A dashboard with the same name in the folder already exists")
|
}
|
||||||
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
|
ErrDashboardFolderNotFound = DashboardErr{
|
||||||
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
|
Reason: "Folder not found",
|
||||||
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
|
StatusCode: 404,
|
||||||
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
|
}
|
||||||
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
|
ErrDashboardSnapshotNotFound = DashboardErr{
|
||||||
ErrDashboardTypeMismatch = errors.New("Dashboard cannot be changed to a folder")
|
Reason: "Dashboard snapshot not found",
|
||||||
ErrDashboardFolderWithSameNameAsDashboard = errors.New("Folder name cannot be the same as one of its dashboards")
|
StatusCode: 404,
|
||||||
ErrDashboardWithSameNameAsFolder = errors.New("Dashboard name cannot be the same as folder")
|
}
|
||||||
ErrDashboardFolderNameExists = errors.New("A folder with that name already exists")
|
ErrDashboardWithSameUIDExists = DashboardErr{
|
||||||
ErrDashboardUpdateAccessDenied = errors.New("Access denied to save dashboard")
|
Reason: "A dashboard with the same uid already exists",
|
||||||
ErrDashboardInvalidUid = errors.New("uid contains illegal characters")
|
StatusCode: 400,
|
||||||
ErrDashboardUidToLong = errors.New("uid to long. max 40 characters")
|
}
|
||||||
ErrDashboardCannotSaveProvisionedDashboard = errors.New("Cannot save provisioned dashboard")
|
ErrDashboardWithSameNameInFolderExists = DashboardErr{
|
||||||
ErrDashboardRefreshIntervalTooShort = errors.New("Dashboard refresh interval is too low")
|
Reason: "A dashboard with the same name in the folder already exists",
|
||||||
ErrDashboardCannotDeleteProvisionedDashboard = errors.New("provisioned dashboard cannot be deleted")
|
StatusCode: 412,
|
||||||
ErrDashboardIdentifierNotSet = errors.New("Unique identifier needed to be able to get a dashboard")
|
Status: "name-exists",
|
||||||
RootFolderName = "General"
|
}
|
||||||
|
ErrDashboardVersionMismatch = DashboardErr{
|
||||||
|
Reason: "The dashboard has been changed by someone else",
|
||||||
|
StatusCode: 412,
|
||||||
|
Status: "version-mismatch",
|
||||||
|
}
|
||||||
|
ErrDashboardTitleEmpty = DashboardErr{
|
||||||
|
Reason: "Dashboard title cannot be empty",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardFolderCannotHaveParent = DashboardErr{
|
||||||
|
Reason: "A Dashboard Folder cannot be added to another folder",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardsWithSameSlugExists = DashboardErr{
|
||||||
|
Reason: "Multiple dashboards with the same slug exists",
|
||||||
|
StatusCode: 412,
|
||||||
|
}
|
||||||
|
ErrDashboardFailedGenerateUniqueUid = DashboardErr{
|
||||||
|
Reason: "Failed to generate unique dashboard id",
|
||||||
|
StatusCode: 500,
|
||||||
|
}
|
||||||
|
ErrDashboardTypeMismatch = DashboardErr{
|
||||||
|
Reason: "Dashboard cannot be changed to a folder",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardFolderWithSameNameAsDashboard = DashboardErr{
|
||||||
|
Reason: "Folder name cannot be the same as one of its dashboards",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardWithSameNameAsFolder = DashboardErr{
|
||||||
|
Reason: "Dashboard name cannot be the same as folder",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardFolderNameExists = DashboardErr{
|
||||||
|
Reason: "A folder with that name already exists",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardUpdateAccessDenied = DashboardErr{
|
||||||
|
Reason: "Access denied to save dashboard",
|
||||||
|
StatusCode: 403,
|
||||||
|
}
|
||||||
|
ErrDashboardInvalidUid = DashboardErr{
|
||||||
|
Reason: "uid contains illegal characters",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardUidTooLong = DashboardErr{
|
||||||
|
Reason: "uid too long, max 40 characters",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardCannotSaveProvisionedDashboard = DashboardErr{
|
||||||
|
Reason: "Cannot save provisioned dashboard",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardRefreshIntervalTooShort = DashboardErr{
|
||||||
|
Reason: "Dashboard refresh interval is too low",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardCannotDeleteProvisionedDashboard = DashboardErr{
|
||||||
|
Reason: "provisioned dashboard cannot be deleted",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
ErrDashboardIdentifierNotSet = DashboardErr{
|
||||||
|
Reason: "Unique identifier needed to be able to get a dashboard",
|
||||||
|
StatusCode: 400,
|
||||||
|
}
|
||||||
|
RootFolderName = "General"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DashboardErr represents a dashboard error.
|
||||||
|
type DashboardErr struct {
|
||||||
|
StatusCode int
|
||||||
|
Status string
|
||||||
|
Reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal returns whether equal to another DashboardErr.
|
||||||
|
func (e DashboardErr) Equal(o DashboardErr) bool {
|
||||||
|
return o.StatusCode == e.StatusCode && o.Status == e.Status && o.Reason == e.Reason
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the error message.
|
||||||
|
func (e DashboardErr) Error() string {
|
||||||
|
if e.Reason != "" {
|
||||||
|
return e.Reason
|
||||||
|
}
|
||||||
|
return "Dashboard Error"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body returns the error's response body, if applicable.
|
||||||
|
func (e DashboardErr) Body() util.DynMap {
|
||||||
|
if e.Status == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return util.DynMap{"status": e.Status, "message": e.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
type UpdatePluginDashboardError struct {
|
type UpdatePluginDashboardError struct {
|
||||||
PluginId string
|
PluginId string
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,7 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
|
|||||||
if !util.IsValidShortUID(dash.Uid) {
|
if !util.IsValidShortUID(dash.Uid) {
|
||||||
return nil, models.ErrDashboardInvalidUid
|
return nil, models.ErrDashboardInvalidUid
|
||||||
} else if len(dash.Uid) > 40 {
|
} else if len(dash.Uid) > 40 {
|
||||||
return nil, models.ErrDashboardUidToLong
|
return nil, models.ErrDashboardUidTooLong
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateDashboardRefreshInterval(dash); err != nil {
|
if err := validateDashboardRefreshInterval(dash); err != nil {
|
||||||
|
@ -75,7 +75,7 @@ func TestDashboardService(t *testing.T) {
|
|||||||
{Uid: "asdf90_-", Error: nil},
|
{Uid: "asdf90_-", Error: nil},
|
||||||
{Uid: "asdf/90", Error: models.ErrDashboardInvalidUid},
|
{Uid: "asdf/90", Error: models.ErrDashboardInvalidUid},
|
||||||
{Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil},
|
{Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil},
|
||||||
{Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: models.ErrDashboardUidToLong},
|
{Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: models.ErrDashboardUidTooLong},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
Loading…
Reference in New Issue
Block a user