diff --git a/pkg/api/folder.go b/pkg/api/folder.go index bfc6cad7e90..fcaf655848c 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -169,16 +169,16 @@ func (hs *HTTPServer) CreateFolder(c *models.ReqContext) response.Response { func (hs *HTTPServer) MoveFolder(c *models.ReqContext) response.Response { if hs.Features.IsEnabled(featuremgmt.FlagNestedFolders) { - cmd := models.MoveFolderCommand{} + cmd := folder.MoveFolderCommand{} if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } var theFolder *folder.Folder var err error - if cmd.ParentUID != nil { + if cmd.NewParentUID != "" { moveCommand := folder.MoveFolderCommand{ UID: web.Params(c.Req)[":uid"], - NewParentUID: *cmd.ParentUID, + NewParentUID: cmd.NewParentUID, OrgID: c.OrgID, } theFolder, err = hs.folderService.Move(c.Req.Context(), &moveCommand) @@ -280,7 +280,7 @@ func (hs *HTTPServer) newToFolderDto(c *models.ReqContext, g guardian.DashboardG Id: folder.ID, Uid: folder.UID, Title: folder.Title, - Url: folder.Url, + Url: folder.URL, HasACL: folder.HasACL, CanSave: canSave, CanEdit: canEdit, diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index 620e8c53659..11a03ddefe5 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -34,8 +34,8 @@ func TestFoldersAPIEndpoint(t *testing.T) { folderService := &foldertest.FakeService{} t.Run("Given a correct request for creating a folder", func(t *testing.T) { - cmd := models.CreateFolderCommand{ - Uid: "uid", + cmd := folder.CreateFolderCommand{ + UID: "uid", Title: "Folder", } @@ -73,8 +73,8 @@ func TestFoldersAPIEndpoint(t *testing.T) { {Error: dashboards.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500}, } - cmd := models.CreateFolderCommand{ - Uid: "uid", + cmd := folder.CreateFolderCommand{ + UID: "uid", Title: "Folder", } @@ -235,7 +235,7 @@ func callCreateFolder(sc *scenarioContext) { } func createFolderScenario(t *testing.T, desc string, url string, routePattern string, folderService folder.Service, - cmd models.CreateFolderCommand, fn scenarioFunc) { + cmd folder.CreateFolderCommand, fn scenarioFunc) { setUpRBACGuardian(t) t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { aclMockResp := []*dashboards.DashboardACLInfoDTO{} diff --git a/pkg/api/index.go b/pkg/api/index.go index dbf621190ef..7037121e86b 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -10,6 +10,7 @@ import ( ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/folder" pref "github.com/grafana/grafana/pkg/services/preference" "github.com/grafana/grafana/pkg/setting" ) @@ -21,11 +22,12 @@ const ( ) func (hs *HTTPServer) editorInAnyFolder(c *models.ReqContext) bool { - hasEditPermissionInFoldersQuery := models.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser} - if err := hs.DashboardService.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery); err != nil { + hasEditPermissionInFoldersQuery := folder.HasEditPermissionInFoldersQuery{SignedInUser: c.SignedInUser} + hasEditPermissionInFoldersQueryResult, err := hs.DashboardService.HasEditPermissionInFolders(c.Req.Context(), &hasEditPermissionInFoldersQuery) + if err != nil { return false } - return hasEditPermissionInFoldersQuery.Result + return hasEditPermissionInFoldersQueryResult } func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewData, error) { diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index 1ae4b33b80b..abb74face6c 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -13,6 +13,7 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/setting" @@ -214,12 +215,13 @@ func OrgAdminDashOrFolderAdminOrTeamAdmin(ss db.DB, ds dashboards.DashboardServi return } - hasAdminPermissionInDashOrFoldersQuery := models.HasAdminPermissionInDashboardsOrFoldersQuery{SignedInUser: c.SignedInUser} - if err := ds.HasAdminPermissionInDashboardsOrFolders(c.Req.Context(), &hasAdminPermissionInDashOrFoldersQuery); err != nil { + hasAdminPermissionInDashOrFoldersQuery := folder.HasAdminPermissionInDashboardsOrFoldersQuery{SignedInUser: c.SignedInUser} + hasAdminPermissionInDashOrFoldersQueryResult, err := ds.HasAdminPermissionInDashboardsOrFolders(c.Req.Context(), &hasAdminPermissionInDashOrFoldersQuery) + if err != nil { c.JsonApiErr(500, "Failed to check if user is a folder admin", err) } - if hasAdminPermissionInDashOrFoldersQuery.Result { + if hasAdminPermissionInDashOrFoldersQueryResult { return } diff --git a/pkg/models/folders.go b/pkg/models/folders.go deleted file mode 100644 index f0951ad8b94..00000000000 --- a/pkg/models/folders.go +++ /dev/null @@ -1,60 +0,0 @@ -package models - -import ( - "time" - - "github.com/grafana/grafana/pkg/services/user" -) - -type Folder struct { - Id int64 - Uid string - Title string - Url string - Version int - - Created time.Time - Updated time.Time - - UpdatedBy int64 - CreatedBy int64 - HasACL bool -} - -// NewFolder creates a new Folder -func NewFolder(title string) *Folder { - folder := &Folder{} - folder.Title = title - folder.Created = time.Now() - folder.Updated = time.Now() - return folder -} - -// -// COMMANDS -// - -type CreateFolderCommand struct { - Uid string `json:"uid"` - Title string `json:"title"` - - Result *Folder `json:"-"` -} - -type MoveFolderCommand struct { - ParentUID *string `json:"parentUid"` -} - -// -// QUERIES -// - -type HasEditPermissionInFoldersQuery struct { - SignedInUser *user.SignedInUser - Result bool -} - -type HasAdminPermissionInDashboardsOrFoldersQuery struct { - SignedInUser *user.SignedInUser - Result bool -} diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index f6fa8e2faf2..0caf638a379 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -21,8 +21,8 @@ type DashboardService interface { GetDashboards(ctx context.Context, query *GetDashboardsQuery) error GetDashboardTags(ctx context.Context, query *GetDashboardTagsQuery) error GetDashboardUIDByID(ctx context.Context, query *GetDashboardRefByIDQuery) error - HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error - HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error + HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *folder.HasAdminPermissionInDashboardsOrFoldersQuery) (bool, error) + HasEditPermissionInFolders(ctx context.Context, query *folder.HasEditPermissionInFoldersQuery) (bool, error) ImportDashboard(ctx context.Context, dto *SaveDashboardDTO) (*Dashboard, error) MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error SaveDashboard(ctx context.Context, dto *SaveDashboardDTO, allowUiUpdate bool) (*Dashboard, error) @@ -68,9 +68,9 @@ type Store interface { GetProvisionedDashboardData(ctx context.Context, name string) ([]*DashboardProvisioning, error) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*DashboardProvisioning, error) GetProvisionedDataByDashboardUID(ctx context.Context, orgID int64, dashboardUID string) (*DashboardProvisioning, error) - HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error - HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error - // SaveAlerts saves dashboard alertmodels. + HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *folder.HasAdminPermissionInDashboardsOrFoldersQuery) (bool, error) + HasEditPermissionInFolders(ctx context.Context, query *folder.HasEditPermissionInFoldersQuery) (bool, error) + // SaveAlerts saves dashboard alerts. SaveAlerts(ctx context.Context, dashID int64, alerts []*alertmodels.Alert) error SaveDashboard(ctx context.Context, cmd SaveDashboardCommand) (*Dashboard, error) SaveProvisionedDashboard(ctx context.Context, cmd SaveDashboardCommand, provisioning *DashboardProvisioning) (*Dashboard, error) diff --git a/pkg/services/dashboards/dashboard_service_mock.go b/pkg/services/dashboards/dashboard_service_mock.go index 65ead437663..7165629552a 100644 --- a/pkg/services/dashboards/dashboard_service_mock.go +++ b/pkg/services/dashboards/dashboard_service_mock.go @@ -5,8 +5,10 @@ package dashboards import ( context "context" - models "github.com/grafana/grafana/pkg/models" + folder "github.com/grafana/grafana/pkg/services/folder" mock "github.com/stretchr/testify/mock" + + models "github.com/grafana/grafana/pkg/models" ) // FakeDashboardService is an autogenerated mock type for the DashboardService type @@ -180,31 +182,45 @@ func (_m *FakeDashboardService) GetDashboards(ctx context.Context, query *GetDas } // HasAdminPermissionInDashboardsOrFolders provides a mock function with given fields: ctx, query -func (_m *FakeDashboardService) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error { +func (_m *FakeDashboardService) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *folder.HasAdminPermissionInDashboardsOrFoldersQuery) (bool, error) { ret := _m.Called(ctx, query) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *models.HasAdminPermissionInDashboardsOrFoldersQuery) error); ok { + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, *folder.HasAdminPermissionInDashboardsOrFoldersQuery) bool); ok { r0 = rf(ctx, query) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(bool) } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *folder.HasAdminPermissionInDashboardsOrFoldersQuery) error); ok { + r1 = rf(ctx, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // HasEditPermissionInFolders provides a mock function with given fields: ctx, query -func (_m *FakeDashboardService) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error { +func (_m *FakeDashboardService) HasEditPermissionInFolders(ctx context.Context, query *folder.HasEditPermissionInFoldersQuery) (bool, error) { ret := _m.Called(ctx, query) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *models.HasEditPermissionInFoldersQuery) error); ok { + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, *folder.HasEditPermissionInFoldersQuery) bool); ok { r0 = rf(ctx, query) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(bool) } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *folder.HasEditPermissionInFoldersQuery) error); ok { + r1 = rf(ctx, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // ImportDashboard provides a mock function with given fields: ctx, dto diff --git a/pkg/services/dashboards/database/acl.go b/pkg/services/dashboards/database/acl.go index 93256cead83..6147eb22334 100644 --- a/pkg/services/dashboards/database/acl.go +++ b/pkg/services/dashboards/database/acl.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/org" ) @@ -97,13 +98,13 @@ func (d *DashboardStore) GetDashboardACLInfoList(ctx context.Context, query *das } // HasEditPermissionInFolders validates that an user have access to a certain folder -func (d *DashboardStore) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error { - return d.store.WithDbSession(ctx, func(dbSession *db.Session) error { - if query.SignedInUser.HasRole(org.RoleEditor) { - query.Result = true - return nil - } - +func (d *DashboardStore) HasEditPermissionInFolders(ctx context.Context, query *folder.HasEditPermissionInFoldersQuery) (bool, error) { + var queryResult bool + if query.SignedInUser.HasRole(org.RoleEditor) { + queryResult = true + return queryResult, nil + } + err := d.store.WithDbSession(ctx, func(dbSession *db.Session) error { builder := db.NewSqlBuilder(d.cfg, d.store.GetDialect()) builder.Write("SELECT COUNT(dashboard.id) AS count FROM dashboard WHERE dashboard.org_id = ? AND dashboard.is_folder = ?", query.SignedInUser.OrgID, d.store.GetDialect().BooleanStr(true)) @@ -119,16 +120,21 @@ func (d *DashboardStore) HasEditPermissionInFolders(ctx context.Context, query * return err } - query.Result = len(resp) > 0 && resp[0].Count > 0 + queryResult = len(resp) > 0 && resp[0].Count > 0 return nil }) + if err != nil { + return queryResult, err + } + return queryResult, nil } -func (d *DashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error { - return d.store.WithDbSession(ctx, func(dbSession *db.Session) error { +func (d *DashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *folder.HasAdminPermissionInDashboardsOrFoldersQuery) (bool, error) { + var queryResult bool + err := d.store.WithDbSession(ctx, func(dbSession *db.Session) error { if query.SignedInUser.HasRole(org.RoleAdmin) { - query.Result = true + queryResult = true return nil } @@ -145,10 +151,14 @@ func (d *DashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Con return err } - query.Result = len(resp) > 0 && resp[0].Count > 0 + queryResult = len(resp) > 0 && resp[0].Count > 0 return nil }) + if err != nil { + return queryResult, err + } + return queryResult, nil } func (d *DashboardStore) DeleteACLByUser(ctx context.Context, userID int64) error { diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index e2ea275c414..59a42e8968c 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -11,6 +11,7 @@ import ( "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/folder" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/quota/quotatest" "github.com/grafana/grafana/pkg/services/sqlstore" @@ -27,7 +28,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { } t.Run("Testing DB", func(t *testing.T) { var sqlStore *sqlstore.SQLStore - var folder, dashInRoot, childDash *dashboards.Dashboard + var flder, dashInRoot, childDash *dashboards.Dashboard var currentUser user.User var dashboardStore *DashboardStore @@ -38,10 +39,10 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { var err error dashboardStore, err = ProvideDashboardStore(sqlStore, &setting.Cfg{}, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) require.NoError(t, err) - folder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp") + flder = insertTestDashboard(t, dashboardStore, "1 test dash folder", 1, 0, true, "prod", "webapp") dashInRoot = insertTestDashboard(t, dashboardStore, "test dash 67", 1, 0, false, "prod", "webapp") - childDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, folder.ID, false, "prod", "webapp") - insertTestDashboard(t, dashboardStore, "test dash 45", 1, folder.ID, false, "prod") + childDash = insertTestDashboard(t, dashboardStore, "test dash 23", 1, flder.ID, false, "prod", "webapp") + insertTestDashboard(t, dashboardStore, "test dash 45", 1, flder.ID, false, "prod") currentUser = createUser(t, sqlStore, "viewer", "Viewer", false) } @@ -53,20 +54,20 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { query := &models.FindPersistedDashboardsQuery{ SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, OrgId: 1, - DashboardIds: []int64{folder.ID, dashInRoot.ID}, + DashboardIds: []int64{flder.ID, dashInRoot.ID}, } err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) - require.Equal(t, query.Result[0].ID, folder.ID) + require.Equal(t, query.Result[0].ID, flder.ID) require.Equal(t, query.Result[1].ID, dashInRoot.ID) }) }) t.Run("and acl is set for dashboard folder", func(t *testing.T) { var otherUser int64 = 999 - err := updateDashboardACL(t, dashboardStore, folder.ID, dashboards.DashboardACL{ - DashboardID: folder.ID, + err := updateDashboardACL(t, dashboardStore, flder.ID, dashboards.DashboardACL{ + DashboardID: flder.ID, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT, @@ -76,7 +77,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("should not return folder", func(t *testing.T) { query := &models.FindPersistedDashboardsQuery{ SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, - OrgId: 1, DashboardIds: []int64{folder.ID, dashInRoot.ID}, + OrgId: 1, DashboardIds: []int64{flder.ID, dashInRoot.ID}, } err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) @@ -86,8 +87,8 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) t.Run("when the user is given permission", func(t *testing.T) { - err := updateDashboardACL(t, dashboardStore, folder.ID, dashboards.DashboardACL{ - DashboardID: folder.ID, OrgID: 1, UserID: currentUser.ID, Permission: models.PERMISSION_EDIT, + err := updateDashboardACL(t, dashboardStore, flder.ID, dashboards.DashboardACL{ + DashboardID: flder.ID, OrgID: 1, UserID: currentUser.ID, Permission: models.PERMISSION_EDIT, }) require.NoError(t, err) @@ -95,12 +96,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { query := &models.FindPersistedDashboardsQuery{ SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, OrgId: 1, - DashboardIds: []int64{folder.ID, dashInRoot.ID}, + DashboardIds: []int64{flder.ID, dashInRoot.ID}, } err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) - require.Equal(t, query.Result[0].ID, folder.ID) + require.Equal(t, query.Result[0].ID, flder.ID) require.Equal(t, query.Result[1].ID, dashInRoot.ID) }) }) @@ -114,12 +115,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { OrgRole: org.RoleAdmin, }, OrgId: 1, - DashboardIds: []int64{folder.ID, dashInRoot.ID}, + DashboardIds: []int64{flder.ID, dashInRoot.ID}, } err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) - require.Equal(t, query.Result[0].ID, folder.ID) + require.Equal(t, query.Result[0].ID, flder.ID) require.Equal(t, query.Result[1].ID, dashInRoot.ID) }) }) @@ -127,16 +128,16 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("and acl is set for dashboard child and folder has all permissions removed", func(t *testing.T) { var otherUser int64 = 999 - err := updateDashboardACL(t, dashboardStore, folder.ID) + err := updateDashboardACL(t, dashboardStore, flder.ID) require.NoError(t, err) err = updateDashboardACL(t, dashboardStore, childDash.ID, dashboards.DashboardACL{ - DashboardID: folder.ID, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT, + DashboardID: flder.ID, OrgID: 1, UserID: otherUser, Permission: models.PERMISSION_EDIT, }) require.NoError(t, err) t.Run("should not return folder or child", func(t *testing.T) { query := &models.FindPersistedDashboardsQuery{ - SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, OrgId: 1, DashboardIds: []int64{folder.ID, childDash.ID, dashInRoot.ID}, + SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, OrgId: 1, DashboardIds: []int64{flder.ID, childDash.ID, dashInRoot.ID}, } err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) @@ -151,7 +152,7 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { require.NoError(t, err) t.Run("should be able to search for child dashboard but not folder", func(t *testing.T) { - query := &models.FindPersistedDashboardsQuery{SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, OrgId: 1, DashboardIds: []int64{folder.ID, childDash.ID, dashInRoot.ID}} + query := &models.FindPersistedDashboardsQuery{SignedInUser: &user.SignedInUser{UserID: currentUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, OrgId: 1, DashboardIds: []int64{flder.ID, childDash.ID, dashInRoot.ID}} err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 2) @@ -169,12 +170,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { OrgRole: org.RoleAdmin, }, OrgId: 1, - DashboardIds: []int64{folder.ID, dashInRoot.ID, childDash.ID}, + DashboardIds: []int64{flder.ID, dashInRoot.ID, childDash.ID}, } err := testSearchDashboards(dashboardStore, query) require.NoError(t, err) require.Equal(t, len(query.Result), 3) - require.Equal(t, query.Result[0].ID, folder.ID) + require.Equal(t, query.Result[0].ID, flder.ID) require.Equal(t, query.Result[1].ID, childDash.ID) require.Equal(t, query.Result[2].ID, dashInRoot.ID) }) @@ -328,21 +329,21 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) t.Run("should have edit permission in folders", func(t *testing.T) { - query := &models.HasEditPermissionInFoldersQuery{ + query := &folder.HasEditPermissionInFoldersQuery{ SignedInUser: &user.SignedInUser{UserID: adminUser.ID, OrgID: 1, OrgRole: org.RoleAdmin}, } - err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) + queryResult, err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) require.NoError(t, err) - require.True(t, query.Result) + require.True(t, queryResult) }) t.Run("should have admin permission in folders", func(t *testing.T) { - query := &models.HasAdminPermissionInDashboardsOrFoldersQuery{ + query := &folder.HasAdminPermissionInDashboardsOrFoldersQuery{ SignedInUser: &user.SignedInUser{UserID: adminUser.ID, OrgID: 1, OrgRole: org.RoleAdmin}, } - err := dashboardStore.HasAdminPermissionInDashboardsOrFolders(context.Background(), query) + queryResult, err := dashboardStore.HasAdminPermissionInDashboardsOrFolders(context.Background(), query) require.NoError(t, err) - require.True(t, query.Result) + require.True(t, queryResult) }) }) @@ -376,21 +377,21 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) t.Run("should have edit permission in folders", func(t *testing.T) { - query := &models.HasEditPermissionInFoldersQuery{ + query := &folder.HasEditPermissionInFoldersQuery{ SignedInUser: &user.SignedInUser{UserID: editorUser.ID, OrgID: 1, OrgRole: org.RoleEditor}, } - err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) + queryResult, err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) go require.NoError(t, err) - require.True(t, query.Result) + require.True(t, queryResult) }) t.Run("should not have admin permission in folders", func(t *testing.T) { - query := &models.HasAdminPermissionInDashboardsOrFoldersQuery{ + query := &folder.HasAdminPermissionInDashboardsOrFoldersQuery{ SignedInUser: &user.SignedInUser{UserID: adminUser.ID, OrgID: 1, OrgRole: org.RoleEditor}, } - err := dashboardStore.HasAdminPermissionInDashboardsOrFolders(context.Background(), query) + queryResult, err := dashboardStore.HasAdminPermissionInDashboardsOrFolders(context.Background(), query) require.NoError(t, err) - require.False(t, query.Result) + require.False(t, queryResult) }) }) @@ -424,21 +425,21 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { t.Run("should not have edit permission in folders", func(t *testing.T) { setup3() - query := &models.HasEditPermissionInFoldersQuery{ + query := &folder.HasEditPermissionInFoldersQuery{ SignedInUser: &user.SignedInUser{UserID: viewerUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, } - err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) + queryResult, err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) go require.NoError(t, err) - require.False(t, query.Result) + require.False(t, queryResult) }) t.Run("should not have admin permission in folders", func(t *testing.T) { - query := &models.HasAdminPermissionInDashboardsOrFoldersQuery{ + query := &folder.HasAdminPermissionInDashboardsOrFoldersQuery{ SignedInUser: &user.SignedInUser{UserID: adminUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, } - err := dashboardStore.HasAdminPermissionInDashboardsOrFolders(context.Background(), query) + queryResult, err := dashboardStore.HasAdminPermissionInDashboardsOrFolders(context.Background(), query) require.NoError(t, err) - require.False(t, query.Result) + require.False(t, queryResult) }) t.Run("and admin permission is given for user with org role viewer in one dashboard folder", func(t *testing.T) { @@ -448,12 +449,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { require.NoError(t, err) t.Run("should have edit permission in folders", func(t *testing.T) { - query := &models.HasEditPermissionInFoldersQuery{ + query := &folder.HasEditPermissionInFoldersQuery{ SignedInUser: &user.SignedInUser{UserID: viewerUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, } - err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) + queryResult, err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) go require.NoError(t, err) - require.True(t, query.Result) + require.True(t, queryResult) }) }) @@ -464,12 +465,12 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { require.NoError(t, err) t.Run("should have edit permission in folders", func(t *testing.T) { - query := &models.HasEditPermissionInFoldersQuery{ + query := &folder.HasEditPermissionInFoldersQuery{ SignedInUser: &user.SignedInUser{UserID: viewerUser.ID, OrgID: 1, OrgRole: org.RoleViewer}, } - err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) + queryResult, err := dashboardStore.HasEditPermissionInFolders(context.Background(), query) go require.NoError(t, err) - require.True(t, query.Result) + require.True(t, queryResult) }) }) }) diff --git a/pkg/services/dashboards/models.go b/pkg/services/dashboards/models.go index 0b74c017793..5d67a74a926 100644 --- a/pkg/services/dashboards/models.go +++ b/pkg/services/dashboards/models.go @@ -336,7 +336,7 @@ func FromDashboard(dash *Dashboard) *folder.Folder { UID: dash.UID, Title: dash.Title, HasACL: dash.HasACL, - Url: GetFolderURL(dash.UID, dash.Slug), + URL: GetFolderURL(dash.UID, dash.Slug), Version: dash.Version, Created: dash.Created, CreatedBy: dash.CreatedBy, diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index ca29dd390c8..5c5c8d9612c 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" @@ -602,11 +603,11 @@ func (dr *DashboardServiceImpl) GetDashboardACLInfoList(ctx context.Context, que return dr.dashboardStore.GetDashboardACLInfoList(ctx, query) } -func (dr *DashboardServiceImpl) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error { +func (dr *DashboardServiceImpl) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *folder.HasAdminPermissionInDashboardsOrFoldersQuery) (bool, error) { return dr.dashboardStore.HasAdminPermissionInDashboardsOrFolders(ctx, query) } -func (dr *DashboardServiceImpl) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error { +func (dr *DashboardServiceImpl) HasEditPermissionInFolders(ctx context.Context, query *folder.HasEditPermissionInFoldersQuery) (bool, error) { return dr.dashboardStore.HasEditPermissionInFolders(ctx, query) } diff --git a/pkg/services/dashboards/store_mock.go b/pkg/services/dashboards/store_mock.go index 361b376ece6..5abfe907689 100644 --- a/pkg/services/dashboards/store_mock.go +++ b/pkg/services/dashboards/store_mock.go @@ -6,6 +6,7 @@ import ( context "context" alertingmodels "github.com/grafana/grafana/pkg/services/alerting/models" + folder "github.com/grafana/grafana/pkg/services/folder" mock "github.com/stretchr/testify/mock" @@ -291,31 +292,45 @@ func (_m *FakeDashboardStore) GetProvisionedDataByDashboardUID(ctx context.Conte } // HasAdminPermissionInDashboardsOrFolders provides a mock function with given fields: ctx, query -func (_m *FakeDashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *models.HasAdminPermissionInDashboardsOrFoldersQuery) error { +func (_m *FakeDashboardStore) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *folder.HasAdminPermissionInDashboardsOrFoldersQuery) (bool, error) { ret := _m.Called(ctx, query) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *models.HasAdminPermissionInDashboardsOrFoldersQuery) error); ok { + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, *folder.HasAdminPermissionInDashboardsOrFoldersQuery) bool); ok { r0 = rf(ctx, query) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(bool) } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *folder.HasAdminPermissionInDashboardsOrFoldersQuery) error); ok { + r1 = rf(ctx, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // HasEditPermissionInFolders provides a mock function with given fields: ctx, query -func (_m *FakeDashboardStore) HasEditPermissionInFolders(ctx context.Context, query *models.HasEditPermissionInFoldersQuery) error { +func (_m *FakeDashboardStore) HasEditPermissionInFolders(ctx context.Context, query *folder.HasEditPermissionInFoldersQuery) (bool, error) { ret := _m.Called(ctx, query) - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *models.HasEditPermissionInFoldersQuery) error); ok { + var r0 bool + if rf, ok := ret.Get(0).(func(context.Context, *folder.HasEditPermissionInFoldersQuery) bool); ok { r0 = rf(ctx, query) } else { - r0 = ret.Error(0) + r0 = ret.Get(0).(bool) } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *folder.HasEditPermissionInFoldersQuery) error); ok { + r1 = rf(ctx, query) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // SaveAlerts provides a mock function with given fields: ctx, dashID, alerts diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 4c27d2c5f87..b8497f26d0c 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -14,7 +14,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" @@ -142,8 +141,8 @@ func TestIntegrationFolderService(t *testing.T) { }) t.Run("When deleting folder by uid should return access denied error", func(t *testing.T) { - newFolder := models.NewFolder("Folder") - newFolder.Uid = folderUID + newFolder := folder.NewFolder("Folder", "") + newFolder.UID = folderUID folderStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(newFolder, nil) folderStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(newFolder, nil) diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index e3ca1f36bbb..40797519ee9 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -180,7 +180,7 @@ func (ss *sqlStore) Get(ctx context.Context, q folder.GetFolderQuery) (*folder.F } return nil }) - foldr.Url = dashboards.GetFolderURL(foldr.UID, slugify.Slugify(foldr.Title)) + foldr.URL = dashboards.GetFolderURL(foldr.UID, slugify.Slugify(foldr.Title)) return foldr, err } diff --git a/pkg/services/folder/model.go b/pkg/services/folder/model.go index 75e920ecccc..62ae6a77b0b 100644 --- a/pkg/services/folder/model.go +++ b/pkg/services/folder/model.go @@ -36,7 +36,7 @@ type Folder struct { // TODO: validate if this field is required/relevant to folders. // currently there is no such column Version int - Url string + URL string UpdatedBy int64 CreatedBy int64 HasACL bool @@ -146,3 +146,11 @@ type GetChildrenQuery struct { SignedInUser *user.SignedInUser `json:"-"` } + +type HasEditPermissionInFoldersQuery struct { + SignedInUser *user.SignedInUser +} + +type HasAdminPermissionInDashboardsOrFoldersQuery struct { + SignedInUser *user.SignedInUser +} diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index a01af5e1fcf..dc27e80e8bd 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -77,7 +77,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { Title: "Testing ConnectLibraryPanelsForDashboard", Data: simplejson.NewFromAny(dashJSON), } - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) err := sc.service.ConnectLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB) require.NoError(t, err) @@ -175,7 +175,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { Title: "Testing ConnectLibraryPanelsForDashboard", Data: simplejson.NewFromAny(dashJSON), } - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) err = sc.service.ConnectLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB) require.NoError(t, err) @@ -221,7 +221,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { Title: "Testing ConnectLibraryPanelsForDashboard", Data: simplejson.NewFromAny(dashJSON), } - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) err := sc.service.ConnectLibraryPanelsForDashboard(sc.ctx, sc.user, dashInDB) require.EqualError(t, err, errLibraryPanelHeaderUIDMissing.Error()) @@ -230,7 +230,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { scenarioWithLibraryPanel(t, "When an admin tries to store a dashboard with unused/removed library panels, it should disconnect unused/removed library panels", func(t *testing.T, sc scenarioContext) { unused, err := sc.elementService.CreateElement(sc.ctx, sc.user, libraryelements.CreateLibraryElementCommand{ - FolderID: sc.folder.Id, + FolderID: sc.folder.ID, Name: "Unused Libray Panel", Model: []byte(` { @@ -277,7 +277,7 @@ func TestConnectLibraryPanelsForDashboard(t *testing.T) { Title: "Testing ConnectLibraryPanelsForDashboard", Data: simplejson.NewFromAny(dashJSON), } - dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.Id) + dashInDB := createDashboard(t, sc.sqlStore, sc.user, &dash, sc.folder.ID) err = sc.elementService.ConnectElementsToDashboard(sc.ctx, sc.user, []string{sc.initialResult.Result.UID}, dashInDB.ID) require.NoError(t, err) @@ -406,7 +406,7 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { _, err := sc.elementService.GetElement(sc.ctx, sc.user, existingUID) require.NoError(t, err) - err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.New(), panels, sc.folder.Id) + err = sc.service.ImportLibraryPanelsForDashboard(sc.ctx, sc.user, simplejson.New(), panels, sc.folder.ID) require.NoError(t, err) element, err := sc.elementService.GetElement(sc.ctx, sc.user, existingUID) @@ -414,7 +414,7 @@ func TestImportLibraryPanelsForDashboard(t *testing.T) { var expected = getExpected(t, element, existingUID, existingName, sc.initialResult.Result.Model) expected.FolderID = sc.initialResult.Result.FolderID expected.Description = sc.initialResult.Result.Description - expected.Meta.FolderUID = sc.folder.Uid + expected.Meta.FolderUID = sc.folder.UID expected.Meta.FolderName = sc.folder.Title var result = toLibraryElement(t, element) if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" { @@ -601,7 +601,7 @@ type scenarioContext struct { service Service elementService libraryelements.Service user *user.SignedInUser - folder *models.Folder + folder *folder.Folder initialResult libraryPanelResult sqlStore db.DB } @@ -778,7 +778,7 @@ func scenarioWithLibraryPanel(t *testing.T, desc string, fn func(t *testing.T, s testScenario(t, desc, func(t *testing.T, sc scenarioContext) { command := libraryelements.CreateLibraryElementCommand{ - FolderID: sc.folder.Id, + FolderID: sc.folder.ID, Name: "Text - Library Panel", Model: []byte(` { @@ -877,15 +877,15 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo sqlStore: sqlStore, } - folder := createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{}) - sc.folder = &models.Folder{ - Id: folder.ID, - Uid: folder.UID, - Title: folder.Title, - Url: dashboards.GetFolderURL(folder.UID, slugify.Slugify(folder.Title)), + foldr := createFolderWithACL(t, sc.sqlStore, "ScenarioFolder", sc.user, []folderACLItem{}) + sc.folder = &folder.Folder{ + ID: foldr.ID, + UID: foldr.UID, + Title: foldr.Title, + URL: dashboards.GetFolderURL(foldr.UID, slugify.Slugify(foldr.Title)), Version: 0, - Created: folder.Created, - Updated: folder.Updated, + Created: foldr.Created, + Updated: foldr.Updated, UpdatedBy: 0, CreatedBy: 0, HasACL: false, diff --git a/pkg/services/ngalert/store/deltas_test.go b/pkg/services/ngalert/store/deltas_test.go index d3e63758e88..e9bc7b947ba 100644 --- a/pkg/services/ngalert/store/deltas_test.go +++ b/pkg/services/ngalert/store/deltas_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - grafana_models "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/tests/fakes" "github.com/grafana/grafana/pkg/util" @@ -194,7 +194,7 @@ func TestCalculateChanges(t *testing.T) { groupKey := models.AlertRuleGroupKey{ OrgID: orgId, - NamespaceUID: namespace.Uid, + NamespaceUID: namespace.UID, RuleGroup: groupName, } @@ -442,12 +442,12 @@ func withUIDs(uids map[string]*models.AlertRule) func(rule *models.AlertRule) { } } -func randFolder() *grafana_models.Folder { - return &grafana_models.Folder{ - Id: rand.Int63(), - Uid: util.GenerateShortUID(), +func randFolder() *folder.Folder { + return &folder.Folder{ + ID: rand.Int63(), + UID: util.GenerateShortUID(), Title: "TEST-FOLDER-" + util.GenerateShortUID(), - Url: "", + URL: "", Version: 0, Created: time.Time{}, Updated: time.Time{},