From 9de00c8eb2c0667ae064426b879f2cee30c5a9a4 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Thu, 30 Jun 2022 09:31:54 -0400 Subject: [PATCH] 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 --- pkg/api/apierrors/dashboard.go | 8 +- pkg/api/apierrors/folder.go | 28 +-- pkg/api/dashboard.go | 10 +- pkg/api/dashboard_public.go | 2 +- pkg/api/dashboard_public_test.go | 6 +- pkg/api/dashboard_snapshot.go | 5 +- pkg/api/dashboard_test.go | 36 ++-- pkg/api/folder_permission.go | 5 +- pkg/api/folder_permission_test.go | 4 +- pkg/api/folder_test.go | 43 ++-- pkg/models/dashboards.go | 163 --------------- pkg/models/dashboards_public.go | 25 --- pkg/models/folders.go | 14 -- pkg/services/dashboards/accesscontrol_test.go | 8 +- pkg/services/dashboards/database/database.go | 57 +++-- .../database/database_dashboard_public.go | 11 +- .../database_dashboard_public_test.go | 14 +- .../database/database_folder_test.go | 9 +- .../dashboards/database/database_test.go | 6 +- pkg/services/dashboards/errors.go | 197 ++++++++++++++++++ .../dashboards/service/dashboard_public.go | 11 +- .../service/dashboard_public_test.go | 13 +- .../dashboards/service/dashboard_service.go | 22 +- .../dashboard_service_integration_test.go | 62 +++--- .../service/dashboard_service_test.go | 26 +-- .../dashboards/service/folder_service.go | 40 ++-- .../dashboards/service/folder_service_test.go | 28 +-- .../dashboardsnapshots/database/database.go | 2 +- pkg/services/dashboardsnapshots/errors.go | 8 + .../dashverimpl/store_test.go | 7 +- pkg/services/libraryelements/api.go | 9 +- pkg/services/libraryelements/database.go | 3 +- pkg/services/libraryelements/guard.go | 9 +- .../libraryelements/libraryelements_test.go | 2 +- pkg/services/ngalert/api/api_ruler.go | 3 +- pkg/services/ngalert/image/service.go | 3 +- .../provisioning/dashboards/file_reader.go | 6 +- .../dashboards/file_reader_test.go | 11 +- pkg/services/provisioning/dashboards/types.go | 2 +- pkg/services/screenshot/screenshot_test.go | 2 +- pkg/services/sqlstore/dashboard_thumbs.go | 7 +- .../sqlstore/dashboard_thumbs_test.go | 6 +- .../sqlstore/migrations/ualert/permissions.go | 3 +- pkg/services/sqlstore/org_test.go | 3 +- pkg/services/thumbs/service.go | 2 +- .../api/dashboards/api_dashboards_test.go | 14 +- 46 files changed, 489 insertions(+), 466 deletions(-) create mode 100644 pkg/services/dashboards/errors.go create mode 100644 pkg/services/dashboardsnapshots/errors.go diff --git a/pkg/api/apierrors/dashboard.go b/pkg/api/apierrors/dashboard.go index a4003646143..5ce72dabeab 100644 --- a/pkg/api/apierrors/dashboard.go +++ b/pkg/api/apierrors/dashboard.go @@ -7,15 +7,15 @@ import ( "net/http" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/alerting" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/util" ) // ToDashboardErrorResponse returns a different response status according to the dashboard error type 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 body := dashboardErr.Body(); body != nil { 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) } - if errors.Is(err, models.ErrFolderNotFound) { + if errors.Is(err, dashboards.ErrFolderNotFound) { 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) } - var pluginErr models.UpdatePluginDashboardError + var pluginErr dashboards.UpdatePluginDashboardError if ok := errors.As(err, &pluginErr); ok { message := fmt.Sprintf("The dashboard belongs to plugin %s.", pluginErr.PluginId) // look up plugin name diff --git a/pkg/api/apierrors/folder.go b/pkg/api/apierrors/folder.go index da6fd4fa124..d6b8e84fc77 100644 --- a/pkg/api/apierrors/folder.go +++ b/pkg/api/apierrors/folder.go @@ -4,40 +4,40 @@ import ( "errors" "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" ) // ToFolderErrorResponse returns a different response status according to the folder error type func ToFolderErrorResponse(err error) response.Response { - var dashboardErr models.DashboardErr + var dashboardErr dashboards.DashboardErr if ok := errors.As(err, &dashboardErr); ok { return response.Error(dashboardErr.StatusCode, err.Error(), err) } - if errors.Is(err, models.ErrFolderTitleEmpty) || - errors.Is(err, models.ErrDashboardTypeMismatch) || - errors.Is(err, models.ErrDashboardInvalidUid) || - errors.Is(err, models.ErrDashboardUidTooLong) || - errors.Is(err, models.ErrFolderContainsAlertRules) { + if errors.Is(err, dashboards.ErrFolderTitleEmpty) || + errors.Is(err, dashboards.ErrDashboardTypeMismatch) || + errors.Is(err, dashboards.ErrDashboardInvalidUid) || + errors.Is(err, dashboards.ErrDashboardUidTooLong) || + errors.Is(err, dashboards.ErrFolderContainsAlertRules) { 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) } - if errors.Is(err, models.ErrFolderNotFound) { - return response.JSON(404, util.DynMap{"status": "not-found", "message": models.ErrFolderNotFound.Error()}) + if errors.Is(err, dashboards.ErrFolderNotFound) { + return response.JSON(404, util.DynMap{"status": "not-found", "message": dashboards.ErrFolderNotFound.Error()}) } - if errors.Is(err, models.ErrFolderSameNameExists) || - errors.Is(err, models.ErrFolderWithSameUIDExists) { + if errors.Is(err, dashboards.ErrFolderSameNameExists) || + errors.Is(err, dashboards.ErrFolderWithSameUIDExists) { return response.Error(409, err.Error(), nil) } - if errors.Is(err, models.ErrFolderVersionMismatch) { - return response.JSON(412, util.DynMap{"status": "version-mismatch", "message": models.ErrFolderVersionMismatch.Error()}) + if errors.Is(err, dashboards.ErrFolderVersionMismatch) { + return response.JSON(412, util.DynMap{"status": "version-mismatch", "message": dashboards.ErrFolderVersionMismatch.Error()}) } return response.Error(500, "Folder API error", err) diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 49d6f293f2a..8b07440df31 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -145,7 +145,7 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response { if dash.FolderId > 0 { query := models.GetDashboardQuery{Id: dash.FolderId, OrgId: c.OrgId} 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(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) if err != nil { - var dashboardErr models.DashboardErr + var dashboardErr dashboards.DashboardErr 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) } } @@ -333,7 +333,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa if cmd.FolderUid != "" { folder, err := hs.folderService.GetFolderByUID(ctx, c.SignedInUser, c.OrgId, cmd.FolderUid) 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(500, "Error while checking folder ID", err) @@ -362,7 +362,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa provisioningData = data } else if 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) } provisioningData = data diff --git a/pkg/api/dashboard_public.go b/pkg/api/dashboard_public.go index 5399f21a722..9850a9cee5d 100644 --- a/pkg/api/dashboard_public.go +++ b/pkg/api/dashboard_public.go @@ -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 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 { return response.Error(dashboardErr.StatusCode, dashboardErr.Error(), dashboardErr) diff --git a/pkg/api/dashboard_public_test.go b/pkg/api/dashboard_public_test.go index 787e16f4813..b916ddd2c74 100644 --- a/pkg/api/dashboard_public_test.go +++ b/pkg/api/dashboard_public_test.go @@ -87,7 +87,7 @@ func TestAPIGetPublicDashboard(t *testing.T) { AccessToken: accessToken, ExpectedHttpResponse: http.StatusNotFound, publicDashboardResult: nil, - publicDashboardErr: models.ErrPublicDashboardNotFound, + publicDashboardErr: dashboards.ErrPublicDashboardNotFound, }, } @@ -153,7 +153,7 @@ func TestAPIGetPublicDashboardConfig(t *testing.T) { DashboardUid: "77777", ExpectedHttpResponse: http.StatusNotFound, PublicDashboardResult: nil, - PublicDashboardError: models.ErrDashboardNotFound, + PublicDashboardError: dashboards.ErrDashboardNotFound, }, { Name: "returns 500 when internal server error", @@ -218,7 +218,7 @@ func TestApiSavePublicDashboardConfig(t *testing.T) { Name: "returns 404 when dashboard not found", ExpectedHttpResponse: http.StatusNotFound, publicDashboardConfig: &models.PublicDashboard{}, - saveDashboardError: models.ErrDashboardNotFound, + saveDashboardError: dashboards.ErrDashboardNotFound, }, } diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index 79ae82d3930..05e57abbdec 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/metrics" "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/guardian" "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) canEdit, err := guardian.CanEdit() // 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) } - 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) } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index d79fdbfab6f..7ababd06b01 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -9,7 +9,6 @@ import ( "net/http" "testing" - "github.com/grafana/grafana/pkg/framework/coremodel/registry" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -18,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" "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/models" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" @@ -679,24 +679,24 @@ func TestDashboardAPIEndpoint(t *testing.T) { SaveError error ExpectedStatusCode int }{ - {SaveError: models.ErrDashboardNotFound, ExpectedStatusCode: 404}, - {SaveError: models.ErrFolderNotFound, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412}, - {SaveError: models.ErrDashboardVersionMismatch, ExpectedStatusCode: 412}, - {SaveError: models.ErrDashboardTitleEmpty, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardNotFound, ExpectedStatusCode: 404}, + {SaveError: dashboards.ErrFolderNotFound, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardWithSameUIDExists, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedStatusCode: 412}, + {SaveError: dashboards.ErrDashboardVersionMismatch, ExpectedStatusCode: 412}, + {SaveError: dashboards.ErrDashboardTitleEmpty, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardFolderCannotHaveParent, ExpectedStatusCode: 400}, {SaveError: alerting.ValidationError{Reason: "Mu"}, ExpectedStatusCode: 422}, - {SaveError: models.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500}, - {SaveError: models.ErrDashboardTypeMismatch, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardFolderNameExists, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403}, - {SaveError: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, - {SaveError: models.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400}, - {SaveError: models.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412}, + {SaveError: dashboards.ErrDashboardFailedGenerateUniqueUid, ExpectedStatusCode: 500}, + {SaveError: dashboards.ErrDashboardTypeMismatch, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardFolderWithSameNameAsDashboard, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardWithSameNameAsFolder, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardFolderNameExists, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedStatusCode: 403}, + {SaveError: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, + {SaveError: dashboards.ErrDashboardCannotSaveProvisionedDashboard, ExpectedStatusCode: 400}, + {SaveError: dashboards.UpdatePluginDashboardError{PluginId: "plug"}, ExpectedStatusCode: 412}, } cmd := models.SaveDashboardCommand{ diff --git a/pkg/api/folder_permission.go b/pkg/api/folder_permission.go index edaff924528..e622c21ab05 100644 --- a/pkg/api/folder_permission.go +++ b/pkg/api/folder_permission.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "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/services/guardian" "github.com/grafana/grafana/pkg/util" "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) if canAdmin, err := g.CanAdmin(); err != nil || !canAdmin { - return apierrors.ToFolderErrorResponse(models.ErrFolderAccessDenied) + return apierrors.ToFolderErrorResponse(dashboards.ErrFolderAccessDenied) } acl, err := g.GetAcl() @@ -78,7 +79,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res } if !canAdmin { - return apierrors.ToFolderErrorResponse(models.ErrFolderAccessDenied) + return apierrors.ToFolderErrorResponse(dashboards.ErrFolderAccessDenied) } var items []*models.DashboardAcl diff --git a/pkg/api/folder_permission_test.go b/pkg/api/folder_permission_test.go index 6ed7c2305ac..48e88febbed 100644 --- a/pkg/api/folder_permission_test.go +++ b/pkg/api/folder_permission_test.go @@ -49,7 +49,7 @@ func TestFolderPermissionAPIEndpoint(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() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { callGetFolderPermissions(sc, hs) @@ -81,7 +81,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { }) 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() loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", models.ROLE_EDITOR, func(sc *scenarioContext) { diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index 24a448e451d..e321cd0e7ca 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -7,6 +7,10 @@ import ( "net/http" "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/response" "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/setting" "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) { @@ -55,15 +56,15 @@ func TestFoldersAPIEndpoint(t *testing.T) { Error error ExpectedStatusCode int }{ - {Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, - {Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, - {Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 409}, - {Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, - {Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, - {Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, - {Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, - {Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, + {Error: dashboards.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, + {Error: dashboards.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, + {Error: dashboards.ErrFolderSameNameExists, ExpectedStatusCode: 409}, + {Error: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, + {Error: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, + {Error: dashboards.ErrFolderAccessDenied, ExpectedStatusCode: 403}, + {Error: dashboards.ErrFolderNotFound, ExpectedStatusCode: 404}, + {Error: dashboards.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, + {Error: dashboards.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, } cmd := models.CreateFolderCommand{ @@ -110,15 +111,15 @@ func TestFoldersAPIEndpoint(t *testing.T) { Error error ExpectedStatusCode int }{ - {Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, - {Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, - {Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 409}, - {Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, - {Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, - {Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403}, - {Error: models.ErrFolderNotFound, ExpectedStatusCode: 404}, - {Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, - {Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, + {Error: dashboards.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409}, + {Error: dashboards.ErrFolderTitleEmpty, ExpectedStatusCode: 400}, + {Error: dashboards.ErrFolderSameNameExists, ExpectedStatusCode: 409}, + {Error: dashboards.ErrDashboardInvalidUid, ExpectedStatusCode: 400}, + {Error: dashboards.ErrDashboardUidTooLong, ExpectedStatusCode: 400}, + {Error: dashboards.ErrFolderAccessDenied, ExpectedStatusCode: 403}, + {Error: dashboards.ErrFolderNotFound, ExpectedStatusCode: 404}, + {Error: dashboards.ErrFolderVersionMismatch, ExpectedStatusCode: 412}, + {Error: dashboards.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, } cmd := models.UpdateFolderCommand{ diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index c10fd39e22c..a289d2c9a72 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -10,173 +10,10 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/setting" - "github.com/grafana/grafana/pkg/util" ) 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 ( DashTypeDB = "db" DashTypeSnapshot = "snapshot" diff --git a/pkg/models/dashboards_public.go b/pkg/models/dashboards_public.go index 4835b1eabad..a05492c6ea8 100644 --- a/pkg/models/dashboards_public.go +++ b/pkg/models/dashboards_public.go @@ -6,31 +6,6 @@ import ( "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 { Uid string `json:"uid" xorm:"pk uid"` DashboardUid string `json:"dashboardUid" xorm:"dashboard_uid"` diff --git a/pkg/models/folders.go b/pkg/models/folders.go index 895cfea2a5c..27cf338f102 100644 --- a/pkg/models/folders.go +++ b/pkg/models/folders.go @@ -1,24 +1,10 @@ package models import ( - "errors" "strings" "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 { Id int64 Uid string diff --git a/pkg/services/dashboards/accesscontrol_test.go b/pkg/services/dashboards/accesscontrol_test.go index dc8f2ba5589..d9e57e44657 100644 --- a/pkg/services/dashboards/accesscontrol_test.go +++ b/pkg/services/dashboards/accesscontrol_test.go @@ -64,12 +64,12 @@ func TestNewFolderNameScopeResolver(t *testing.T) { _, resolver := NewFolderNameScopeResolver(dashboardStore) 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() resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope) - require.ErrorIs(t, err, models.ErrDashboardNotFound) + require.ErrorIs(t, err, ErrDashboardNotFound) require.Nil(t, resolvedScopes) }) } @@ -136,11 +136,11 @@ func TestNewFolderIDScopeResolver(t *testing.T) { _, resolver := NewFolderIDScopeResolver(dashboardStore) 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" resolvedScopes, err := resolver.Resolve(context.Background(), orgId, scope) - require.ErrorIs(t, err, models.ErrDashboardNotFound) + require.ErrorIs(t, err, ErrDashboardNotFound) require.Nil(t, resolvedScopes) }) } diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 847654c416c..41b3fb271bf 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -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) { if title == "" { - return nil, models.ErrFolderTitleEmpty + return nil, dashboards.ErrFolderTitleEmpty } // 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 } if !has { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } dashboard.SetId(dashboard.Id) dashboard.SetUid(dashboard.Uid) @@ -89,7 +89,7 @@ func (d *DashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int6 return err } if !has { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } dashboard.SetId(dashboard.Id) 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) { if uid == "" { - return nil, models.ErrDashboardIdentifierNotSet + return nil, dashboards.ErrDashboardIdentifierNotSet } 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 } if !has { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } dashboard.SetId(dashboard.Id) dashboard.SetUid(dashboard.Uid) @@ -147,15 +147,14 @@ func (d *DashboardStore) GetProvisionedDataByDashboardUID(orgID int64, dashboard return err } if !exists { - return models. - ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } exists, err = sess.Where("dashboard_id = ?", dashboard.Id).Get(&provisionedDashboard) if err != nil { return err } if !exists { - return models.ErrProvisionedDashboardNotFound + return dashboards.ErrProvisionedDashboardNotFound } return nil }) @@ -267,7 +266,7 @@ func (d *DashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context for _, deleteDashCommand := range result { 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 } } @@ -289,7 +288,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode } if !dashWithIdExists { - return false, models.ErrDashboardNotFound + return false, dashboards.ErrDashboardNotFound } if dash.Uid == "" { @@ -317,7 +316,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode } 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 { - return false, models.ErrDashboardWithSameUIDExists + return false, dashboards.ErrDashboardWithSameUIDExists } existing := existingById @@ -339,7 +338,7 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode if (existing.IsFolder && !dash.IsFolder) || (!existing.IsFolder && dash.IsFolder) { - return isParentFolderChanged, models.ErrDashboardTypeMismatch + return isParentFolderChanged, dashboards.ErrDashboardTypeMismatch } if !dash.IsFolder && dash.FolderId != existing.FolderId { @@ -351,13 +350,13 @@ func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *mode if overwrite { dash.SetVersion(existing.Version) } else { - return isParentFolderChanged, models.ErrDashboardVersionMismatch + return isParentFolderChanged, dashboards.ErrDashboardVersionMismatch } } // do not allow plugin dashboard updates without overwrite flag if existing.PluginId != "" && !overwrite { - return isParentFolderChanged, models.UpdatePluginDashboardError{PluginId: existing.PluginId} + return isParentFolderChanged, dashboards.UpdatePluginDashboardError{PluginId: existing.PluginId} } return isParentFolderChanged, nil @@ -374,11 +373,11 @@ func getExistingDashboardByTitleAndFolder(sess *sqlstore.DBSession, dash *models if exists && dash.Id != existing.Id { if existing.IsFolder && !dash.IsFolder { - return isParentFolderChanged, models.ErrDashboardWithSameNameAsFolder + return isParentFolderChanged, dashboards.ErrDashboardWithSameNameAsFolder } if !existing.IsFolder && dash.IsFolder { - return isParentFolderChanged, models.ErrDashboardFolderWithSameNameAsDashboard + return isParentFolderChanged, dashboards.ErrDashboardFolderWithSameNameAsDashboard } 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.SetVersion(existing.Version) } else { - return isParentFolderChanged, models.ErrDashboardWithSameNameInFolderExists + return isParentFolderChanged, dashboards.ErrDashboardWithSameNameInFolderExists } } @@ -413,7 +412,7 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e return err } if !dashWithIdExists { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } // 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 { dash.SetVersion(existing.Version) } else { - return models.ErrDashboardVersionMismatch + return dashboards.ErrDashboardVersionMismatch } } // do not allow plugin dashboard updates without overwrite flag 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 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } dashVersion := &dashver.DashboardVersion{ @@ -488,7 +487,7 @@ func saveDashboard(sess *sqlstore.DBSession, cmd *models.SaveDashboardCommand) e if affectedRows, err = sess.Insert(dashVersion); err != nil { return err } else if affectedRows == 0 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } // 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 { @@ -709,7 +708,7 @@ func (d *DashboardStore) deleteDashboard(cmd *models.DeleteDashboardCommand, ses if err != nil { return err } else if !has { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } deletes := []string{ @@ -779,7 +778,7 @@ func (d *DashboardStore) deleteDashboard(cmd *models.DeleteDashboardCommand, ses } if exists { 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. @@ -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) { err := d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { 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} @@ -845,7 +844,7 @@ func (d *DashboardStore) GetDashboard(ctx context.Context, query *models.GetDash if err != nil { return err } else if !has { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } dashboard.SetId(dashboard.Id) @@ -865,7 +864,7 @@ func (d *DashboardStore) GetDashboardUIDById(ctx context.Context, query *models. if err != nil { return err } else if !exists { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } query.Result = us return nil diff --git a/pkg/services/dashboards/database/database_dashboard_public.go b/pkg/services/dashboards/database/database_dashboard_public.go index 0c5ff91aa70..e2974633200 100644 --- a/pkg/services/dashboards/database/database_dashboard_public.go +++ b/pkg/services/dashboards/database/database_dashboard_public.go @@ -4,6 +4,7 @@ import ( "context" "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/util" ) @@ -11,7 +12,7 @@ import ( // retrieves public dashboard configuration func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken string) (*models.PublicDashboard, *models.Dashboard, error) { if accessToken == "" { - return nil, nil, models.ErrPublicDashboardIdentifierNotSet + return nil, nil, dashboards.ErrPublicDashboardIdentifierNotSet } // get public dashboard @@ -22,7 +23,7 @@ func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken str return err } if !has { - return models.ErrPublicDashboardNotFound + return dashboards.ErrPublicDashboardNotFound } return nil }) @@ -39,7 +40,7 @@ func (d *DashboardStore) GetPublicDashboard(ctx context.Context, accessToken str return err } if !has { - return models.ErrPublicDashboardNotFound + return dashboards.ErrPublicDashboardNotFound } return nil }) @@ -69,7 +70,7 @@ func (d *DashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (str } } - return models.ErrPublicDashboardFailedGenerateUniqueUid + return dashboards.ErrPublicDashboardFailedGenerateUniqueUid }) if err != nil { @@ -82,7 +83,7 @@ func (d *DashboardStore) GenerateNewPublicDashboardUid(ctx context.Context) (str // retrieves public dashboard configuration func (d *DashboardStore) GetPublicDashboardConfig(ctx context.Context, orgId int64, dashboardUid string) (*models.PublicDashboard, error) { if dashboardUid == "" { - return nil, models.ErrDashboardIdentifierNotSet + return nil, dashboards.ErrDashboardIdentifierNotSet } pdRes := &models.PublicDashboard{OrgId: orgId, DashboardUid: dashboardUid} diff --git a/pkg/services/dashboards/database/database_dashboard_public_test.go b/pkg/services/dashboards/database/database_dashboard_public_test.go index 268781cef1d..6462b9bf50a 100644 --- a/pkg/services/dashboards/database/database_dashboard_public_test.go +++ b/pkg/services/dashboards/database/database_dashboard_public_test.go @@ -5,13 +5,15 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/components/simplejson" "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/sqlstore" "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 @@ -58,13 +60,13 @@ func TestIntegrationGetPublicDashboard(t *testing.T) { t.Run("returns ErrPublicDashboardNotFound with empty uid", func(t *testing.T) { setup() _, _, 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) { setup() _, _, 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) { @@ -83,7 +85,7 @@ func TestIntegrationGetPublicDashboard(t *testing.T) { }) require.NoError(t, err) _, _, 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) { setup() _, 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) { diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index 5d1378d65f1..586e8e2e453 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "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/user" ) @@ -492,12 +493,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("should not find dashboard", func(t *testing.T) { d, err := dashboardStore.GetFolderByUID(context.Background(), orgId, dash.Uid) 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) { d, err := dashboardStore.GetFolderByUID(context.Background(), orgId+1, folder.Uid) 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) { d, err := dashboardStore.GetFolderByID(context.Background(), orgId, dash.Id) 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) { d, err := dashboardStore.GetFolderByID(context.Background(), orgId+1, folder.Id) require.Nil(t, d) - require.ErrorIs(t, err, models.ErrFolderNotFound) + require.ErrorIs(t, err, dashboards.ErrFolderNotFound) }) }) }) diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index 3f692f26f45..d0332cebc99 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -124,7 +124,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { } _, 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) { @@ -227,7 +227,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { setup() deleteCmd := &models.DeleteDashboardCommand{Id: savedFolder.Id, ForceDeleteFolderRules: false} 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) { @@ -274,7 +274,7 @@ func TestIntegrationDashboardDataAccess(t *testing.T) { } _, 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) { diff --git a/pkg/services/dashboards/errors.go b/pkg/services/dashboards/errors.go new file mode 100644 index 00000000000..4ee627bcd74 --- /dev/null +++ b/pkg/services/dashboards/errors.go @@ -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" +} diff --git a/pkg/services/dashboards/service/dashboard_public.go b/pkg/services/dashboards/service/dashboard_public.go index a0e5c790abe..1e1c3124e39 100644 --- a/pkg/services/dashboards/service/dashboard_public.go +++ b/pkg/services/dashboards/service/dashboard_public.go @@ -6,6 +6,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" @@ -21,11 +22,11 @@ func (dr *DashboardServiceImpl) GetPublicDashboard(ctx context.Context, accessTo } if pubdash == nil || d == nil { - return nil, models.ErrPublicDashboardNotFound + return nil, dashboards.ErrPublicDashboardNotFound } if !pubdash.IsEnabled { - return nil, models.ErrPublicDashboardNotFound + return nil, dashboards.ErrPublicDashboardNotFound } 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 func (dr *DashboardServiceImpl) SavePublicDashboardConfig(ctx context.Context, dto *dashboards.SavePublicDashboardConfigDTO) (*models.PublicDashboard, error) { if len(dto.DashboardUid) == 0 { - return nil, models.ErrDashboardIdentifierNotSet + return nil, dashboards.ErrDashboardIdentifierNotSet } // 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 func (dr *DashboardServiceImpl) BuildPublicDashboardMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *models.PublicDashboard, panelId int64) (dtos.MetricRequest, error) { if !publicDashboard.IsEnabled { - return dtos.MetricRequest{}, models.ErrPublicDashboardNotFound + return dtos.MetricRequest{}, dashboards.ErrPublicDashboardNotFound } queriesByPanel := models.GetQueriesFromDashboard(dashboard.Data) if _, ok := queriesByPanel[panelId]; !ok { - return dtos.MetricRequest{}, models.ErrPublicDashboardPanelNotFound + return dtos.MetricRequest{}, dashboards.ErrPublicDashboardPanelNotFound } ts := publicDashboard.BuildTimeSettings(dashboard) diff --git a/pkg/services/dashboards/service/dashboard_public_test.go b/pkg/services/dashboards/service/dashboard_public_test.go index 758d8ee8371..d6bbfd43ca7 100644 --- a/pkg/services/dashboards/service/dashboard_public_test.go +++ b/pkg/services/dashboards/service/dashboard_public_test.go @@ -6,15 +6,16 @@ import ( "time" "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/infra/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" "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"}`)) @@ -66,21 +67,21 @@ func TestGetPublicDashboard(t *testing.T) { d: &models.Dashboard{Uid: "mydashboard"}, err: nil, }, - ErrResp: models.ErrPublicDashboardNotFound, + ErrResp: dashboards.ErrPublicDashboardNotFound, DashResp: nil, }, { Name: "returns ErrPublicDashboardNotFound if PublicDashboard missing", AccessToken: "abc123", StoreResp: &storeResp{pd: nil, d: nil, err: nil}, - ErrResp: models.ErrPublicDashboardNotFound, + ErrResp: dashboards.ErrPublicDashboardNotFound, DashResp: nil, }, { Name: "returns ErrPublicDashboardNotFound if Dashboard missing", AccessToken: "abc123", StoreResp: &storeResp{pd: nil, d: nil, err: nil}, - ErrResp: models.ErrPublicDashboardNotFound, + ErrResp: dashboards.ErrPublicDashboardNotFound, DashResp: nil, }, } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index fc193ca4888..f67643b5dcf 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -83,21 +83,21 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d dash.SetUid(strings.TrimSpace(dash.Uid)) if dash.Title == "" { - return nil, models.ErrDashboardTitleEmpty + return nil, dashboards.ErrDashboardTitleEmpty } if dash.IsFolder && dash.FolderId > 0 { - return nil, models.ErrDashboardFolderCannotHaveParent + return nil, dashboards.ErrDashboardFolderCannotHaveParent } if dash.IsFolder && strings.EqualFold(dash.Title, models.RootFolderName) { - return nil, models.ErrDashboardFolderNameExists + return nil, dashboards.ErrDashboardFolderNameExists } if !util.IsValidShortUID(dash.Uid) { - return nil, models.ErrDashboardInvalidUid + return nil, dashboards.ErrDashboardInvalidUid } else if util.IsShortUIDTooLong(dash.Uid) { - return nil, models.ErrDashboardUidTooLong + return nil, dashboards.ErrDashboardUidTooLong } if err := validateDashboardRefreshInterval(dash); err != nil { @@ -123,7 +123,7 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d if err != nil { 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 { - return nil, models.ErrDashboardCannotSaveProvisionedDashboard + return nil, dashboards.ErrDashboardCannotSaveProvisionedDashboard } } @@ -144,14 +144,14 @@ func (dr *DashboardServiceImpl) BuildSaveDashboardCommand(ctx context.Context, d if err != nil { return nil, err } - return nil, models.ErrDashboardUpdateAccessDenied + return nil, dashboards.ErrDashboardUpdateAccessDenied } } else { if canSave, err := guard.CanSave(); err != nil || !canSave { if err != nil { return nil, err } - return nil, models.ErrDashboardUpdateAccessDenied + return nil, dashboards.ErrDashboardUpdateAccessDenied } } @@ -202,7 +202,7 @@ func validateDashboardRefreshInterval(dash *models.Dashboard) error { } if d < minRefreshInterval { - return models.ErrDashboardRefreshIntervalTooShort + return dashboards.ErrDashboardRefreshIntervalTooShort } return nil @@ -414,7 +414,7 @@ func (dr *DashboardServiceImpl) deleteDashboard(ctx context.Context, dashboardId } if provisionedData != nil { - return models.ErrDashboardCannotDeleteProvisionedDashboard + return dashboards.ErrDashboardCannotDeleteProvisionedDashboard } } cmd := &models.DeleteDashboardCommand{OrgId: orgId, Id: dashboardId} diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 0cf92da3839..dad3576988e 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/models" accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "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/featuremgmt" "github.com/grafana/grafana/pkg/services/guardian" @@ -39,7 +39,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } err := callSaveWithError(cmd, sc.sqlStore) - assert.Equal(t, models.ErrDashboardNotFound, err) + assert.Equal(t, dashboards.ErrDashboardNotFound, err) }) // Given other organization @@ -59,7 +59,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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", @@ -101,7 +101,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -121,7 +121,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -141,7 +141,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -162,7 +162,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -183,7 +183,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -204,7 +204,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -225,7 +225,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -246,7 +246,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -267,7 +267,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -288,7 +288,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, cmd.OrgId, sc.dashboardGuardianMock.OrgId) @@ -429,7 +429,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, @@ -445,7 +445,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, @@ -485,7 +485,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, @@ -524,7 +524,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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", @@ -540,7 +540,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, @@ -556,7 +556,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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) - 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, @@ -708,7 +708,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, @@ -724,7 +724,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, @@ -740,7 +740,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, @@ -756,7 +756,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, @@ -771,7 +771,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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, @@ -786,7 +786,7 @@ func TestIntegrationIntegratedDashboardService(t *testing.T) { } 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 { dashboardGuardianMock *guardian.FakeDashboardGuardian sqlStore *sqlstore.SQLStore - dashboardStore dashbboardservice.Store + dashboardStore dashboards.Store savedFolder *models.Dashboard savedDashInFolder *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, Dashboard: cmd.GetDashboardModel(), 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, Dashboard: cmd.GetDashboardModel(), User: &models.SignedInUser{ @@ -975,10 +975,10 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore *sqlstore. return res } -func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashbboardservice.SaveDashboardDTO { +func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO { dash := (&cmd).GetDashboardModel() - return dashbboardservice.SaveDashboardDTO{ + return dashboards.SaveDashboardDTO{ Dashboard: dash, Message: cmd.Message, OrgId: cmd.OrgId, diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index dc9dc3cfefc..652d245f823 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "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/setting" ) @@ -21,7 +21,7 @@ func TestIntegrationDashboardService(t *testing.T) { t.Skip("skipping integration test") } t.Run("Dashboard service tests", func(t *testing.T) { - fakeStore := m.FakeDashboardStore{} + fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) service := &DashboardServiceImpl{ log: log.New("test.logger"), @@ -34,7 +34,7 @@ func TestIntegrationDashboardService(t *testing.T) { guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) 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) { titles := []string{"", " ", " \t "} @@ -42,7 +42,7 @@ func TestIntegrationDashboardService(t *testing.T) { for _, title := range titles { dto.Dashboard = models.NewDashboard(title) _, 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.FolderId = 1 _, 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) { dto.Dashboard = models.NewDashboardFolder("General") _, 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) { @@ -68,9 +68,9 @@ func TestIntegrationDashboardService(t *testing.T) { {Uid: " ", Error: nil}, {Uid: " \t ", Error: nil}, {Uid: "asdf90_-", Error: nil}, - {Uid: "asdf/90", Error: models.ErrDashboardInvalidUid}, + {Uid: "asdf/90", Error: dashboards.ErrDashboardInvalidUid}, {Uid: " asdfghjklqwertyuiopzxcvbnmasdfghjklqwer ", Error: nil}, - {Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: models.ErrDashboardUidTooLong}, + {Uid: "asdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnmasdfghjklqwertyuiopzxcvbnm", Error: dashboards.ErrDashboardUidTooLong}, } for _, tc := range testCases { @@ -94,7 +94,7 @@ func TestIntegrationDashboardService(t *testing.T) { dto.Dashboard.SetId(3) dto.User = &models.SignedInUser{UserId: 1} _, 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) { @@ -123,7 +123,7 @@ func TestIntegrationDashboardService(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) { 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) { - dto := &m.SaveDashboardDTO{} + dto := &dashboards.SaveDashboardDTO{} 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() @@ -167,7 +167,7 @@ func TestIntegrationDashboardService(t *testing.T) { dto.Dashboard.SetId(3) dto.User = &models.SignedInUser{UserId: 1} _, 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) { fakeStore.On("GetProvisionedDataByDashboardID", mock.Anything).Return(&models.DashboardProvisioning{}, nil).Once() err := service.DeleteDashboard(context.Background(), 1, 1) - require.Equal(t, err, models.ErrDashboardCannotDeleteProvisionedDashboard) + require.Equal(t, err, dashboards.ErrDashboardCannotDeleteProvisionedDashboard) }) }) diff --git a/pkg/services/dashboards/service/folder_service.go b/pkg/services/dashboards/service/folder_service.go index c48c5bf7655..de4f3016821 100644 --- a/pkg/services/dashboards/service/folder_service.go +++ b/pkg/services/dashboards/service/folder_service.go @@ -94,7 +94,7 @@ func (f *FolderServiceImpl) GetFolderByID(ctx context.Context, user *models.Sign if err != nil { return nil, toFolderError(err) } - return nil, models.ErrFolderAccessDenied + return nil, dashboards.ErrFolderAccessDenied } return dashFolder, nil @@ -111,7 +111,7 @@ func (f *FolderServiceImpl) GetFolderByUID(ctx context.Context, user *models.Sig if err != nil { return nil, toFolderError(err) } - return nil, models.ErrFolderAccessDenied + return nil, dashboards.ErrFolderAccessDenied } return dashFolder, nil @@ -128,7 +128,7 @@ func (f *FolderServiceImpl) GetFolderByTitle(ctx context.Context, user *models.S if err != nil { return nil, toFolderError(err) } - return nil, models.ErrFolderAccessDenied + return nil, dashboards.ErrFolderAccessDenied } return dashFolder, nil @@ -140,7 +140,7 @@ func (f *FolderServiceImpl) CreateFolder(ctx context.Context, user *models.Signe trimmedUID := strings.TrimSpace(uid) if trimmedUID == accesscontrol.GeneralFolderUID { - return nil, models.ErrFolderInvalidUID + return nil, dashboards.ErrFolderInvalidUID } dashFolder.SetUid(trimmedUID) @@ -201,7 +201,7 @@ func (f *FolderServiceImpl) UpdateFolder(ctx context.Context, user *models.Signe dashFolder := query.Result if !dashFolder.IsFolder { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } cmd.UpdateDashboardModel(dashFolder, orgID, user.UserId) @@ -254,7 +254,7 @@ func (f *FolderServiceImpl) DeleteFolder(ctx context.Context, user *models.Signe if err != nil { return nil, toFolderError(err) } - return nil, models.ErrFolderAccessDenied + return nil, dashboards.ErrFolderAccessDenied } 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 { - if errors.Is(err, models.ErrDashboardTitleEmpty) { - return models.ErrFolderTitleEmpty + if errors.Is(err, dashboards.ErrDashboardTitleEmpty) { + return dashboards.ErrFolderTitleEmpty } - if errors.Is(err, models.ErrDashboardUpdateAccessDenied) { - return models.ErrFolderAccessDenied + if errors.Is(err, dashboards.ErrDashboardUpdateAccessDenied) { + return dashboards.ErrFolderAccessDenied } - if errors.Is(err, models.ErrDashboardWithSameNameInFolderExists) { - return models.ErrFolderSameNameExists + if errors.Is(err, dashboards.ErrDashboardWithSameNameInFolderExists) { + return dashboards.ErrFolderSameNameExists } - if errors.Is(err, models.ErrDashboardWithSameUIDExists) { - return models.ErrFolderWithSameUIDExists + if errors.Is(err, dashboards.ErrDashboardWithSameUIDExists) { + return dashboards.ErrFolderWithSameUIDExists } - if errors.Is(err, models.ErrDashboardVersionMismatch) { - return models.ErrFolderVersionMismatch + if errors.Is(err, dashboards.ErrDashboardVersionMismatch) { + return dashboards.ErrFolderVersionMismatch } - if errors.Is(err, models.ErrDashboardNotFound) { - return models.ErrFolderNotFound + if errors.Is(err, dashboards.ErrDashboardNotFound) { + return dashboards.ErrFolderNotFound } - if errors.Is(err, models.ErrDashboardFailedGenerateUniqueUid) { - err = models.ErrFolderFailedGenerateUniqueUid + if errors.Is(err, dashboards.ErrDashboardFailedGenerateUniqueUid) { + err = dashboards.ErrFolderFailedGenerateUniqueUid } return err diff --git a/pkg/services/dashboards/service/folder_service_test.go b/pkg/services/dashboards/service/folder_service_test.go index 99cf9168ab7..46c03f6830f 100644 --- a/pkg/services/dashboards/service/folder_service_test.go +++ b/pkg/services/dashboards/service/folder_service_test.go @@ -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) { _, 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) { @@ -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) { _, 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) { store.On("ValidateDashboardBeforeSave", mock.Anything, mock.Anything).Return(true, nil).Times(2) _, 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) { @@ -107,13 +107,13 @@ func TestIntegrationFolderService(t *testing.T) { Uid: folderUID, 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) { _, err := service.DeleteFolder(context.Background(), user, orgID, folderUID, false) require.Error(t, err) - require.Equal(t, err, models.ErrFolderAccessDenied) + require.Equal(t, err, dashboards.ErrFolderAccessDenied) }) t.Cleanup(func() { @@ -144,7 +144,7 @@ func TestIntegrationFolderService(t *testing.T) { dash.Id = rand.Int63() _, 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) { @@ -238,14 +238,14 @@ func TestIntegrationFolderService(t *testing.T) { ActualError error ExpectedError error }{ - {ActualError: models.ErrDashboardTitleEmpty, ExpectedError: models.ErrFolderTitleEmpty}, - {ActualError: models.ErrDashboardUpdateAccessDenied, ExpectedError: models.ErrFolderAccessDenied}, - {ActualError: models.ErrDashboardWithSameNameInFolderExists, ExpectedError: models.ErrFolderSameNameExists}, - {ActualError: models.ErrDashboardWithSameUIDExists, ExpectedError: models.ErrFolderWithSameUIDExists}, - {ActualError: models.ErrDashboardVersionMismatch, ExpectedError: models.ErrFolderVersionMismatch}, - {ActualError: models.ErrDashboardNotFound, ExpectedError: models.ErrFolderNotFound}, - {ActualError: models.ErrDashboardFailedGenerateUniqueUid, ExpectedError: models.ErrFolderFailedGenerateUniqueUid}, - {ActualError: models.ErrDashboardInvalidUid, ExpectedError: models.ErrDashboardInvalidUid}, + {ActualError: dashboards.ErrDashboardTitleEmpty, ExpectedError: dashboards.ErrFolderTitleEmpty}, + {ActualError: dashboards.ErrDashboardUpdateAccessDenied, ExpectedError: dashboards.ErrFolderAccessDenied}, + {ActualError: dashboards.ErrDashboardWithSameNameInFolderExists, ExpectedError: dashboards.ErrFolderSameNameExists}, + {ActualError: dashboards.ErrDashboardWithSameUIDExists, ExpectedError: dashboards.ErrFolderWithSameUIDExists}, + {ActualError: dashboards.ErrDashboardVersionMismatch, ExpectedError: dashboards.ErrFolderVersionMismatch}, + {ActualError: dashboards.ErrDashboardNotFound, ExpectedError: dashboards.ErrFolderNotFound}, + {ActualError: dashboards.ErrDashboardFailedGenerateUniqueUid, ExpectedError: dashboards.ErrFolderFailedGenerateUniqueUid}, + {ActualError: dashboards.ErrDashboardInvalidUid, ExpectedError: dashboards.ErrDashboardInvalidUid}, } for _, tc := range testCases { diff --git a/pkg/services/dashboardsnapshots/database/database.go b/pkg/services/dashboardsnapshots/database/database.go index 8732802d737..fc0b956ff9e 100644 --- a/pkg/services/dashboardsnapshots/database/database.go +++ b/pkg/services/dashboardsnapshots/database/database.go @@ -91,7 +91,7 @@ func (d *DashboardSnapshotStore) GetDashboardSnapshot(ctx context.Context, query if err != nil { return err } else if !has { - return models.ErrDashboardSnapshotNotFound + return dashboardsnapshots.ErrDashboardSnapshotNotFound } query.Result = &snapshot diff --git a/pkg/services/dashboardsnapshots/errors.go b/pkg/services/dashboardsnapshots/errors.go new file mode 100644 index 00000000000..268532c833b --- /dev/null +++ b/pkg/services/dashboardsnapshots/errors.go @@ -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, +} diff --git a/pkg/services/dashboardversion/dashverimpl/store_test.go b/pkg/services/dashboardversion/dashverimpl/store_test.go index de11a60ca8a..d696ef4e2a0 100644 --- a/pkg/services/dashboardversion/dashverimpl/store_test.go +++ b/pkg/services/dashboardversion/dashverimpl/store_test.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/util" @@ -134,7 +135,7 @@ func getDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, dashboard *models.D if err != nil { return err } else if !has { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } 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 { return err } else if affectedRows == 0 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } return nil @@ -250,7 +251,7 @@ func updateTestDashboard(t *testing.T, sqlStore *sqlstore.SQLStore, dashboard *m if affectedRows, err := sess.Insert(dashVersion); err != nil { return err } else if affectedRows == 0 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } return nil diff --git a/pkg/services/libraryelements/api.go b/pkg/services/libraryelements/api.go index d78cc817fb3..8781d0cd0b6 100644 --- a/pkg/services/libraryelements/api.go +++ b/pkg/services/libraryelements/api.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/web" ) @@ -173,11 +174,11 @@ func toLibraryElementError(err error, message string) response.Response { if errors.Is(err, errLibraryElementVersionMismatch) { return response.Error(412, errLibraryElementVersionMismatch.Error(), err) } - if errors.Is(err, models.ErrFolderNotFound) { - return response.Error(404, models.ErrFolderNotFound.Error(), err) + if errors.Is(err, dashboards.ErrFolderNotFound) { + return response.Error(404, dashboards.ErrFolderNotFound.Error(), err) } - if errors.Is(err, models.ErrFolderAccessDenied) { - return response.Error(403, models.ErrFolderAccessDenied.Error(), err) + if errors.Is(err, dashboards.ErrFolderAccessDenied) { + return response.Error(403, dashboards.ErrFolderAccessDenied.Error(), err) } if errors.Is(err, errLibraryElementHasConnections) { return response.Error(403, errLibraryElementHasConnections.Error(), err) diff --git a/pkg/services/libraryelements/database.go b/pkg/services/libraryelements/database.go index e5b0cb5c1c2..674799c0e4a 100644 --- a/pkg/services/libraryelements/database.go +++ b/pkg/services/libraryelements/database.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "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/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" @@ -707,7 +708,7 @@ func (l *LibraryElementService) deleteLibraryElementsInFolderUID(c context.Conte } if len(folderUIDs) == 0 { - return models.ErrFolderNotFound + return dashboards.ErrFolderNotFound } if len(folderUIDs) != 1 { diff --git a/pkg/services/libraryelements/guard.go b/pkg/services/libraryelements/guard.go index 3f7d776f6f7..7795a5f58cf 100644 --- a/pkg/services/libraryelements/guard.go +++ b/pkg/services/libraryelements/guard.go @@ -4,6 +4,7 @@ import ( "context" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" "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) { - return models.ErrFolderAccessDenied + return dashboards.ErrFolderAccessDenied } folder, err := l.folderService.GetFolderByID(ctx, user, folderID, user.OrgId) if err != nil { @@ -43,7 +44,7 @@ func (l *LibraryElementService) requireEditPermissionsOnFolder(ctx context.Conte return err } if !canEdit { - return models.ErrFolderAccessDenied + return dashboards.ErrFolderAccessDenied } return nil @@ -66,7 +67,7 @@ func (l *LibraryElementService) requireViewPermissionsOnFolder(ctx context.Conte return err } if !canView { - return models.ErrFolderAccessDenied + return dashboards.ErrFolderAccessDenied } return nil @@ -80,7 +81,7 @@ func (l *LibraryElementService) requireEditPermissionsOnDashboard(ctx context.Co return err } if !canEdit { - return models.ErrDashboardUpdateAccessDenied + return dashboards.ErrDashboardUpdateAccessDenied } return nil diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index d0463b59da7..00753e31fed 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -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", func(t *testing.T, sc scenarioContext) { 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", diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 6dd093c66d4..5e15578c861 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -9,6 +9,7 @@ import ( "time" "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/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/store" @@ -495,7 +496,7 @@ func toNamespaceErrorResponse(err error) response.Response { if errors.Is(err, ngmodels.ErrCannotEditNamespace) { 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 apierrors.ToFolderErrorResponse(err) diff --git a/pkg/services/ngalert/image/service.go b/pkg/services/ngalert/image/service.go index 68111528372..e139db3f1f3 100644 --- a/pkg/services/ngalert/image/service.go +++ b/pkg/services/ngalert/image/service.go @@ -9,7 +9,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/grafana/grafana/pkg/components/imguploader" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/dashboards" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "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 { // TODO: Check for screenshot upload failures. These images should still be // 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, err diff --git a/pkg/services/provisioning/dashboards/file_reader.go b/pkg/services/provisioning/dashboards/file_reader.go index 827a941a87a..35e16e870ea 100644 --- a/pkg/services/provisioning/dashboards/file_reader.go +++ b/pkg/services/provisioning/dashboards/file_reader.go @@ -302,12 +302,12 @@ func (fr *FileReader) getOrCreateFolderID(ctx context.Context, cfg *config, serv cmd := &models.GetDashboardQuery{Slug: models.SlugifyTitle(folderName), OrgId: cfg.OrgID} 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 } // dashboard folder not found. create one. - if errors.Is(err, models.ErrDashboardNotFound) { + if errors.Is(err, dashboards.ErrDashboardNotFound) { dash := &dashboards.SaveDashboardDTO{} dash.Dashboard = models.NewDashboardFolder(folderName) dash.Dashboard.IsFolder = true @@ -315,7 +315,7 @@ func (fr *FileReader) getOrCreateFolderID(ctx context.Context, cfg *config, serv dash.OrgId = cfg.OrgID // set dashboard folderUid if given if cfg.FolderUID == accesscontrol.GeneralFolderUID { - return 0, models.ErrFolderInvalidUID + return 0, dashboards.ErrFolderInvalidUID } dash.Dashboard.SetUid(cfg.FolderUID) dbDash, err := service.SaveFolderForProvisionedDashboards(ctx, dash) diff --git a/pkg/services/provisioning/dashboards/file_reader_test.go b/pkg/services/provisioning/dashboards/file_reader_test.go index 627ed6041b8..30eb32e0dca 100644 --- a/pkg/services/provisioning/dashboards/file_reader_test.go +++ b/pkg/services/provisioning/dashboards/file_reader_test.go @@ -8,13 +8,14 @@ import ( "testing" "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/models" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/util" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" ) const ( @@ -405,7 +406,7 @@ func TestDashboardFileReader(t *testing.T) { require.NoError(t, err) _, 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) { @@ -513,5 +514,5 @@ func (ffi FakeFileInfo) Sys() interface{} { type fakeDashboardStore struct{} func (fds *fakeDashboardStore) GetDashboard(_ context.Context, _ *models.GetDashboardQuery) error { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } diff --git a/pkg/services/provisioning/dashboards/types.go b/pkg/services/provisioning/dashboards/types.go index 51133a2bf58..b565085dceb 100644 --- a/pkg/services/provisioning/dashboards/types.go +++ b/pkg/services/provisioning/dashboards/types.go @@ -67,7 +67,7 @@ func createDashboardJSON(data *simplejson.Json, lastModified time.Time, cfg *con dash.Dashboard.FolderId = folderID if dash.Dashboard.Title == "" { - return nil, models.ErrDashboardTitleEmpty + return nil, dashboards.ErrDashboardTitleEmpty } return dash, nil diff --git a/pkg/services/screenshot/screenshot_test.go b/pkg/services/screenshot/screenshot_test.go index 9cc912a6d42..5988fc7c056 100644 --- a/pkg/services/screenshot/screenshot_test.go +++ b/pkg/services/screenshot/screenshot_test.go @@ -79,7 +79,7 @@ func TestBrowserScreenshotService(t *testing.T) { s := NewBrowserScreenshotService(&d, r) // 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() opts := ScreenshotOptions{} screenshot, err := s.Take(ctx, opts) diff --git a/pkg/services/sqlstore/dashboard_thumbs.go b/pkg/services/sqlstore/dashboard_thumbs.go index 907d7928991..f4321c4406f 100644 --- a/pkg/services/sqlstore/dashboard_thumbs.go +++ b/pkg/services/sqlstore/dashboard_thumbs.go @@ -6,6 +6,7 @@ import ( "time" "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) { @@ -25,7 +26,7 @@ func (ss *SQLStore) SaveThumbnail(ctx context.Context, cmd *models.SaveDashboard err := ss.WithTransactionalDbSession(ctx, func(sess *DBSession) error { existing, err := findThumbnailByMeta(sess, cmd.DashboardThumbnailMeta) - if err != nil && !errors.Is(err, models.ErrDashboardThumbnailNotFound) { + if err != nil && !errors.Is(err, dashboards.ErrDashboardThumbnailNotFound) { return err } @@ -150,7 +151,7 @@ func findThumbnailByMeta(sess *DBSession, meta models.DashboardThumbnailMeta) (* exists, err := sess.Get(result) if !exists { - return nil, models.ErrDashboardThumbnailNotFound + return nil, dashboards.ErrDashboardThumbnailNotFound } if err != nil { @@ -174,7 +175,7 @@ func findDashboardIdByThumbMeta(sess *DBSession, meta models.DashboardThumbnailM return nil, err } if !exists { - return nil, models.ErrDashboardNotFound + return nil, dashboards.ErrDashboardNotFound } return result, err diff --git a/pkg/services/sqlstore/dashboard_thumbs_test.go b/pkg/services/sqlstore/dashboard_thumbs_test.go index 61cfc7e6bf0..30900ec2017 100644 --- a/pkg/services/sqlstore/dashboard_thumbs_test.go +++ b/pkg/services/sqlstore/dashboard_thumbs_test.go @@ -5,11 +5,13 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/util" - "github.com/stretchr/testify/require" ) 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 { return err } else if affectedRows == 0 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } return nil diff --git a/pkg/services/sqlstore/migrations/ualert/permissions.go b/pkg/services/sqlstore/migrations/ualert/permissions.go index 0e2bd3b7e1d..369f6bb15b3 100644 --- a/pkg/services/sqlstore/migrations/ualert/permissions.go +++ b/pkg/services/sqlstore/migrations/ualert/permissions.go @@ -7,6 +7,7 @@ import ( "xorm.io/xorm" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/services/dashboards" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" "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() diff --git a/pkg/services/sqlstore/org_test.go b/pkg/services/sqlstore/org_test.go index 93986114e7b..69057ea193b 100644 --- a/pkg/services/sqlstore/org_test.go +++ b/pkg/services/sqlstore/org_test.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/dashboards" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/services/user" "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 { return err } else if affectedRows == 0 { - return models.ErrDashboardNotFound + return dashboards.ErrDashboardNotFound } return nil diff --git a/pkg/services/thumbs/service.go b/pkg/services/thumbs/service.go index b6b75e8f983..c2e647b55b9 100644 --- a/pkg/services/thumbs/service.go +++ b/pkg/services/thumbs/service.go @@ -222,7 +222,7 @@ func (hs *thumbService) GetImage(c *models.ReqContext) { Kind: models.ThumbnailKindDefault, }) - if errors.Is(err, models.ErrDashboardThumbnailNotFound) { + if errors.Is(err, dashboards.ErrDashboardThumbnailNotFound) { c.Resp.WriteHeader(404) return } diff --git a/pkg/tests/api/dashboards/api_dashboards_test.go b/pkg/tests/api/dashboards/api_dashboards_test.go index 711d42b6abd..b05709cf230 100644 --- a/pkg/tests/api/dashboards/api_dashboards_test.go +++ b/pkg/tests/api/dashboards/api_dashboards_test.go @@ -12,15 +12,17 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" "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/sqlstore" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/tests/testinfra" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestDashboardQuota(t *testing.T) { @@ -173,19 +175,19 @@ providers: desc: "when updating provisioned dashboard using ID it should fail", dashboardData: fmt.Sprintf(`{"title":"just testing", "id": %d, "version": 1}`, dashboardID), expStatus: http.StatusBadRequest, - expErrReason: models.ErrDashboardCannotSaveProvisionedDashboard.Reason, + expErrReason: dashboards.ErrDashboardCannotSaveProvisionedDashboard.Reason, }, { desc: "when updating provisioned dashboard using UID it should fail", dashboardData: fmt.Sprintf(`{"title":"just testing", "uid": %q, "version": 1}`, dashboardUID), expStatus: http.StatusBadRequest, - expErrReason: models.ErrDashboardCannotSaveProvisionedDashboard.Reason, + expErrReason: dashboards.ErrDashboardCannotSaveProvisionedDashboard.Reason, }, { desc: "when updating dashboard using unknown ID, it should fail", dashboardData: `{"title":"just testing", "id": 42, "version": 1}`, expStatus: http.StatusNotFound, - expErrReason: models.ErrDashboardNotFound.Reason, + expErrReason: dashboards.ErrDashboardNotFound.Reason, }, { desc: "when updating dashboard using unknown UID, it should succeed", @@ -247,7 +249,7 @@ providers: dashboardErr := &errorResponseBody{} err = json.Unmarshal(b, dashboardErr) require.NoError(t, err) - assert.Equal(t, models.ErrDashboardCannotDeleteProvisionedDashboard.Reason, dashboardErr.Message) + assert.Equal(t, dashboards.ErrDashboardCannotDeleteProvisionedDashboard.Reason, dashboardErr.Message) }) }) }