chore/backend: move dashboard errors to dashboard service (#51593)

* chore/backend: move dashboard errors to dashboard service

Dashboard-related models are slowly moving out of the models package and into dashboard services. This commit moves dashboard-related errors; the rest will come in later commits.

There are no logical code changes, this is only a structural (package) move.

* lint lint lint
This commit is contained in:
Kristin Laemmert 2022-06-30 09:31:54 -04:00 committed by GitHub
parent a1fb73c503
commit 9de00c8eb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 489 additions and 466 deletions

View File

@ -7,15 +7,15 @@ import (
"net/http" "net/http"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
// ToDashboardErrorResponse returns a different response status according to the dashboard error type // ToDashboardErrorResponse returns a different response status according to the dashboard error type
func ToDashboardErrorResponse(ctx context.Context, pluginStore plugins.Store, err error) response.Response { func ToDashboardErrorResponse(ctx context.Context, pluginStore plugins.Store, err error) response.Response {
var dashboardErr models.DashboardErr var dashboardErr dashboards.DashboardErr
if ok := errors.As(err, &dashboardErr); ok { if ok := errors.As(err, &dashboardErr); ok {
if body := dashboardErr.Body(); body != nil { if body := dashboardErr.Body(); body != nil {
return response.JSON(dashboardErr.StatusCode, body) return response.JSON(dashboardErr.StatusCode, body)
@ -26,7 +26,7 @@ func ToDashboardErrorResponse(ctx context.Context, pluginStore plugins.Store, er
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), nil) return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), nil)
} }
if errors.Is(err, models.ErrFolderNotFound) { if errors.Is(err, dashboards.ErrFolderNotFound) {
return response.Error(http.StatusBadRequest, err.Error(), nil) return response.Error(http.StatusBadRequest, err.Error(), nil)
} }
@ -35,7 +35,7 @@ func ToDashboardErrorResponse(ctx context.Context, pluginStore plugins.Store, er
return response.Error(http.StatusUnprocessableEntity, validationErr.Error(), err) return response.Error(http.StatusUnprocessableEntity, validationErr.Error(), err)
} }
var pluginErr models.UpdatePluginDashboardError var pluginErr dashboards.UpdatePluginDashboardError
if ok := errors.As(err, &pluginErr); ok { if ok := errors.As(err, &pluginErr); ok {
message := fmt.Sprintf("The dashboard belongs to plugin %s.", pluginErr.PluginId) message := fmt.Sprintf("The dashboard belongs to plugin %s.", pluginErr.PluginId)
// look up plugin name // look up plugin name

View File

@ -4,40 +4,40 @@ import (
"errors" "errors"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
// ToFolderErrorResponse returns a different response status according to the folder error type // ToFolderErrorResponse returns a different response status according to the folder error type
func ToFolderErrorResponse(err error) response.Response { func ToFolderErrorResponse(err error) response.Response {
var dashboardErr models.DashboardErr var dashboardErr dashboards.DashboardErr
if ok := errors.As(err, &dashboardErr); ok { if ok := errors.As(err, &dashboardErr); ok {
return response.Error(dashboardErr.StatusCode, err.Error(), err) return response.Error(dashboardErr.StatusCode, err.Error(), err)
} }
if errors.Is(err, models.ErrFolderTitleEmpty) || if errors.Is(err, dashboards.ErrFolderTitleEmpty) ||
errors.Is(err, models.ErrDashboardTypeMismatch) || errors.Is(err, dashboards.ErrDashboardTypeMismatch) ||
errors.Is(err, models.ErrDashboardInvalidUid) || errors.Is(err, dashboards.ErrDashboardInvalidUid) ||
errors.Is(err, models.ErrDashboardUidTooLong) || errors.Is(err, dashboards.ErrDashboardUidTooLong) ||
errors.Is(err, models.ErrFolderContainsAlertRules) { errors.Is(err, dashboards.ErrFolderContainsAlertRules) {
return response.Error(400, err.Error(), nil) return response.Error(400, err.Error(), nil)
} }
if errors.Is(err, models.ErrFolderAccessDenied) { if errors.Is(err, dashboards.ErrFolderAccessDenied) {
return response.Error(403, "Access denied", err) return response.Error(403, "Access denied", err)
} }
if errors.Is(err, models.ErrFolderNotFound) { if errors.Is(err, dashboards.ErrFolderNotFound) {
return response.JSON(404, util.DynMap{"status": "not-found", "message": models.ErrFolderNotFound.Error()}) return response.JSON(404, util.DynMap{"status": "not-found", "message": dashboards.ErrFolderNotFound.Error()})
} }
if errors.Is(err, models.ErrFolderSameNameExists) || if errors.Is(err, dashboards.ErrFolderSameNameExists) ||
errors.Is(err, models.ErrFolderWithSameUIDExists) { errors.Is(err, dashboards.ErrFolderWithSameUIDExists) {
return response.Error(409, err.Error(), nil) return response.Error(409, err.Error(), nil)
} }
if errors.Is(err, models.ErrFolderVersionMismatch) { if errors.Is(err, dashboards.ErrFolderVersionMismatch) {
return response.JSON(412, util.DynMap{"status": "version-mismatch", "message": models.ErrFolderVersionMismatch.Error()}) return response.JSON(412, util.DynMap{"status": "version-mismatch", "message": dashboards.ErrFolderVersionMismatch.Error()})
} }
return response.Error(500, "Folder API error", err) return response.Error(500, "Folder API error", err)

View File

@ -145,7 +145,7 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response {
if dash.FolderId > 0 { if dash.FolderId > 0 {
query := models.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId} query := models.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId}
if err := hs.dashboardService.GetDashboard(c.Req.Context(), &query); err != nil { if err := hs.dashboardService.GetDashboard(c.Req.Context(), &query); err != nil {
if errors.Is(err, models.ErrFolderNotFound) { if errors.Is(err, dashboards.ErrFolderNotFound) {
return response.Error(404, "Folder not found", err) return response.Error(404, "Folder not found", err)
} }
return response.Error(500, "Dashboard folder could not be read", err) return response.Error(500, "Dashboard folder could not be read", err)
@ -264,9 +264,9 @@ func (hs *HTTPServer) deleteDashboard(c *models.ReqContext) response.Response {
err = hs.dashboardService.DeleteDashboard(c.Req.Context(), dash.Id, c.OrgId) err = hs.dashboardService.DeleteDashboard(c.Req.Context(), dash.Id, c.OrgId)
if err != nil { if err != nil {
var dashboardErr models.DashboardErr var dashboardErr dashboards.DashboardErr
if ok := errors.As(err, &dashboardErr); ok { if ok := errors.As(err, &dashboardErr); ok {
if errors.Is(err, models.ErrDashboardCannotDeleteProvisionedDashboard) { if errors.Is(err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard) {
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err) return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), err)
} }
} }
@ -333,7 +333,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
if cmd.FolderUid != "" { if cmd.FolderUid != "" {
folder, err := hs.folderService.GetFolderByUID(ctx, c.SignedInUser, c.OrgId, cmd.FolderUid) folder, err := hs.folderService.GetFolderByUID(ctx, c.SignedInUser, c.OrgId, cmd.FolderUid)
if err != nil { if err != nil {
if errors.Is(err, models.ErrFolderNotFound) { if errors.Is(err, dashboards.ErrFolderNotFound) {
return response.Error(400, "Folder not found", err) return response.Error(400, "Folder not found", err)
} }
return response.Error(500, "Error while checking folder ID", err) return response.Error(500, "Error while checking folder ID", err)
@ -362,7 +362,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa
provisioningData = data provisioningData = data
} else if dash.Uid != "" { } else if dash.Uid != "" {
data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardUID(dash.OrgId, dash.Uid) data, err := hs.dashboardProvisioningService.GetProvisionedDashboardDataByDashboardUID(dash.OrgId, dash.Uid)
if err != nil && !errors.Is(err, models.ErrProvisionedDashboardNotFound) && !errors.Is(err, models.ErrDashboardNotFound) { if err != nil && !errors.Is(err, dashboards.ErrProvisionedDashboardNotFound) && !errors.Is(err, dashboards.ErrDashboardNotFound) {
return response.Error(500, "Error while checking if dashboard is provisioned", err) return response.Error(500, "Error while checking if dashboard is provisioned", err)
} }
provisioningData = data provisioningData = data

View File

@ -131,7 +131,7 @@ func (hs *HTTPServer) QueryPublicDashboard(c *models.ReqContext) response.Respon
// util to help us unpack a dashboard err or use default http code and message // util to help us unpack a dashboard err or use default http code and message
func handleDashboardErr(defaultCode int, defaultMsg string, err error) response.Response { func handleDashboardErr(defaultCode int, defaultMsg string, err error) response.Response {
var dashboardErr models.DashboardErr var dashboardErr dashboards.DashboardErr
if ok := errors.As(err, &dashboardErr); ok { if ok := errors.As(err, &dashboardErr); ok {
return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), dashboardErr) return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), dashboardErr)

View File

@ -87,7 +87,7 @@ func TestAPIGetPublicDashboard(t *testing.T) {
AccessToken: accessToken, AccessToken: accessToken,
ExpectedHttpResponse: http.StatusNotFound, ExpectedHttpResponse: http.StatusNotFound,
publicDashboardResult: nil, publicDashboardResult: nil,
publicDashboardErr: models.ErrPublicDashboardNotFound, publicDashboardErr: dashboards.ErrPublicDashboardNotFound,
}, },
} }
@ -153,7 +153,7 @@ func TestAPIGetPublicDashboardConfig(t *testing.T) {
DashboardUid: "77777", DashboardUid: "77777",
ExpectedHttpResponse: http.StatusNotFound, ExpectedHttpResponse: http.StatusNotFound,
PublicDashboardResult: nil, PublicDashboardResult: nil,
PublicDashboardError: models.ErrDashboardNotFound, PublicDashboardError: dashboards.ErrDashboardNotFound,
}, },
{ {
Name: "returns 500 when internal server error", Name: "returns 500 when internal server error",
@ -218,7 +218,7 @@ func TestApiSavePublicDashboardConfig(t *testing.T) {
Name: "returns 404 when dashboard not found", Name: "returns 404 when dashboard not found",
ExpectedHttpResponse: http.StatusNotFound, ExpectedHttpResponse: http.StatusNotFound,
publicDashboardConfig: &models.PublicDashboard{}, publicDashboardConfig: &models.PublicDashboard{},
saveDashboardError: models.ErrDashboardNotFound, saveDashboardError: dashboards.ErrDashboardNotFound,
}, },
} }

View File

@ -13,6 +13,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/dashboardsnapshots"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -272,11 +273,11 @@ func (hs *HTTPServer) DeleteDashboardSnapshot(c *models.ReqContext) response.Res
guardian := guardian.New(c.Req.Context(), dashboardID, c.OrgId, c.SignedInUser) guardian := guardian.New(c.Req.Context(), dashboardID, c.OrgId, c.SignedInUser)
canEdit, err := guardian.CanEdit() canEdit, err := guardian.CanEdit()
// check for permissions only if the dahboard is found // check for permissions only if the dahboard is found
if err != nil && !errors.Is(err, models.ErrDashboardNotFound) { if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) {
return response.Error(500, "Error while checking permissions for snapshot", err) return response.Error(500, "Error while checking permissions for snapshot", err)
} }
if !canEdit && query.Result.UserId != c.SignedInUser.UserId && !errors.Is(err, models.ErrDashboardNotFound) { if !canEdit && query.Result.UserId != c.SignedInUser.UserId && !errors.Is(err, dashboards.ErrDashboardNotFound) {
return response.Error(403, "Access denied to this snapshot", nil) return response.Error(403, "Access denied to this snapshot", nil)
} }

View File

@ -9,7 +9,6 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/grafana/grafana/pkg/framework/coremodel/registry"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -18,6 +17,7 @@ import (
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/framework/coremodel/registry"
"github.com/grafana/grafana/pkg/infra/usagestats" "github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
@ -679,24 +679,24 @@ func TestDashboardAPIEndpoint(t *testing.T) {
SaveError error SaveError error
ExpectedStatusCode int ExpectedStatusCode int
}{ }{
{SaveError: models.ErrDashboardNotFound, ExpectedStatusCode: 404}, {SaveError: dashboards.ErrDashboardNotFound, ExpectedStatusCode: 404},
{SaveError: models.ErrFolderNotFound, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrFolderNotFound, ExpectedStatusCode: 400},
{SaveError: models.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400},
{SaveError: models.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412}, {SaveError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412},
{SaveError: models.ErrDashboardVersionMismatch, ExpectedStatusCode: 412}, {SaveError: dashboards.ErrDashboardVersionMismatch, ExpectedStatusCode: 412},
{SaveError: models.ErrDashboardTitleEmpty, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardTitleEmpty, ExpectedStatusCode: 400},
{SaveError: models.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400},
{SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422}, {SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422},
{SaveError: models.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500}, {SaveError: dashboards.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500},
{SaveError: models.ErrDashboardTypeMismatch, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardTypeMismatch, ExpectedStatusCode: 400},
{SaveError: models.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400},
{SaveError: models.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400},
{SaveError: models.ErrDashboardFolderNameExists, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardFolderNameExists, ExpectedStatusCode: 400},
{SaveError: models.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403}, {SaveError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403},
{SaveError: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
{SaveError: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
{SaveError: models.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400}, {SaveError: dashboards.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400},
{SaveError: models.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412}, {SaveError: dashboards.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412},
} }
cmd := models.SaveDashboardCommand{ cmd := models.SaveDashboardCommand{

View File

@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"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"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
@ -24,7 +25,7 @@ func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Res
g := guardian.New(c.Req.Context(), folder.Id, c.OrgId, c.SignedInUser) g := guardian.New(c.Req.Context(), folder.Id, c.OrgId, c.SignedInUser)
if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin {
return apierrors.ToFolderErrorResponse(models.ErrFolderAccessDenied) return apierrors.ToFolderErrorResponse(dashboards.ErrFolderAccessDenied)
} }
acl, err := g.GetAcl() acl, err := g.GetAcl()
@ -78,7 +79,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res
} }
if !canAdmin { if !canAdmin {
return apierrors.ToFolderErrorResponse(models.ErrFolderAccessDenied) return apierrors.ToFolderErrorResponse(dashboards.ErrFolderAccessDenied)
} }
var items []*models.DashboardAcl var items []*models.DashboardAcl

View File

@ -49,7 +49,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
} }
t.Run("Given folder not exists", func(t *testing.T) { t.Run("Given folder not exists", func(t *testing.T) {
folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrFolderNotFound).Twice() folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, dashboards.ErrFolderNotFound).Twice()
mockSQLStore := mockstore.NewSQLStoreMock() mockSQLStore := mockstore.NewSQLStoreMock()
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {
callGetFolderPermissions(sc, hs) callGetFolderPermissions(sc, hs)
@ -81,7 +81,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) {
}) })
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false}) guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false})
folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrFolderAccessDenied).Twice() folderService.On("GetFolderByUID", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, dashboards.ErrFolderAccessDenied).Twice()
mockSQLStore := mockstore.NewSQLStoreMock() mockSQLStore := mockstore.NewSQLStoreMock()
loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) {

View File

@ -7,6 +7,10 @@ import (
"net/http" "net/http"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
@ -19,9 +23,6 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/mockstore" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web/webtest" "github.com/grafana/grafana/pkg/web/webtest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
) )
func TestFoldersAPIEndpoint(t *testing.T) { func TestFoldersAPIEndpoint(t *testing.T) {
@ -55,15 +56,15 @@ func TestFoldersAPIEndpoint(t *testing.T) {
Error error Error error
ExpectedStatusCode int ExpectedStatusCode int
}{ }{
{Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, {Error: dashboards.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409},
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, {Error: dashboards.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 409}, {Error: dashboards.ErrFolderSameNameExists, ExpectedStatusCode: 409},
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, {Error: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
{Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, {Error: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, {Error: dashboards.ErrFolderAccessDenied, ExpectedStatusCode: 403},
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, {Error: dashboards.ErrFolderNotFound, ExpectedStatusCode: 404},
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, {Error: dashboards.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
{Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, {Error: dashboards.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500},
} }
cmd := models.CreateFolderCommand{ cmd := models.CreateFolderCommand{
@ -110,15 +111,15 @@ func TestFoldersAPIEndpoint(t *testing.T) {
Error error Error error
ExpectedStatusCode int ExpectedStatusCode int
}{ }{
{Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, {Error: dashboards.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409},
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, {Error: dashboards.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 409}, {Error: dashboards.ErrFolderSameNameExists, ExpectedStatusCode: 409},
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, {Error: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
{Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, {Error: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, {Error: dashboards.ErrFolderAccessDenied, ExpectedStatusCode: 403},
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, {Error: dashboards.ErrFolderNotFound, ExpectedStatusCode: 404},
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, {Error: dashboards.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
{Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, {Error: dashboards.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500},
} }
cmd := models.UpdateFolderCommand{ cmd := models.UpdateFolderCommand{

View File

@ -10,173 +10,10 @@ import (
"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"
) )
const RootFolderName = "General" const RootFolderName = "General"
// Typed errors
var (
ErrDashboardNotFound = DashboardErr{
Reason: "Dashboard not found",
StatusCode: 404,
Status: "not-found",
}
ErrDashboardCorrupt = DashboardErr{
Reason: "Dashboard data is missing or corrupt",
StatusCode: 500,
Status: "not-found",
}
ErrDashboardPanelNotFound = DashboardErr{
Reason: "Dashboard panel not found",
StatusCode: 404,
Status: "not-found",
}
ErrDashboardFolderNotFound = DashboardErr{
Reason: "Folder not found",
StatusCode: 404,
}
ErrDashboardSnapshotNotFound = DashboardErr{
Reason: "Dashboard snapshot not found",
StatusCode: 404,
}
ErrDashboardWithSameUIDExists = DashboardErr{
Reason: "A dashboard with the same uid already exists",
StatusCode: 400,
}
ErrDashboardWithSameNameInFolderExists = DashboardErr{
Reason: "A dashboard with the same name in the folder already exists",
StatusCode: 412,
Status: "name-exists",
}
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,
Status: "empty-name",
}
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,
Status: "name-match",
}
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,
}
ErrDashboardIdentifierInvalid = DashboardErr{
Reason: "Dashboard ID not a number",
StatusCode: 400,
}
ErrDashboardPanelIdentifierInvalid = DashboardErr{
Reason: "Dashboard panel ID not a number",
StatusCode: 400,
}
ErrDashboardOrPanelIdentifierNotSet = DashboardErr{
Reason: "Unique identifier needed to be able to get a dashboard panel",
StatusCode: 400,
}
ErrProvisionedDashboardNotFound = DashboardErr{
Reason: "Dashboard is not provisioned",
StatusCode: 404,
Status: "not-found",
}
ErrDashboardThumbnailNotFound = DashboardErr{
Reason: "Dashboard thumbnail not found",
StatusCode: 404,
Status: "not-found",
}
)
// 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 {
PluginId string
}
func (d UpdatePluginDashboardError) Error() string {
return "Dashboard belongs to plugin"
}
const ( const (
DashTypeDB = "db" DashTypeDB = "db"
DashTypeSnapshot = "snapshot" DashTypeSnapshot = "snapshot"

View File

@ -6,31 +6,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
) )
var (
ErrPublicDashboardFailedGenerateUniqueUid = DashboardErr{
Reason: "Failed to generate unique public dashboard id",
StatusCode: 500,
}
ErrPublicDashboardFailedGenerateAccesstoken = DashboardErr{
Reason: "Failed to public dashboard access token",
StatusCode: 500,
}
ErrPublicDashboardNotFound = DashboardErr{
Reason: "Public dashboard not found",
StatusCode: 404,
Status: "not-found",
}
ErrPublicDashboardPanelNotFound = DashboardErr{
Reason: "Panel not found in dashboard",
StatusCode: 404,
Status: "not-found",
}
ErrPublicDashboardIdentifierNotSet = DashboardErr{
Reason: "No Uid for public dashboard specified",
StatusCode: 400,
}
)
type PublicDashboard struct { type PublicDashboard struct {
Uid string `json:"uid" xorm:"pk uid"` Uid string `json:"uid" xorm:"pk uid"`
DashboardUid string `json:"dashboardUid" xorm:"dashboard_uid"` DashboardUid string `json:"dashboardUid" xorm:"dashboard_uid"`

View File

@ -1,24 +1,10 @@
package models package models
import ( import (
"errors"
"strings" "strings"
"time" "time"
) )
// Typed errors
var (
ErrFolderNotFound = errors.New("folder not found")
ErrFolderVersionMismatch = errors.New("the folder has been changed by someone else")
ErrFolderTitleEmpty = errors.New("folder title cannot be empty")
ErrFolderWithSameUIDExists = errors.New("a folder/dashboard with the same uid already exists")
ErrFolderInvalidUID = errors.New("invalid uid for folder provided")
ErrFolderSameNameExists = errors.New("a folder or dashboard in the general folder with the same name already exists")
ErrFolderFailedGenerateUniqueUid = errors.New("failed to generate unique folder ID")
ErrFolderAccessDenied = errors.New("access denied to folder")
ErrFolderContainsAlertRules = errors.New("folder contains alert rules")
)
type Folder struct { type Folder struct {
Id int64 Id int64
Uid string Uid string

View File

@ -64,12 +64,12 @@ func TestNewFolderNameScopeResolver(t *testing.T) {
_, resolver := NewFolderNameScopeResolver(dashboardStore) _, resolver := NewFolderNameScopeResolver(dashboardStore)
orgId := rand.Int63() orgId := rand.Int63()
dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrDashboardNotFound).Once() dashboardStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once()
scope := "folders:name:" + util.GenerateShortUID() scope := "folders:name:" + util.GenerateShortUID()
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope) resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
require.ErrorIs(t, err, models.ErrDashboardNotFound) require.ErrorIs(t, err, ErrDashboardNotFound)
require.Nil(t, resolvedScopes) require.Nil(t, resolvedScopes)
}) })
} }
@ -136,11 +136,11 @@ func TestNewFolderIDScopeResolver(t *testing.T) {
_, resolver := NewFolderIDScopeResolver(dashboardStore) _, resolver := NewFolderIDScopeResolver(dashboardStore)
orgId := rand.Int63() orgId := rand.Int63()
dashboardStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, models.ErrDashboardNotFound).Once() dashboardStore.On("GetFolderByID", mock.Anything, mock.Anything, mock.Anything).Return(nil, ErrDashboardNotFound).Once()
scope := "folders:id:10" scope := "folders:id:10"
resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope) resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope)
require.ErrorIs(t, err, models.ErrDashboardNotFound) require.ErrorIs(t, err, ErrDashboardNotFound)
require.Nil(t, resolvedScopes) require.Nil(t, resolvedScopes)
}) })
} }

View File

@ -60,7 +60,7 @@ func (d *DashboardStore) ValidateDashboardBeforeSave(dashboard *models.Dashboard
func (d *DashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*models.Folder, error) { func (d *DashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*models.Folder, error) {
if title == "" { if title == "" {
return nil, models.ErrFolderTitleEmpty return nil, dashboards.ErrFolderTitleEmpty
} }
// there is a unique constraint on org_id, folder_id, title // there is a unique constraint on org_id, folder_id, title
@ -72,7 +72,7 @@ func (d *DashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, titl
return err return err
} }
if !has { if !has {
return models.ErrFolderNotFound return dashboards.ErrFolderNotFound
} }
dashboard.SetId(dashboard.Id) dashboard.SetId(dashboard.Id)
dashboard.SetUid(dashboard.Uid) dashboard.SetUid(dashboard.Uid)
@ -89,7 +89,7 @@ func (d *DashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int6
return err return err
} }
if !has { if !has {
return models.ErrFolderNotFound return dashboards.ErrFolderNotFound
} }
dashboard.SetId(dashboard.Id) dashboard.SetId(dashboard.Id)
dashboard.SetUid(dashboard.Uid) dashboard.SetUid(dashboard.Uid)
@ -103,7 +103,7 @@ func (d *DashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int6
func (d *DashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*models.Folder, error) { func (d *DashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*models.Folder, error) {
if uid == "" { if uid == "" {
return nil, models.ErrDashboardIdentifierNotSet return nil, dashboards.ErrDashboardIdentifierNotSet
} }
dashboard := models.Dashboard{OrgId: orgID, FolderId: 0, Uid: uid} dashboard := models.Dashboard{OrgId: orgID, FolderId: 0, Uid: uid}
@ -113,7 +113,7 @@ func (d *DashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid st
return err return err
} }
if !has { if !has {
return models.ErrFolderNotFound return dashboards.ErrFolderNotFound
} }
dashboard.SetId(dashboard.Id) dashboard.SetId(dashboard.Id)
dashboard.SetUid(dashboard.Uid) dashboard.SetUid(dashboard.Uid)
@ -147,15 +147,14 @@ func (d *DashboardStore) GetProvisionedDataByDashboardUID(orgID int64, dashboard
return err return err
} }
if !exists { if !exists {
return models. return dashboards.ErrDashboardNotFound
ErrDashboardNotFound
} }
exists, err = sess.Where("dashboard_id = ?", dashboard.Id).Get(&provisionedDashboard) exists, err = sess.Where("dashboard_id = ?", dashboard.Id).Get(&provisionedDashboard)
if err != nil { if err != nil {
return err return err
} }
if !exists { if !exists {
return models.ErrProvisionedDashboardNotFound return dashboards.ErrProvisionedDashboardNotFound
} }
return nil return nil
}) })
@ -267,7 +266,7 @@ func (d *DashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context
for _, deleteDashCommand := range result { for _, deleteDashCommand := range result {
err := d.DeleteDashboard(ctx, &models.DeleteDashboardCommand{Id: deleteDashCommand.DashboardId}) err := d.DeleteDashboard(ctx, &models.DeleteDashboardCommand{Id: deleteDashCommand.DashboardId})
if err != nil && !errors.Is(err, models.ErrDashboardNotFound) { if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) {
return err return err
} }
} }
@ -289,7 +288,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode
} }
if !dashWithIdExists { if !dashWithIdExists {
return false, models.ErrDashboardNotFound return false, dashboards.ErrDashboardNotFound
} }
if dash.Uid == "" { if dash.Uid == "" {
@ -317,7 +316,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode
} }
if !folderExists { if !folderExists {
return false, models.ErrDashboardFolderNotFound return false, dashboards.ErrDashboardFolderNotFound
} }
} }
@ -326,7 +325,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode
} }
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id { if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
return false, models.ErrDashboardWithSameUIDExists return false, dashboards.ErrDashboardWithSameUIDExists
} }
existing := existingById existing := existingById
@ -339,7 +338,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode
if (existing.IsFolder && !dash.IsFolder) || if (existing.IsFolder && !dash.IsFolder) ||
(!existing.IsFolder && dash.IsFolder) { (!existing.IsFolder && dash.IsFolder) {
return isParentFolderChanged, models.ErrDashboardTypeMismatch return isParentFolderChanged, dashboards.ErrDashboardTypeMismatch
} }
if !dash.IsFolder && dash.FolderId != existing.FolderId { if !dash.IsFolder && dash.FolderId != existing.FolderId {
@ -351,13 +350,13 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode
if overwrite { if overwrite {
dash.SetVersion(existing.Version) dash.SetVersion(existing.Version)
} else { } else {
return isParentFolderChanged, models.ErrDashboardVersionMismatch return isParentFolderChanged, dashboards.ErrDashboardVersionMismatch
} }
} }
// do not allow plugin dashboard updates without overwrite flag // do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && !overwrite { if existing.PluginId != "" && !overwrite {
return isParentFolderChanged, models.UpdatePluginDashboardError{PluginId: existing.PluginId} return isParentFolderChanged, dashboards.UpdatePluginDashboardError{PluginId: existing.PluginId}
} }
return isParentFolderChanged, nil return isParentFolderChanged, nil
@ -374,11 +373,11 @@ func getExistingDashboardByTitleAndFolder(sess *sqlstore.DBSession, dash *models
if exists && dash.Id != existing.Id { if exists && dash.Id != existing.Id {
if existing.IsFolder && !dash.IsFolder { if existing.IsFolder && !dash.IsFolder {
return isParentFolderChanged, models.ErrDashboardWithSameNameAsFolder return isParentFolderChanged, dashboards.ErrDashboardWithSameNameAsFolder
} }
if !existing.IsFolder && dash.IsFolder { if !existing.IsFolder && dash.IsFolder {
return isParentFolderChanged, models.ErrDashboardFolderWithSameNameAsDashboard return isParentFolderChanged, dashboards.ErrDashboardFolderWithSameNameAsDashboard
} }
if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) { if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) {
@ -390,7 +389,7 @@ func getExistingDashboardByTitleAndFolder(sess *sqlstore.DBSession, dash *models
dash.SetUid(existing.Uid) dash.SetUid(existing.Uid)
dash.SetVersion(existing.Version) dash.SetVersion(existing.Version)
} else { } else {
return isParentFolderChanged, models.ErrDashboardWithSameNameInFolderExists return isParentFolderChanged, dashboards.ErrDashboardWithSameNameInFolderExists
} }
} }
@ -413,7 +412,7 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e
return err return err
} }
if !dashWithIdExists { if !dashWithIdExists {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
// check for is someone else has written in between // check for is someone else has written in between
@ -421,13 +420,13 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e
if cmd.Overwrite { if cmd.Overwrite {
dash.SetVersion(existing.Version) dash.SetVersion(existing.Version)
} else { } else {
return models.ErrDashboardVersionMismatch return dashboards.ErrDashboardVersionMismatch
} }
} }
// do not allow plugin dashboard updates without overwrite flag // do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && !cmd.Overwrite { if existing.PluginId != "" && !cmd.Overwrite {
return models.UpdatePluginDashboardError{PluginId: existing.PluginId} return dashboards.UpdatePluginDashboardError{PluginId: existing.PluginId}
} }
} }
@ -470,7 +469,7 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e
} }
if affectedRows == 0 { if affectedRows == 0 {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
dashVersion := &dashver.DashboardVersion{ dashVersion := &dashver.DashboardVersion{
@ -488,7 +487,7 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e
if affectedRows, err = sess.Insert(dashVersion); err != nil { if affectedRows, err = sess.Insert(dashVersion); err != nil {
return err return err
} else if affectedRows == 0 { } else if affectedRows == 0 {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
// delete existing tags // delete existing tags
@ -525,7 +524,7 @@ func generateNewDashboardUid(sess *sqlstore.DBSession, orgId int64) (string, err
} }
} }
return "", models.ErrDashboardFailedGenerateUniqueUid return "", dashboards.ErrDashboardFailedGenerateUniqueUid
} }
func saveProvisionedData(sess *sqlstore.DBSession, provisioning *models.DashboardProvisioning, dashboard *models.Dashboard) error { func saveProvisionedData(sess *sqlstore.DBSession, provisioning *models.DashboardProvisioning, dashboard *models.Dashboard) error {
@ -709,7 +708,7 @@ func (d *DashboardStore) deleteDashboard(cmd *models.DeleteDashboardCommand, ses
if err != nil { if err != nil {
return err return err
} else if !has { } else if !has {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
deletes := []string{ deletes := []string{
@ -779,7 +778,7 @@ func (d *DashboardStore) deleteDashboard(cmd *models.DeleteDashboardCommand, ses
} }
if exists { if exists {
if !cmd.ForceDeleteFolderRules { if !cmd.ForceDeleteFolderRules {
return fmt.Errorf("folder cannot be deleted: %w", models.ErrFolderContainsAlertRules) return fmt.Errorf("folder cannot be deleted: %w", dashboards.ErrFolderContainsAlertRules)
} }
// Delete all rules under this folder. // Delete all rules under this folder.
@ -836,7 +835,7 @@ func (d *DashboardStore) deleteAlertDefinition(dashboardId int64, sess *sqlstore
func (d *DashboardStore) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) (*models.Dashboard, error) { func (d *DashboardStore) GetDashboard(ctx context.Context, query *models.GetDashboardQuery) (*models.Dashboard, error) {
err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
if query.Id == 0 && len(query.Slug) == 0 && len(query.Uid) == 0 { if query.Id == 0 && len(query.Slug) == 0 && len(query.Uid) == 0 {
return models.ErrDashboardIdentifierNotSet return dashboards.ErrDashboardIdentifierNotSet
} }
dashboard := models.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid} dashboard := models.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid}
@ -845,7 +844,7 @@ func (d *DashboardStore) GetDashboard(ctx context.Context, query *models.GetDash
if err != nil { if err != nil {
return err return err
} else if !has { } else if !has {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
dashboard.SetId(dashboard.Id) dashboard.SetId(dashboard.Id)
@ -865,7 +864,7 @@ func (d *DashboardStore) GetDashboardUIDById(ctx context.Context, query *models.
if err != nil { if err != nil {
return err return err
} else if !exists { } else if !exists {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
query.Result = us query.Result = us
return nil return nil

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
@ -11,7 +12,7 @@ import (
// retrieves public dashboard configuration // retrieves public dashboard configuration
func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) { func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) {
if accessToken == "" { if accessToken == "" {
return nil, nil, models.ErrPublicDashboardIdentifierNotSet return nil, nil, dashboards.ErrPublicDashboardIdentifierNotSet
} }
// get public dashboard // get public dashboard
@ -22,7 +23,7 @@ func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken str
return err return err
} }
if !has { if !has {
return models.ErrPublicDashboardNotFound return dashboards.ErrPublicDashboardNotFound
} }
return nil return nil
}) })
@ -39,7 +40,7 @@ func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken str
return err return err
} }
if !has { if !has {
return models.ErrPublicDashboardNotFound return dashboards.ErrPublicDashboardNotFound
} }
return nil return nil
}) })
@ -69,7 +70,7 @@ func (d *DashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (str
} }
} }
return models.ErrPublicDashboardFailedGenerateUniqueUid return dashboards.ErrPublicDashboardFailedGenerateUniqueUid
}) })
if err != nil { if err != nil {
@ -82,7 +83,7 @@ func (d *DashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (str
// retrieves public dashboard configuration // retrieves public dashboard configuration
func (d *DashboardStore) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { func (d *DashboardStore) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) {
if dashboardUid == "" { if dashboardUid == "" {
return nil, models.ErrDashboardIdentifierNotSet return nil, dashboards.ErrDashboardIdentifierNotSet
} }
pdRes := &models.PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid} pdRes := &models.PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid}

View File

@ -5,13 +5,15 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
// This is what the db sets empty time settings to // This is what the db sets empty time settings to
@ -58,13 +60,13 @@ func TestIntegrationGetPublicDashboard(t *testing.T) {
t.Run("returns ErrPublicDashboardNotFound with empty uid", func(t *testing.T) { t.Run("returns ErrPublicDashboardNotFound with empty uid", func(t *testing.T) {
setup() setup()
_, _, err := dashboardStore.GetPublicDashboard(context.Background(), "") _, _, err := dashboardStore.GetPublicDashboard(context.Background(), "")
require.Error(t, models.ErrPublicDashboardIdentifierNotSet, err) require.Error(t, dashboards.ErrPublicDashboardIdentifierNotSet, err)
}) })
t.Run("returns ErrPublicDashboardNotFound when PublicDashboard not found", func(t *testing.T) { t.Run("returns ErrPublicDashboardNotFound when PublicDashboard not found", func(t *testing.T) {
setup() setup()
_, _, err := dashboardStore.GetPublicDashboard(context.Background(), "zzzzzz") _, _, err := dashboardStore.GetPublicDashboard(context.Background(), "zzzzzz")
require.Error(t, models.ErrPublicDashboardNotFound, err) require.Error(t, dashboards.ErrPublicDashboardNotFound, err)
}) })
t.Run("returns ErrDashboardNotFound when Dashboard not found", func(t *testing.T) { t.Run("returns ErrDashboardNotFound when Dashboard not found", func(t *testing.T) {
@ -83,7 +85,7 @@ func TestIntegrationGetPublicDashboard(t *testing.T) {
}) })
require.NoError(t, err) require.NoError(t, err)
_, _, err = dashboardStore.GetPublicDashboard(context.Background(), "abc1234") _, _, err = dashboardStore.GetPublicDashboard(context.Background(), "abc1234")
require.Error(t, models.ErrDashboardNotFound, err) require.Error(t, dashboards.ErrDashboardNotFound, err)
}) })
} }
@ -109,7 +111,7 @@ func TestIntegrationGetPublicDashboardConfig(t *testing.T) {
t.Run("returns dashboard errDashboardIdentifierNotSet", func(t *testing.T) { t.Run("returns dashboard errDashboardIdentifierNotSet", func(t *testing.T) {
setup() setup()
_, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, "") _, err := dashboardStore.GetPublicDashboardConfig(context.Background(), savedDashboard.OrgId, "")
require.Error(t, models.ErrDashboardIdentifierNotSet, err) require.Error(t, dashboards.ErrDashboardIdentifierNotSet, err)
}) })
t.Run("returns isPublic along with public dashboard when exists", func(t *testing.T) { t.Run("returns isPublic along with public dashboard when exists", func(t *testing.T) {

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
) )
@ -492,12 +493,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
t.Run("should not find dashboard", func(t *testing.T) { t.Run("should not find dashboard", func(t *testing.T) {
d, err := dashboardStore.GetFolderByUID(context.Background(), orgId, dash.Uid) d, err := dashboardStore.GetFolderByUID(context.Background(), orgId, dash.Uid)
require.Nil(t, d) require.Nil(t, d)
require.ErrorIs(t, err, models.ErrFolderNotFound) require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
}) })
t.Run("should search in organization", func(t *testing.T) { t.Run("should search in organization", func(t *testing.T) {
d, err := dashboardStore.GetFolderByUID(context.Background(), orgId+1, folder.Uid) d, err := dashboardStore.GetFolderByUID(context.Background(), orgId+1, folder.Uid)
require.Nil(t, d) require.Nil(t, d)
require.ErrorIs(t, err, models.ErrFolderNotFound) require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
}) })
}) })
@ -516,12 +517,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
t.Run("should not find dashboard", func(t *testing.T) { t.Run("should not find dashboard", func(t *testing.T) {
d, err := dashboardStore.GetFolderByID(context.Background(), orgId, dash.Id) d, err := dashboardStore.GetFolderByID(context.Background(), orgId, dash.Id)
require.Nil(t, d) require.Nil(t, d)
require.ErrorIs(t, err, models.ErrFolderNotFound) require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
}) })
t.Run("should search in organization", func(t *testing.T) { t.Run("should search in organization", func(t *testing.T) {
d, err := dashboardStore.GetFolderByID(context.Background(), orgId+1, folder.Id) d, err := dashboardStore.GetFolderByID(context.Background(), orgId+1, folder.Id)
require.Nil(t, d) require.Nil(t, d)
require.ErrorIs(t, err, models.ErrFolderNotFound) require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
}) })
}) })
}) })

View File

@ -124,7 +124,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
} }
_, err := dashboardStore.GetDashboard(context.Background(), &query) _, err := dashboardStore.GetDashboard(context.Background(), &query)
require.Equal(t, err, models.ErrDashboardIdentifierNotSet) require.Equal(t, err, dashboards.ErrDashboardIdentifierNotSet)
}) })
t.Run("Should be able to get dashboards by IDs & UIDs", func(t *testing.T) { t.Run("Should be able to get dashboards by IDs & UIDs", func(t *testing.T) {
@ -227,7 +227,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
setup() setup()
deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: false} deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: false}
err := dashboardStore.DeleteDashboard(context.Background(), deleteCmd) err := dashboardStore.DeleteDashboard(context.Background(), deleteCmd)
require.True(t, errors.Is(err, models.ErrFolderContainsAlertRules)) require.True(t, errors.Is(err, dashboards.ErrFolderContainsAlertRules))
}) })
t.Run("Should be able to delete a dashboard folder and its children if force delete rules is enabled", func(t *testing.T) { t.Run("Should be able to delete a dashboard folder and its children if force delete rules is enabled", func(t *testing.T) {
@ -274,7 +274,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) {
} }
_, err := dashboardStore.SaveDashboard(cmd) _, err := dashboardStore.SaveDashboard(cmd)
require.Equal(t, err, models.ErrDashboardNotFound) require.Equal(t, err, dashboards.ErrDashboardNotFound)
}) })
t.Run("Should not return error if no dashboard is found for update when dashboard id is zero", func(t *testing.T) { t.Run("Should not return error if no dashboard is found for update when dashboard id is zero", func(t *testing.T) {

View File

@ -0,0 +1,197 @@
package dashboards
import (
"errors"
"github.com/grafana/grafana/pkg/util"
)
// Typed errors
var (
ErrDashboardNotFound = DashboardErr{
Reason: "Dashboard not found",
StatusCode: 404,
Status: "not-found",
}
ErrDashboardCorrupt = DashboardErr{
Reason: "Dashboard data is missing or corrupt",
StatusCode: 500,
Status: "not-found",
}
ErrDashboardPanelNotFound = DashboardErr{
Reason: "Dashboard panel not found",
StatusCode: 404,
Status: "not-found",
}
ErrDashboardFolderNotFound = DashboardErr{
Reason: "Folder not found",
StatusCode: 404,
}
ErrDashboardWithSameUIDExists = DashboardErr{
Reason: "A dashboard with the same uid already exists",
StatusCode: 400,
}
ErrDashboardWithSameNameInFolderExists = DashboardErr{
Reason: "A dashboard with the same name in the folder already exists",
StatusCode: 412,
Status: "name-exists",
}
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,
Status: "empty-name",
}
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,
Status: "name-match",
}
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,
}
ErrDashboardIdentifierInvalid = DashboardErr{
Reason: "Dashboard ID not a number",
StatusCode: 400,
}
ErrDashboardPanelIdentifierInvalid = DashboardErr{
Reason: "Dashboard panel ID not a number",
StatusCode: 400,
}
ErrDashboardOrPanelIdentifierNotSet = DashboardErr{
Reason: "Unique identifier needed to be able to get a dashboard panel",
StatusCode: 400,
}
ErrProvisionedDashboardNotFound = DashboardErr{
Reason: "Dashboard is not provisioned",
StatusCode: 404,
Status: "not-found",
}
ErrDashboardThumbnailNotFound = DashboardErr{
Reason: "Dashboard thumbnail not found",
StatusCode: 404,
Status: "not-found",
}
ErrPublicDashboardFailedGenerateUniqueUid = DashboardErr{
Reason: "Failed to generate unique public dashboard id",
StatusCode: 500,
}
ErrPublicDashboardFailedGenerateAccesstoken = DashboardErr{
Reason: "Failed to public dashboard access token",
StatusCode: 500,
}
ErrPublicDashboardNotFound = DashboardErr{
Reason: "Public dashboard not found",
StatusCode: 404,
Status: "not-found",
}
ErrPublicDashboardPanelNotFound = DashboardErr{
Reason: "Panel not found in dashboard",
StatusCode: 404,
Status: "not-found",
}
ErrPublicDashboardIdentifierNotSet = DashboardErr{
Reason: "No Uid for public dashboard specified",
StatusCode: 400,
}
ErrFolderNotFound = errors.New("folder not found")
ErrFolderVersionMismatch = errors.New("the folder has been changed by someone else")
ErrFolderTitleEmpty = errors.New("folder title cannot be empty")
ErrFolderWithSameUIDExists = errors.New("a folder/dashboard with the same uid already exists")
ErrFolderInvalidUID = errors.New("invalid uid for folder provided")
ErrFolderSameNameExists = errors.New("a folder or dashboard in the general folder with the same name already exists")
ErrFolderFailedGenerateUniqueUid = errors.New("failed to generate unique folder ID")
ErrFolderAccessDenied = errors.New("access denied to folder")
ErrFolderContainsAlertRules = errors.New("folder contains alert rules")
)
// 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 {
PluginId string
}
func (d UpdatePluginDashboardError) Error() string {
return "Dashboard belongs to plugin"
}

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@ -21,11 +22,11 @@ func (dr *DashboardServiceImpl) GetPublicDashboard(ctx context.Context, accessTo
} }
if pubdash == nil || d == nil { if pubdash == nil || d == nil {
return nil, models.ErrPublicDashboardNotFound return nil, dashboards.ErrPublicDashboardNotFound
} }
if !pubdash.IsEnabled { if !pubdash.IsEnabled {
return nil, models.ErrPublicDashboardNotFound return nil, dashboards.ErrPublicDashboardNotFound
} }
ts := pubdash.BuildTimeSettings(d) ts := pubdash.BuildTimeSettings(d)
@ -49,7 +50,7 @@ func (dr *DashboardServiceImpl) GetPublicDashboardConfig(ctx context.Context, or
// to the database. It handles validations for sharing config and persistence // to the database. It handles validations for sharing config and persistence
func (dr *DashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) { func (dr *DashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) {
if len(dto.DashboardUid) == 0 { if len(dto.DashboardUid) == 0 {
return nil, models.ErrDashboardIdentifierNotSet return nil, dashboards.ErrDashboardIdentifierNotSet
} }
// set default value for time settings // set default value for time settings
@ -125,13 +126,13 @@ func (dr *DashboardServiceImpl) updatePublicDashboardConfig(ctx context.Context,
// dashboard and returns a metrics request to be sent to query backend // dashboard and returns a metrics request to be sent to query backend
func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) { func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) {
if !publicDashboard.IsEnabled { if !publicDashboard.IsEnabled {
return dtos.MetricRequest{}, models.ErrPublicDashboardNotFound return dtos.MetricRequest{}, dashboards.ErrPublicDashboardNotFound
} }
queriesByPanel := models.GetQueriesFromDashboard(dashboard.Data) queriesByPanel := models.GetQueriesFromDashboard(dashboard.Data)
if _, ok := queriesByPanel[panelId]; !ok { if _, ok := queriesByPanel[panelId]; !ok {
return dtos.MetricRequest{}, models.ErrPublicDashboardPanelNotFound return dtos.MetricRequest{}, dashboards.ErrPublicDashboardPanelNotFound
} }
ts := publicDashboard.BuildTimeSettings(dashboard) ts := publicDashboard.BuildTimeSettings(dashboard)

View File

@ -6,15 +6,16 @@ import (
"time" "time"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
) )
var timeSettings, _ = simplejson.NewJson([]byte(`{"from": "now-12", "to": "now"}`)) var timeSettings, _ = simplejson.NewJson([]byte(`{"from": "now-12", "to": "now"}`))
@ -66,21 +67,21 @@ func TestGetPublicDashboard(t *testing.T) {
d: &models.Dashboard{Uid: "mydashboard"}, d: &models.Dashboard{Uid: "mydashboard"},
err: nil, err: nil,
}, },
ErrResp: models.ErrPublicDashboardNotFound, ErrResp: dashboards.ErrPublicDashboardNotFound,
DashResp: nil, DashResp: nil,
}, },
{ {
Name: "returns ErrPublicDashboardNotFound if PublicDashboard missing", Name: "returns ErrPublicDashboardNotFound if PublicDashboard missing",
AccessToken: "abc123", AccessToken: "abc123",
StoreResp: &storeResp{pd: nil, d: nil, err: nil}, StoreResp: &storeResp{pd: nil, d: nil, err: nil},
ErrResp: models.ErrPublicDashboardNotFound, ErrResp: dashboards.ErrPublicDashboardNotFound,
DashResp: nil, DashResp: nil,
}, },
{ {
Name: "returns ErrPublicDashboardNotFound if Dashboard missing", Name: "returns ErrPublicDashboardNotFound if Dashboard missing",
AccessToken: "abc123", AccessToken: "abc123",
StoreResp: &storeResp{pd: nil, d: nil, err: nil}, StoreResp: &storeResp{pd: nil, d: nil, err: nil},
ErrResp: models.ErrPublicDashboardNotFound, ErrResp: dashboards.ErrPublicDashboardNotFound,
DashResp: nil, DashResp: nil,
}, },
} }

View File

@ -83,21 +83,21 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
dash.SetUid(strings.TrimSpace(dash.Uid)) dash.SetUid(strings.TrimSpace(dash.Uid))
if dash.Title == "" { if dash.Title == "" {
return nil, models.ErrDashboardTitleEmpty return nil, dashboards.ErrDashboardTitleEmpty
} }
if dash.IsFolder && dash.FolderId > 0 { if dash.IsFolder && dash.FolderId > 0 {
return nil, models.ErrDashboardFolderCannotHaveParent return nil, dashboards.ErrDashboardFolderCannotHaveParent
} }
if dash.IsFolder && strings.EqualFold(dash.Title, models.RootFolderName) { if dash.IsFolder && strings.EqualFold(dash.Title, models.RootFolderName) {
return nil, models.ErrDashboardFolderNameExists return nil, dashboards.ErrDashboardFolderNameExists
} }
if !util.IsValidShortUID(dash.Uid) { if !util.IsValidShortUID(dash.Uid) {
return nil, models.ErrDashboardInvalidUid return nil, dashboards.ErrDashboardInvalidUid
} else if util.IsShortUIDTooLong(dash.Uid) { } else if util.IsShortUIDTooLong(dash.Uid) {
return nil, models.ErrDashboardUidTooLong return nil, dashboards.ErrDashboardUidTooLong
} }
if err := validateDashboardRefreshInterval(dash); err != nil { if err := validateDashboardRefreshInterval(dash); err != nil {
@ -123,7 +123,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
if err != nil { if err != nil {
return nil, err return nil, err
} }
return nil, models.ErrDashboardUpdateAccessDenied return nil, dashboards.ErrDashboardUpdateAccessDenied
} }
} }
@ -134,7 +134,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
} }
if provisionedData != nil { if provisionedData != nil {
return nil, models.ErrDashboardCannotSaveProvisionedDashboard return nil, dashboards.ErrDashboardCannotSaveProvisionedDashboard
} }
} }
@ -144,14 +144,14 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d
if err != nil { if err != nil {
return nil, err return nil, err
} }
return nil, models.ErrDashboardUpdateAccessDenied return nil, dashboards.ErrDashboardUpdateAccessDenied
} }
} else { } else {
if canSave, err := guard.CanSave(); err != nil || !canSave { if canSave, err := guard.CanSave(); err != nil || !canSave {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return nil, models.ErrDashboardUpdateAccessDenied return nil, dashboards.ErrDashboardUpdateAccessDenied
} }
} }
@ -202,7 +202,7 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error {
} }
if d < minRefreshInterval { if d < minRefreshInterval {
return models.ErrDashboardRefreshIntervalTooShort return dashboards.ErrDashboardRefreshIntervalTooShort
} }
return nil return nil
@ -414,7 +414,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId
} }
if provisionedData != nil { if provisionedData != nil {
return models.ErrDashboardCannotDeleteProvisionedDashboard return dashboards.ErrDashboardCannotDeleteProvisionedDashboard
} }
} }
cmd := &models.DeleteDashboardCommand{OrgId: orgId, Id: dashboardId} cmd := &models.DeleteDashboardCommand{OrgId: orgId, Id: dashboardId}

View File

@ -11,7 +11,7 @@ import (
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
dashbboardservice "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/dashboards/database" "github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
@ -39,7 +39,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardNotFound, err) assert.Equal(t, dashboards.ErrDashboardNotFound, err)
}) })
// Given other organization // Given other organization
@ -59,7 +59,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardNotFound, err) assert.Equal(t, dashboards.ErrDashboardNotFound, err)
}) })
permissionScenario(t, "When creating a dashboard with same uid as dashboard in organization A, it should create a new dashboard in org B", permissionScenario(t, "When creating a dashboard with same uid as dashboard in organization A, it should create a new dashboard in org B",
@ -101,7 +101,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sqlStore) err := callSaveWithError(cmd, sqlStore)
assert.Equal(t, models.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, int64(0), sc.dashboardGuardianMock.DashId) assert.Equal(t, int64(0), sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -121,7 +121,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.otherSavedFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, sc.otherSavedFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -141,7 +141,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -162,7 +162,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -183,7 +183,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -204,7 +204,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -225,7 +225,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -246,7 +246,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardUpdateAccessDenied, err) assert.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -267,7 +267,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, sc.savedDashInGeneralFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -288,7 +288,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
require.Equal(t, models.ErrDashboardUpdateAccessDenied, err) require.Equal(t, dashboards.ErrDashboardUpdateAccessDenied, err)
assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId) assert.Equal(t, sc.savedDashInFolder.Id, sc.dashboardGuardianMock.DashId)
assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId) assert.Equal(t, cmd.OrgId, sc.dashboardGuardianMock.OrgId)
@ -429,7 +429,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardFolderNotFound, err) assert.Equal(t, dashboards.ErrDashboardFolderNotFound, err)
}) })
permissionScenario(t, "When updating an existing dashboard by id without current version", canSave, permissionScenario(t, "When updating an existing dashboard by id without current version", canSave,
@ -445,7 +445,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardVersionMismatch, err) assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err)
}) })
permissionScenario(t, "When updating an existing dashboard by id with current version", canSave, permissionScenario(t, "When updating an existing dashboard by id with current version", canSave,
@ -485,7 +485,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardVersionMismatch, err) assert.Equal(t, dashboards.ErrDashboardVersionMismatch, err)
}) })
permissionScenario(t, "When updating an existing dashboard by uid with current version", canSave, permissionScenario(t, "When updating an existing dashboard by uid with current version", canSave,
@ -524,7 +524,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameNameInFolderExists, err) assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err)
}) })
permissionScenario(t, "When creating a dashboard with same name as dashboard in General folder", permissionScenario(t, "When creating a dashboard with same name as dashboard in General folder",
@ -540,7 +540,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameNameInFolderExists, err) assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err)
}) })
permissionScenario(t, "When creating a folder with same name as existing folder", canSave, permissionScenario(t, "When creating a folder with same name as existing folder", canSave,
@ -556,7 +556,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameNameInFolderExists, err) assert.Equal(t, dashboards.ErrDashboardWithSameNameInFolderExists, err)
}) })
}) })
@ -644,7 +644,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameUIDExists, err) assert.Equal(t, dashboards.ErrDashboardWithSameUIDExists, err)
}) })
permissionScenario(t, "When creating a dashboard with same name as dashboard in other folder", canSave, permissionScenario(t, "When creating a dashboard with same name as dashboard in other folder", canSave,
@ -708,7 +708,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardTypeMismatch, err) assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err)
}) })
permissionScenario(t, "When updating existing dashboard to a folder using id", canSave, permissionScenario(t, "When updating existing dashboard to a folder using id", canSave,
@ -724,7 +724,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardTypeMismatch, err) assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err)
}) })
permissionScenario(t, "When updating existing folder to a dashboard using uid", canSave, permissionScenario(t, "When updating existing folder to a dashboard using uid", canSave,
@ -740,7 +740,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardTypeMismatch, err) assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err)
}) })
permissionScenario(t, "When updating existing dashboard to a folder using uid", canSave, permissionScenario(t, "When updating existing dashboard to a folder using uid", canSave,
@ -756,7 +756,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardTypeMismatch, err) assert.Equal(t, dashboards.ErrDashboardTypeMismatch, err)
}) })
permissionScenario(t, "When updating existing folder to a dashboard using title", canSave, permissionScenario(t, "When updating existing folder to a dashboard using title", canSave,
@ -771,7 +771,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardWithSameNameAsFolder, err) assert.Equal(t, dashboards.ErrDashboardWithSameNameAsFolder, err)
}) })
permissionScenario(t, "When updating existing dashboard to a folder using title", canSave, permissionScenario(t, "When updating existing dashboard to a folder using title", canSave,
@ -786,7 +786,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
} }
err := callSaveWithError(cmd, sc.sqlStore) err := callSaveWithError(cmd, sc.sqlStore)
assert.Equal(t, models.ErrDashboardFolderWithSameNameAsDashboard, err) assert.Equal(t, dashboards.ErrDashboardFolderWithSameNameAsDashboard, err)
}) })
}) })
}) })
@ -796,7 +796,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) {
type permissionScenarioContext struct { type permissionScenarioContext struct {
dashboardGuardianMock *guardian.FakeDashboardGuardian dashboardGuardianMock *guardian.FakeDashboardGuardian
sqlStore *sqlstore.SQLStore sqlStore *sqlstore.SQLStore
dashboardStore dashbboardservice.Store dashboardStore dashboards.Store
savedFolder *models.Dashboard savedFolder *models.Dashboard
savedDashInFolder *models.Dashboard savedDashInFolder *models.Dashboard
otherSavedFolder *models.Dashboard otherSavedFolder *models.Dashboard
@ -913,7 +913,7 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto
}), }),
} }
dto := dashbboardservice.SaveDashboardDTO{ dto := dashboards.SaveDashboardDTO{
OrgId: orgID, OrgId: orgID,
Dashboard: cmd.GetDashboardModel(), Dashboard: cmd.GetDashboardModel(),
User: &models.SignedInUser{ User: &models.SignedInUser{
@ -950,7 +950,7 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore.
}), }),
} }
dto := dashbboardservice.SaveDashboardDTO{ dto := dashboards.SaveDashboardDTO{
OrgId: orgID, OrgId: orgID,
Dashboard: cmd.GetDashboardModel(), Dashboard: cmd.GetDashboardModel(),
User: &models.SignedInUser{ User: &models.SignedInUser{
@ -975,10 +975,10 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore.
return res return res
} }
func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashbboardservice.SaveDashboardDTO { func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO {
dash := (&cmd).GetDashboardModel() dash := (&cmd).GetDashboardModel()
return dashbboardservice.SaveDashboardDTO{ return dashboards.SaveDashboardDTO{
Dashboard: dash, Dashboard: dash,
Message: cmd.Message, Message: cmd.Message,
OrgId: cmd.OrgId, OrgId: cmd.OrgId,

View File

@ -11,7 +11,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
m "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -21,7 +21,7 @@ func TestIntegrationDashboardService(t *testing.T) {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
t.Run("Dashboard service tests", func(t *testing.T) { t.Run("Dashboard service tests", func(t *testing.T) {
fakeStore := m.FakeDashboardStore{} fakeStore := dashboards.FakeDashboardStore{}
defer fakeStore.AssertExpectations(t) defer fakeStore.AssertExpectations(t)
service := &DashboardServiceImpl{ service := &DashboardServiceImpl{
log: log.New("test.logger"), log: log.New("test.logger"),
@ -34,7 +34,7 @@ func TestIntegrationDashboardService(t *testing.T) {
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
t.Run("Save dashboard validation", func(t *testing.T) { t.Run("Save dashboard validation", func(t *testing.T) {
dto := &m.SaveDashboardDTO{} dto := &dashboards.SaveDashboardDTO{}
t.Run("When saving a dashboard with empty title it should return error", func(t *testing.T) { t.Run("When saving a dashboard with empty title it should return error", func(t *testing.T) {
titles := []string{"", " ", " \t "} titles := []string{"", " ", " \t "}
@ -42,7 +42,7 @@ func TestIntegrationDashboardService(t *testing.T) {
for _, title := range titles { for _, title := range titles {
dto.Dashboard = models.NewDashboard(title) dto.Dashboard = models.NewDashboard(title)
_, err := service.SaveDashboard(context.Background(), dto, false) _, err := service.SaveDashboard(context.Background(), dto, false)
require.Equal(t, err, models.ErrDashboardTitleEmpty) require.Equal(t, err, dashboards.ErrDashboardTitleEmpty)
} }
}) })
@ -50,13 +50,13 @@ func TestIntegrationDashboardService(t *testing.T) {
dto.Dashboard = models.NewDashboardFolder("Folder") dto.Dashboard = models.NewDashboardFolder("Folder")
dto.Dashboard.FolderId = 1 dto.Dashboard.FolderId = 1
_, err := service.SaveDashboard(context.Background(), dto, false) _, err := service.SaveDashboard(context.Background(), dto, false)
require.Equal(t, err, models.ErrDashboardFolderCannotHaveParent) require.Equal(t, err, dashboards.ErrDashboardFolderCannotHaveParent)
}) })
t.Run("Should return validation error if folder is named General", func(t *testing.T) { t.Run("Should return validation error if folder is named General", func(t *testing.T) {
dto.Dashboard = models.NewDashboardFolder("General") dto.Dashboard = models.NewDashboardFolder("General")
_, err := service.SaveDashboard(context.Background(), dto, false) _, err := service.SaveDashboard(context.Background(), dto, false)
require.Equal(t, err, models.ErrDashboardFolderNameExists) require.Equal(t, err, dashboards.ErrDashboardFolderNameExists)
}) })
t.Run("When saving a dashboard should validate uid", func(t *testing.T) { t.Run("When saving a dashboard should validate uid", func(t *testing.T) {
@ -68,9 +68,9 @@ func TestIntegrationDashboardService(t *testing.T) {
{Uid: " ", Error: nil}, {Uid: " ", Error: nil},
{Uid: " \t ", Error: nil}, {Uid: " \t ", Error: nil},
{Uid: "asdf90_-", Error: nil}, {Uid: "asdf90_-", Error: nil},
{Uid: "asdf/90", Error: models.ErrDashboardInvalidUid}, {Uid: "asdf/90", Error: dashboards.ErrDashboardInvalidUid},
{Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil}, {Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil},
{Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: models.ErrDashboardUidTooLong}, {Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: dashboards.ErrDashboardUidTooLong},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -94,7 +94,7 @@ func TestIntegrationDashboardService(t *testing.T) {
dto.Dashboard.SetId(3) dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1} dto.User = &models.SignedInUser{UserId: 1}
_, err := service.SaveDashboard(context.Background(), dto, false) _, err := service.SaveDashboard(context.Background(), dto, false)
require.Equal(t, err, models.ErrDashboardCannotSaveProvisionedDashboard) require.Equal(t, err, dashboards.ErrDashboardCannotSaveProvisionedDashboard)
}) })
t.Run("Should not return validation error if dashboard is provisioned but UI updates allowed", func(t *testing.T) { t.Run("Should not return validation error if dashboard is provisioned but UI updates allowed", func(t *testing.T) {
@ -123,7 +123,7 @@ func TestIntegrationDashboardService(t *testing.T) {
}) })
t.Run("Save provisioned dashboard validation", func(t *testing.T) { t.Run("Save provisioned dashboard validation", func(t *testing.T) {
dto := &m.SaveDashboardDTO{} dto := &dashboards.SaveDashboardDTO{}
t.Run("Should not return validation error if dashboard is provisioned", func(t *testing.T) { t.Run("Should not return validation error if dashboard is provisioned", func(t *testing.T) {
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once() fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once()
@ -157,7 +157,7 @@ func TestIntegrationDashboardService(t *testing.T) {
}) })
t.Run("Import dashboard validation", func(t *testing.T) { t.Run("Import dashboard validation", func(t *testing.T) {
dto := &m.SaveDashboardDTO{} dto := &dashboards.SaveDashboardDTO{}
t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) { t.Run("Should return validation error if dashboard is provisioned", func(t *testing.T) {
fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once() fakeStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Once()
@ -167,7 +167,7 @@ func TestIntegrationDashboardService(t *testing.T) {
dto.Dashboard.SetId(3) dto.Dashboard.SetId(3)
dto.User = &models.SignedInUser{UserId: 1} dto.User = &models.SignedInUser{UserId: 1}
_, err := service.ImportDashboard(context.Background(), dto) _, err := service.ImportDashboard(context.Background(), dto)
require.Equal(t, err, models.ErrDashboardCannotSaveProvisionedDashboard) require.Equal(t, err, dashboards.ErrDashboardCannotSaveProvisionedDashboard)
}) })
}) })
@ -182,7 +182,7 @@ func TestIntegrationDashboardService(t *testing.T) {
t.Run("DeleteDashboard should fail to delete it when provisioning information is missing", func(t *testing.T) { t.Run("DeleteDashboard should fail to delete it when provisioning information is missing", func(t *testing.T) {
fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{}, nil).Once() fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{}, nil).Once()
err := service.DeleteDashboard(context.Background(), 1, 1) err := service.DeleteDashboard(context.Background(), 1, 1)
require.Equal(t, err, models.ErrDashboardCannotDeleteProvisionedDashboard) require.Equal(t, err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard)
}) })
}) })

View File

@ -94,7 +94,7 @@ func (f *FolderServiceImpl) GetFolderByID(ctx context.Context, user *models.Sign
if err != nil { if err != nil {
return nil, toFolderError(err) return nil, toFolderError(err)
} }
return nil, models.ErrFolderAccessDenied return nil, dashboards.ErrFolderAccessDenied
} }
return dashFolder, nil return dashFolder, nil
@ -111,7 +111,7 @@ func (f *FolderServiceImpl) GetFolderByUID(ctx context.Context, user *models.Sig
if err != nil { if err != nil {
return nil, toFolderError(err) return nil, toFolderError(err)
} }
return nil, models.ErrFolderAccessDenied return nil, dashboards.ErrFolderAccessDenied
} }
return dashFolder, nil return dashFolder, nil
@ -128,7 +128,7 @@ func (f *FolderServiceImpl) GetFolderByTitle(ctx context.Context, user *models.S
if err != nil { if err != nil {
return nil, toFolderError(err) return nil, toFolderError(err)
} }
return nil, models.ErrFolderAccessDenied return nil, dashboards.ErrFolderAccessDenied
} }
return dashFolder, nil return dashFolder, nil
@ -140,7 +140,7 @@ func (f *FolderServiceImpl) CreateFolder(ctx context.Context, user *models.Signe
trimmedUID := strings.TrimSpace(uid) trimmedUID := strings.TrimSpace(uid)
if trimmedUID == accesscontrol.GeneralFolderUID { if trimmedUID == accesscontrol.GeneralFolderUID {
return nil, models.ErrFolderInvalidUID return nil, dashboards.ErrFolderInvalidUID
} }
dashFolder.SetUid(trimmedUID) dashFolder.SetUid(trimmedUID)
@ -201,7 +201,7 @@ func (f *FolderServiceImpl) UpdateFolder(ctx context.Context, user *models.Signe
dashFolder := query.Result dashFolder := query.Result
if !dashFolder.IsFolder { if !dashFolder.IsFolder {
return models.ErrFolderNotFound return dashboards.ErrFolderNotFound
} }
cmd.UpdateDashboardModel(dashFolder, orgID, user.UserId) cmd.UpdateDashboardModel(dashFolder, orgID, user.UserId)
@ -254,7 +254,7 @@ func (f *FolderServiceImpl) DeleteFolder(ctx context.Context, user *models.Signe
if err != nil { if err != nil {
return nil, toFolderError(err) return nil, toFolderError(err)
} }
return nil, models.ErrFolderAccessDenied return nil, dashboards.ErrFolderAccessDenied
} }
deleteCmd := models.DeleteDashboardCommand{OrgId: orgID, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules} deleteCmd := models.DeleteDashboardCommand{OrgId: orgID, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules}
@ -271,32 +271,32 @@ func (f *FolderServiceImpl) MakeUserAdmin(ctx context.Context, orgID int64, user
} }
func toFolderError(err error) error { func toFolderError(err error) error {
if errors.Is(err, models.ErrDashboardTitleEmpty) { if errors.Is(err, dashboards.ErrDashboardTitleEmpty) {
return models.ErrFolderTitleEmpty return dashboards.ErrFolderTitleEmpty
} }
if errors.Is(err, models.ErrDashboardUpdateAccessDenied) { if errors.Is(err, dashboards.ErrDashboardUpdateAccessDenied) {
return models.ErrFolderAccessDenied return dashboards.ErrFolderAccessDenied
} }
if errors.Is(err, models.ErrDashboardWithSameNameInFolderExists) { if errors.Is(err, dashboards.ErrDashboardWithSameNameInFolderExists) {
return models.ErrFolderSameNameExists return dashboards.ErrFolderSameNameExists
} }
if errors.Is(err, models.ErrDashboardWithSameUIDExists) { if errors.Is(err, dashboards.ErrDashboardWithSameUIDExists) {
return models.ErrFolderWithSameUIDExists return dashboards.ErrFolderWithSameUIDExists
} }
if errors.Is(err, models.ErrDashboardVersionMismatch) { if errors.Is(err, dashboards.ErrDashboardVersionMismatch) {
return models.ErrFolderVersionMismatch return dashboards.ErrFolderVersionMismatch
} }
if errors.Is(err, models.ErrDashboardNotFound) { if errors.Is(err, dashboards.ErrDashboardNotFound) {
return models.ErrFolderNotFound return dashboards.ErrFolderNotFound
} }
if errors.Is(err, models.ErrDashboardFailedGenerateUniqueUid) { if errors.Is(err, dashboards.ErrDashboardFailedGenerateUniqueUid) {
err = models.ErrFolderFailedGenerateUniqueUid err = dashboards.ErrFolderFailedGenerateUniqueUid
} }
return err return err

View File

@ -77,7 +77,7 @@ func TestIntegrationFolderService(t *testing.T) {
t.Run("When get folder by id should return access denied error", func(t *testing.T) { t.Run("When get folder by id should return access denied error", func(t *testing.T) {
_, err := service.GetFolderByID(context.Background(), user, folderId, orgID) _, err := service.GetFolderByID(context.Background(), user, folderId, orgID)
require.Equal(t, err, models.ErrFolderAccessDenied) require.Equal(t, err, dashboards.ErrFolderAccessDenied)
}) })
t.Run("When get folder by id, with id = 0 should return default folder", func(t *testing.T) { t.Run("When get folder by id, with id = 0 should return default folder", func(t *testing.T) {
@ -88,13 +88,13 @@ func TestIntegrationFolderService(t *testing.T) {
t.Run("When get folder by uid should return access denied error", func(t *testing.T) { t.Run("When get folder by uid should return access denied error", func(t *testing.T) {
_, err := service.GetFolderByUID(context.Background(), user, orgID, folderUID) _, err := service.GetFolderByUID(context.Background(), user, orgID, folderUID)
require.Equal(t, err, models.ErrFolderAccessDenied) require.Equal(t, err, dashboards.ErrFolderAccessDenied)
}) })
t.Run("When creating folder should return access denied error", func(t *testing.T) { t.Run("When creating folder should return access denied error", func(t *testing.T) {
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Times(2) store.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Times(2)
_, err := service.CreateFolder(context.Background(), user, orgID, folder.Title, folderUID) _, err := service.CreateFolder(context.Background(), user, orgID, folder.Title, folderUID)
require.Equal(t, err, models.ErrFolderAccessDenied) require.Equal(t, err, dashboards.ErrFolderAccessDenied)
}) })
t.Run("When updating folder should return access denied error", func(t *testing.T) { t.Run("When updating folder should return access denied error", func(t *testing.T) {
@ -107,13 +107,13 @@ func TestIntegrationFolderService(t *testing.T) {
Uid: folderUID, Uid: folderUID,
Title: "Folder-TEST", Title: "Folder-TEST",
}) })
require.Equal(t, err, models.ErrFolderAccessDenied) require.Equal(t, err, dashboards.ErrFolderAccessDenied)
}) })
t.Run("When deleting folder by uid should return access denied error", func(t *testing.T) { t.Run("When deleting folder by uid should return access denied error", func(t *testing.T) {
_, err := service.DeleteFolder(context.Background(), user, orgID, folderUID, false) _, err := service.DeleteFolder(context.Background(), user, orgID, folderUID, false)
require.Error(t, err) require.Error(t, err)
require.Equal(t, err, models.ErrFolderAccessDenied) require.Equal(t, err, dashboards.ErrFolderAccessDenied)
}) })
t.Cleanup(func() { t.Cleanup(func() {
@ -144,7 +144,7 @@ func TestIntegrationFolderService(t *testing.T) {
dash.Id = rand.Int63() dash.Id = rand.Int63()
_, err := service.CreateFolder(context.Background(), user, orgID, dash.Title, "general") _, err := service.CreateFolder(context.Background(), user, orgID, dash.Title, "general")
require.ErrorIs(t, err, models.ErrFolderInvalidUID) require.ErrorIs(t, err, dashboards.ErrFolderInvalidUID)
}) })
t.Run("When updating folder should not return access denied error", func(t *testing.T) { t.Run("When updating folder should not return access denied error", func(t *testing.T) {
@ -238,14 +238,14 @@ func TestIntegrationFolderService(t *testing.T) {
ActualError error ActualError error
ExpectedError error ExpectedError error
}{ }{
{ActualError: models.ErrDashboardTitleEmpty, ExpectedError: models.ErrFolderTitleEmpty}, {ActualError: dashboards.ErrDashboardTitleEmpty, ExpectedError: dashboards.ErrFolderTitleEmpty},
{ActualError: models.ErrDashboardUpdateAccessDenied, ExpectedError: models.ErrFolderAccessDenied}, {ActualError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedError: dashboards.ErrFolderAccessDenied},
{ActualError: models.ErrDashboardWithSameNameInFolderExists, ExpectedError: models.ErrFolderSameNameExists}, {ActualError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedError: dashboards.ErrFolderSameNameExists},
{ActualError: models.ErrDashboardWithSameUIDExists, ExpectedError: models.ErrFolderWithSameUIDExists}, {ActualError: dashboards.ErrDashboardWithSameUIDExists, ExpectedError: dashboards.ErrFolderWithSameUIDExists},
{ActualError: models.ErrDashboardVersionMismatch, ExpectedError: models.ErrFolderVersionMismatch}, {ActualError: dashboards.ErrDashboardVersionMismatch, ExpectedError: dashboards.ErrFolderVersionMismatch},
{ActualError: models.ErrDashboardNotFound, ExpectedError: models.ErrFolderNotFound}, {ActualError: dashboards.ErrDashboardNotFound, ExpectedError: dashboards.ErrFolderNotFound},
{ActualError: models.ErrDashboardFailedGenerateUniqueUid, ExpectedError: models.ErrFolderFailedGenerateUniqueUid}, {ActualError: dashboards.ErrDashboardFailedGenerateUniqueUid, ExpectedError: dashboards.ErrFolderFailedGenerateUniqueUid},
{ActualError: models.ErrDashboardInvalidUid, ExpectedError: models.ErrDashboardInvalidUid}, {ActualError: dashboards.ErrDashboardInvalidUid, ExpectedError: dashboards.ErrDashboardInvalidUid},
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -91,7 +91,7 @@ func (d *DashboardSnapshotStore) GetDashboardSnapshot(ctx context.Context, query
if err != nil { if err != nil {
return err return err
} else if !has { } else if !has {
return models.ErrDashboardSnapshotNotFound return dashboardsnapshots.ErrDashboardSnapshotNotFound
} }
query.Result = &snapshot query.Result = &snapshot

View File

@ -0,0 +1,8 @@
package dashboardsnapshots
import "github.com/grafana/grafana/pkg/services/dashboards"
var ErrDashboardSnapshotNotFound = dashboards.DashboardErr{
Reason: "Dashboard snapshot not found",
StatusCode: 404,
}

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion" dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@ -134,7 +135,7 @@ func getDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, dashboard *models.D
if err != nil { if err != nil {
return err return err
} else if !has { } else if !has {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
dashboard.SetId(dashboard.Id) dashboard.SetId(dashboard.Id)
@ -188,7 +189,7 @@ func insertTestDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, title string
if affectedRows, err := sess.Insert(dashVersion); err != nil { if affectedRows, err := sess.Insert(dashVersion); err != nil {
return err return err
} else if affectedRows == 0 { } else if affectedRows == 0 {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
return nil return nil
@ -250,7 +251,7 @@ func updateTestDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, dashboard *m
if affectedRows, err := sess.Insert(dashVersion); err != nil { if affectedRows, err := sess.Insert(dashVersion); err != nil {
return err return err
} else if affectedRows == 0 { } else if affectedRows == 0 {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
return nil return nil

View File

@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
) )
@ -173,11 +174,11 @@ func toLibraryElementError(err error, message string) response.Response {
if errors.Is(err, errLibraryElementVersionMismatch) { if errors.Is(err, errLibraryElementVersionMismatch) {
return response.Error(412, errLibraryElementVersionMismatch.Error(), err) return response.Error(412, errLibraryElementVersionMismatch.Error(), err)
} }
if errors.Is(err, models.ErrFolderNotFound) { if errors.Is(err, dashboards.ErrFolderNotFound) {
return response.Error(404, models.ErrFolderNotFound.Error(), err) return response.Error(404, dashboards.ErrFolderNotFound.Error(), err)
} }
if errors.Is(err, models.ErrFolderAccessDenied) { if errors.Is(err, dashboards.ErrFolderAccessDenied) {
return response.Error(403, models.ErrFolderAccessDenied.Error(), err) return response.Error(403, dashboards.ErrFolderAccessDenied.Error(), err)
} }
if errors.Is(err, errLibraryElementHasConnections) { if errors.Is(err, errLibraryElementHasConnections) {
return response.Error(403, errLibraryElementHasConnections.Error(), err) return response.Error(403, errLibraryElementHasConnections.Error(), err)

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
@ -707,7 +708,7 @@ func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Conte
} }
if len(folderUIDs) == 0 { if len(folderUIDs) == 0 {
return models.ErrFolderNotFound return dashboards.ErrFolderNotFound
} }
if len(folderUIDs) != 1 { if len(folderUIDs) != 1 {

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
) )
@ -29,7 +30,7 @@ func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Conte
} }
if isGeneralFolder(folderID) && user.HasRole(models.ROLE_VIEWER) { if isGeneralFolder(folderID) && user.HasRole(models.ROLE_VIEWER) {
return models.ErrFolderAccessDenied return dashboards.ErrFolderAccessDenied
} }
folder, err := l.folderService.GetFolderByID(ctx, user, folderID, user.OrgId) folder, err := l.folderService.GetFolderByID(ctx, user, folderID, user.OrgId)
if err != nil { if err != nil {
@ -43,7 +44,7 @@ func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Conte
return err return err
} }
if !canEdit { if !canEdit {
return models.ErrFolderAccessDenied return dashboards.ErrFolderAccessDenied
} }
return nil return nil
@ -66,7 +67,7 @@ func (l *LibraryElementService) requireViewPermissionsOnFolder(ctx context.Conte
return err return err
} }
if !canView { if !canView {
return models.ErrFolderAccessDenied return dashboards.ErrFolderAccessDenied
} }
return nil return nil
@ -80,7 +81,7 @@ func (l *LibraryElementService) requireEditPermissionsOnDashboard(ctx context.Co
return err return err
} }
if !canEdit { if !canEdit {
return models.ErrDashboardUpdateAccessDenied return dashboards.ErrDashboardUpdateAccessDenied
} }
return nil return nil

View File

@ -77,7 +77,7 @@ func TestDeleteLibraryPanelsInFolder(t *testing.T) {
scenarioWithPanel(t, "When an admin tries to delete a folder uid that doesn't exist, it should fail", scenarioWithPanel(t, "When an admin tries to delete a folder uid that doesn't exist, it should fail",
func(t *testing.T, sc scenarioContext) { func(t *testing.T, sc scenarioContext) {
err := sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.Uid+"xxxx") err := sc.service.DeleteLibraryElementsInFolder(sc.reqContext.Req.Context(), sc.reqContext.SignedInUser, sc.folder.Uid+"xxxx")
require.EqualError(t, err, models.ErrFolderNotFound.Error()) require.EqualError(t, err, dashboards.ErrFolderNotFound.Error())
}) })
scenarioWithPanel(t, "When an admin tries to delete a folder that contains disconnected elements, it should delete all disconnected elements too", scenarioWithPanel(t, "When an admin tries to delete a folder that contains disconnected elements, it should delete all disconnected elements too",

View File

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
@ -495,7 +496,7 @@ func toNamespaceErrorResponse(err error) response.Response {
if errors.Is(err, ngmodels.ErrCannotEditNamespace) { if errors.Is(err, ngmodels.ErrCannotEditNamespace) {
return ErrResp(http.StatusForbidden, err, err.Error()) return ErrResp(http.StatusForbidden, err, err.Error())
} }
if errors.Is(err, models.ErrDashboardIdentifierNotSet) { if errors.Is(err, dashboards.ErrDashboardIdentifierNotSet) {
return ErrResp(http.StatusBadRequest, err, err.Error()) return ErrResp(http.StatusBadRequest, err, err.Error())
} }
return apierrors.ToFolderErrorResponse(err) return apierrors.ToFolderErrorResponse(err)

View File

@ -9,7 +9,6 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/grafana/grafana/pkg/components/imguploader" "github.com/grafana/grafana/pkg/components/imguploader"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store" "github.com/grafana/grafana/pkg/services/ngalert/store"
@ -111,7 +110,7 @@ func (s *ScreenshotImageService) NewImage(ctx context.Context, r *ngmodels.Alert
if err != nil { if err != nil {
// TODO: Check for screenshot upload failures. These images should still be // TODO: Check for screenshot upload failures. These images should still be
// stored because we have a local disk path that could be useful. // stored because we have a local disk path that could be useful.
if errors.Is(err, models.ErrDashboardNotFound) { if errors.Is(err, dashboards.ErrDashboardNotFound) {
return nil, ErrNoDashboard return nil, ErrNoDashboard
} }
return nil, err return nil, err

View File

@ -302,12 +302,12 @@ func (fr *FileReader) getOrCreateFolderID(ctx context.Context, cfg *config, serv
cmd := &models.GetDashboardQuery{Slug: models.SlugifyTitle(folderName), OrgId: cfg.OrgID} cmd := &models.GetDashboardQuery{Slug: models.SlugifyTitle(folderName), OrgId: cfg.OrgID}
err := fr.dashboardStore.GetDashboard(ctx, cmd) err := fr.dashboardStore.GetDashboard(ctx, cmd)
if err != nil && !errors.Is(err, models.ErrDashboardNotFound) { if err != nil && !errors.Is(err, dashboards.ErrDashboardNotFound) {
return 0, err return 0, err
} }
// dashboard folder not found. create one. // dashboard folder not found. create one.
if errors.Is(err, models.ErrDashboardNotFound) { if errors.Is(err, dashboards.ErrDashboardNotFound) {
dash := &dashboards.SaveDashboardDTO{} dash := &dashboards.SaveDashboardDTO{}
dash.Dashboard = models.NewDashboardFolder(folderName) dash.Dashboard = models.NewDashboardFolder(folderName)
dash.Dashboard.IsFolder = true dash.Dashboard.IsFolder = true
@ -315,7 +315,7 @@ func (fr *FileReader) getOrCreateFolderID(ctx context.Context, cfg *config, serv
dash.OrgId = cfg.OrgID dash.OrgId = cfg.OrgID
// set dashboard folderUid if given // set dashboard folderUid if given
if cfg.FolderUID == accesscontrol.GeneralFolderUID { if cfg.FolderUID == accesscontrol.GeneralFolderUID {
return 0, models.ErrFolderInvalidUID return 0, dashboards.ErrFolderInvalidUID
} }
dash.Dashboard.SetUid(cfg.FolderUID) dash.Dashboard.SetUid(cfg.FolderUID)
dbDash, err := service.SaveFolderForProvisionedDashboards(ctx, dash) dbDash, err := service.SaveFolderForProvisionedDashboards(ctx, dash)

View File

@ -8,13 +8,14 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
) )
const ( const (
@ -405,7 +406,7 @@ func TestDashboardFileReader(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
_, err = r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder) _, err = r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
require.ErrorIs(t, err, models.ErrFolderInvalidUID) require.ErrorIs(t, err, dashboards.ErrFolderInvalidUID)
}) })
t.Run("Walking the folder with dashboards", func(t *testing.T) { t.Run("Walking the folder with dashboards", func(t *testing.T) {
@ -513,5 +514,5 @@ func (ffi FakeFileInfo) Sys() interface{} {
type fakeDashboardStore struct{} type fakeDashboardStore struct{}
func (fds *fakeDashboardStore) GetDashboard(_ context.Context, _ *models.GetDashboardQuery) error { func (fds *fakeDashboardStore) GetDashboard(_ context.Context, _ *models.GetDashboardQuery) error {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }

View File

@ -67,7 +67,7 @@ func createDashboardJSON(data *simplejson.Json, lastModified time.Time, cfg *con
dash.Dashboard.FolderId = folderID dash.Dashboard.FolderId = folderID
if dash.Dashboard.Title == "" { if dash.Dashboard.Title == "" {
return nil, models.ErrDashboardTitleEmpty return nil, dashboards.ErrDashboardTitleEmpty
} }
return dash, nil return dash, nil

View File

@ -79,7 +79,7 @@ func TestBrowserScreenshotService(t *testing.T) {
s := NewBrowserScreenshotService(&d, r) s := NewBrowserScreenshotService(&d, r)
// a non-existent dashboard should return error // a non-existent dashboard should return error
d.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Return(models.ErrDashboardNotFound).Once() d.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Return(dashboards.ErrDashboardNotFound).Once()
ctx := context.Background() ctx := context.Background()
opts := ScreenshotOptions{} opts := ScreenshotOptions{}
screenshot, err := s.Take(ctx, opts) screenshot, err := s.Take(ctx, opts)

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
) )
func (ss *SQLStore) GetThumbnail(ctx context.Context, query *models.GetDashboardThumbnailCommand) (*models.DashboardThumbnail, error) { func (ss *SQLStore) GetThumbnail(ctx context.Context, query *models.GetDashboardThumbnailCommand) (*models.DashboardThumbnail, error) {
@ -25,7 +26,7 @@ func (ss *SQLStore) SaveThumbnail(ctx context.Context, cmd *models.SaveDashboard
err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error {
existing, err := findThumbnailByMeta(sess, cmd.DashboardThumbnailMeta) existing, err := findThumbnailByMeta(sess, cmd.DashboardThumbnailMeta)
if err != nil && !errors.Is(err, models.ErrDashboardThumbnailNotFound) { if err != nil && !errors.Is(err, dashboards.ErrDashboardThumbnailNotFound) {
return err return err
} }
@ -150,7 +151,7 @@ func findThumbnailByMeta(sess *DBSession, meta models.DashboardThumbnailMeta) (*
exists, err := sess.Get(result) exists, err := sess.Get(result)
if !exists { if !exists {
return nil, models.ErrDashboardThumbnailNotFound return nil, dashboards.ErrDashboardThumbnailNotFound
} }
if err != nil { if err != nil {
@ -174,7 +175,7 @@ func findDashboardIdByThumbMeta(sess *DBSession, meta models.DashboardThumbnailM
return nil, err return nil, err
} }
if !exists { if !exists {
return nil, models.ErrDashboardNotFound return nil, dashboards.ErrDashboardNotFound
} }
return result, err return result, err

View File

@ -5,11 +5,13 @@ import (
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion" dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/require"
) )
var theme = models.ThemeDark var theme = models.ThemeDark
@ -313,7 +315,7 @@ func updateTestDashboard(t *testing.T, sqlStore *SQLStore, dashboard *models.Das
if affectedRows, err := sess.Insert(dashVersion); err != nil { if affectedRows, err := sess.Insert(dashVersion); err != nil {
return err return err
} else if affectedRows == 0 { } else if affectedRows == 0 {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
return nil return nil

View File

@ -7,6 +7,7 @@ import (
"xorm.io/xorm" "xorm.io/xorm"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion" dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@ -151,7 +152,7 @@ func (m *folderHelper) generateNewDashboardUid(orgId int64) (string, error) {
} }
} }
return "", models.ErrDashboardFailedGenerateUniqueUid return "", dashboards.ErrDashboardFailedGenerateUniqueUid
} }
// based on SQLStore.UpdateDashboardACL() // based on SQLStore.UpdateDashboardACL()

View File

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion" dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@ -461,7 +462,7 @@ func insertTestDashboard(t *testing.T, sqlStore *SQLStore, title string, orgId i
if affectedRows, err := sess.Insert(dashVersion); err != nil { if affectedRows, err := sess.Insert(dashVersion); err != nil {
return err return err
} else if affectedRows == 0 { } else if affectedRows == 0 {
return models.ErrDashboardNotFound return dashboards.ErrDashboardNotFound
} }
return nil return nil

View File

@ -222,7 +222,7 @@ func (hs *thumbService) GetImage(c *models.ReqContext) {
Kind: models.ThumbnailKindDefault, Kind: models.ThumbnailKindDefault,
}) })
if errors.Is(err, models.ErrDashboardThumbnailNotFound) { if errors.Is(err, dashboards.ErrDashboardThumbnailNotFound) {
c.Resp.WriteHeader(404) c.Resp.WriteHeader(404)
return return
} }

View File

@ -12,15 +12,17 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboardimport" "github.com/grafana/grafana/pkg/services/dashboardimport"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/plugindashboards" "github.com/grafana/grafana/pkg/services/plugindashboards"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/tests/testinfra" "github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestDashboardQuota(t *testing.T) { func TestDashboardQuota(t *testing.T) {
@ -173,19 +175,19 @@ providers:
desc: "when updating provisioned dashboard using ID it should fail", desc: "when updating provisioned dashboard using ID it should fail",
dashboardData: fmt.Sprintf(`{"title":"just testing", "id": %d, "version": 1}`, dashboardID), dashboardData: fmt.Sprintf(`{"title":"just testing", "id": %d, "version": 1}`, dashboardID),
expStatus: http.StatusBadRequest, expStatus: http.StatusBadRequest,
expErrReason: models.ErrDashboardCannotSaveProvisionedDashboard.Reason, expErrReason: dashboards.ErrDashboardCannotSaveProvisionedDashboard.Reason,
}, },
{ {
desc: "when updating provisioned dashboard using UID it should fail", desc: "when updating provisioned dashboard using UID it should fail",
dashboardData: fmt.Sprintf(`{"title":"just testing", "uid": %q, "version": 1}`, dashboardUID), dashboardData: fmt.Sprintf(`{"title":"just testing", "uid": %q, "version": 1}`, dashboardUID),
expStatus: http.StatusBadRequest, expStatus: http.StatusBadRequest,
expErrReason: models.ErrDashboardCannotSaveProvisionedDashboard.Reason, expErrReason: dashboards.ErrDashboardCannotSaveProvisionedDashboard.Reason,
}, },
{ {
desc: "when updating dashboard using unknown ID, it should fail", desc: "when updating dashboard using unknown ID, it should fail",
dashboardData: `{"title":"just testing", "id": 42, "version": 1}`, dashboardData: `{"title":"just testing", "id": 42, "version": 1}`,
expStatus: http.StatusNotFound, expStatus: http.StatusNotFound,
expErrReason: models.ErrDashboardNotFound.Reason, expErrReason: dashboards.ErrDashboardNotFound.Reason,
}, },
{ {
desc: "when updating dashboard using unknown UID, it should succeed", desc: "when updating dashboard using unknown UID, it should succeed",
@ -247,7 +249,7 @@ providers:
dashboardErr := &errorResponseBody{} dashboardErr := &errorResponseBody{}
err = json.Unmarshal(b, dashboardErr) err = json.Unmarshal(b, dashboardErr)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, models.ErrDashboardCannotDeleteProvisionedDashboard.Reason, dashboardErr.Message) assert.Equal(t, dashboards.ErrDashboardCannotDeleteProvisionedDashboard.Reason, dashboardErr.Message)
}) })
}) })
} }