diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 2a7a99657cd..9c2bedfbfc1 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -15,6 +15,7 @@ import ( "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" "github.com/grafana/grafana/pkg/services/dashboards" @@ -349,30 +350,21 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) { func TestNestedFolderService(t *testing.T) { t.Run("with feature flag unset", func(t *testing.T) { - store := NewFakeStore() - dashStore := dashboards.FakeDashboardStore{} - dashboardsvc := dashboards.FakeDashboardService{} - // nothing enabled yet - cfg := setting.NewCfg() - cfg.RBACEnabled = false - foldersvc := &Service{ - cfg: cfg, - log: log.New("test-folder-service"), - dashboardService: &dashboardsvc, - dashboardStore: &dashStore, - store: store, - features: featuremgmt.WithFeatures(), - } - t.Run("When create folder, no create in folder table done", func(t *testing.T) { // dashboard store & service commands that should be called. + dashboardsvc := dashboards.FakeDashboardService{} dashboardsvc.On("BuildSaveDashboardCommand", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&dashboards.SaveDashboardCommand{}, nil) + + dashStore := &dashboards.FakeDashboardStore{} dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil) dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - _, err := foldersvc.Create(context.Background(), &folder.CreateFolderCommand{ + folderStore := NewFakeStore() + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures(), nil, &dashboardsvc) + _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: "myFolder", UID: "myFolder", @@ -380,11 +372,14 @@ func TestNestedFolderService(t *testing.T) { }) require.NoError(t, err) // CreateFolder should not call the folder store create if the feature toggle is not enabled. - require.False(t, store.CreateCalled) + require.False(t, folderStore.CreateCalled) }) t.Run("When delete folder, no delete in folder table done", func(t *testing.T) { + dashboardsvc := dashboards.FakeDashboardService{} + var actualCmd *models.DeleteDashboardCommand + dashStore := &dashboards.FakeDashboardStore{} dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { actualCmd = args.Get(1).(*models.DeleteDashboardCommand) }).Return(nil).Once() @@ -393,44 +388,38 @@ func TestNestedFolderService(t *testing.T) { g := guardian.New guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true}) - err := foldersvc.Delete(context.Background(), &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID, SignedInUser: usr}) + folderStore := NewFakeStore() + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures(), nil, &dashboardsvc) + err := folderSvc.Delete(context.Background(), &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID, SignedInUser: usr}) require.NoError(t, err) require.NotNil(t, actualCmd) t.Cleanup(func() { guardian.New = g }) - require.False(t, store.DeleteCalled) + require.False(t, folderStore.DeleteCalled) }) }) t.Run("with nested folder feature flag on", func(t *testing.T) { - store := NewFakeStore() - dashStore := &dashboards.FakeDashboardStore{} - dashboardsvc := &dashboards.FakeDashboardService{} - // nothing enabled yet - cfg := setting.NewCfg() - cfg.RBACEnabled = false - foldersvc := &Service{ - cfg: cfg, - log: log.New("test-folder-service"), - dashboardService: dashboardsvc, - dashboardStore: dashStore, - store: store, - features: featuremgmt.WithFeatures("nestedFolders"), - accessControl: actest.FakeAccessControl{ - ExpectedEvaluate: true, - }, - } - t.Run("create, no error", func(t *testing.T) { + dashboardsvc := dashboards.FakeDashboardService{} // dashboard store & service commands that should be called. dashboardsvc.On("BuildSaveDashboardCommand", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&dashboards.SaveDashboardCommand{}, nil) + + dashStore := &dashboards.FakeDashboardStore{} dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil) dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - _, err := foldersvc.Create(context.Background(), &folder.CreateFolderCommand{ + + folderStore := NewFakeStore() + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: "myFolder", UID: "myFolder", @@ -438,26 +427,33 @@ func TestNestedFolderService(t *testing.T) { }) require.NoError(t, err) // CreateFolder should also call the folder store's create method. - require.True(t, store.CreateCalled) + require.True(t, folderStore.CreateCalled) }) t.Run("create without UID, no error", func(t *testing.T) { // dashboard store & service commands that should be called. - dashStore = &dashboards.FakeDashboardStore{} - foldersvc.dashboardStore = dashStore + dashboardsvc := dashboards.FakeDashboardService{} dashboardsvc.On("BuildSaveDashboardCommand", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&dashboards.SaveDashboardCommand{}, nil) + + dashStore := &dashboards.FakeDashboardStore{} dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{UID: "newUID"}, nil) dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) - f, err := foldersvc.Create(context.Background(), &folder.CreateFolderCommand{ + + folderStore := NewFakeStore() + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + f, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: "myFolder", SignedInUser: usr, }) require.NoError(t, err) // CreateFolder should also call the folder store's create method. - require.True(t, store.CreateCalled) + require.True(t, folderStore.CreateCalled) require.Equal(t, "newUID", f.UID) }) @@ -468,8 +464,12 @@ func TestNestedFolderService(t *testing.T) { dashboardFolder.UID = "myFolder" f := dashboards.FromDashboard(dashboardFolder) - dashStore = &dashboards.FakeDashboardStore{} - foldersvc.dashboardStore = dashStore + dashboardsvc := dashboards.FakeDashboardService{} + dashboardsvc.On("BuildSaveDashboardCommand", + mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), + mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&dashboards.SaveDashboardCommand{}, nil) + + dashStore := &dashboards.FakeDashboardStore{} dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*dashboards.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(dashboardFolder, nil) dashStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.ID).Return(f, nil) @@ -478,7 +478,8 @@ func TestNestedFolderService(t *testing.T) { actualCmd = args.Get(1).(*models.DeleteDashboardCommand) }).Return(nil).Once() - store.ExpectedParentFolders = []*folder.Folder{ + folderStore := NewFakeStore() + folderStore.ExpectedParentFolders = []*folder.Folder{ {UID: "newFolder", ParentUID: "newFolder"}, {UID: "newFolder2", ParentUID: "newFolder2"}, {UID: "newFolder3", ParentUID: "newFolder3"}, @@ -492,10 +493,14 @@ func TestNestedFolderService(t *testing.T) { UID: "myFolder", SignedInUser: usr, } - _, err := foldersvc.Create(context.Background(), &cmd) + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + _, err := folderSvc.Create(context.Background(), &cmd) require.Error(t, err, folder.ErrCircularReference) - // CreateFolder should also call the folder store's create method. - require.True(t, store.CreateCalled) + // CreateFolder should not call the folder store's create method. + require.False(t, folderStore.CreateCalled) require.NotNil(t, actualCmd) }) @@ -504,12 +509,13 @@ func TestNestedFolderService(t *testing.T) { g := guardian.New guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{}) - dashStore = &dashboards.FakeDashboardStore{} - foldersvc.dashboardStore = dashStore // dashboard store & service commands that should be called. + dashboardsvc := dashboards.FakeDashboardService{} dashboardsvc.On("BuildSaveDashboardCommand", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&dashboards.SaveDashboardCommand{}, nil) + + dashStore := &dashboards.FakeDashboardStore{} dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("dashboards.SaveDashboardCommand")).Return(&dashboards.Dashboard{}, nil) dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) @@ -519,10 +525,14 @@ func TestNestedFolderService(t *testing.T) { }).Return(nil).Once() // return an error from the folder store - store.ExpectedError = errors.New("FAILED") + folderStore := NewFakeStore() + folderStore.ExpectedError = errors.New("FAILED") // the service return success as long as the legacy create succeeds - _, err := foldersvc.Create(context.Background(), &folder.CreateFolderCommand{ + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ OrgID: orgID, Title: "myFolder", UID: "myFolder", @@ -531,7 +541,7 @@ func TestNestedFolderService(t *testing.T) { require.Error(t, err, "FAILED") // CreateFolder should also call the folder store's create method. - require.True(t, store.CreateCalled) + require.True(t, folderStore.CreateCalled) require.NotNil(t, actualCmd) t.Cleanup(func() { @@ -547,9 +557,16 @@ func TestNestedFolderService(t *testing.T) { guardian.New = g }) - store.ExpectedError = nil - store.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - _, err := foldersvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) + dashboardsvc := dashboards.FakeDashboardService{} + dashStore := &dashboards.FakeDashboardStore{} + + folderStore := NewFakeStore() + folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + _, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) require.Error(t, err, dashboards.ErrFolderAccessDenied) }) @@ -561,9 +578,16 @@ func TestNestedFolderService(t *testing.T) { guardian.New = g }) - store.ExpectedError = nil - store.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - _, err := foldersvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) + dashboardsvc := dashboards.FakeDashboardService{} + dashStore := &dashboards.FakeDashboardStore{} + + folderStore := NewFakeStore() + folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + _, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) require.Error(t, err, dashboards.ErrFolderAccessDenied) }) @@ -575,14 +599,21 @@ func TestNestedFolderService(t *testing.T) { guardian.New = g }) - store.ExpectedError = nil - store.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - store.ExpectedParentFolders = []*folder.Folder{ + dashboardsvc := dashboards.FakeDashboardService{} + dashStore := &dashboards.FakeDashboardStore{} + + folderStore := NewFakeStore() + folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + folderStore.ExpectedParentFolders = []*folder.Folder{ {UID: "newFolder", ParentUID: "newFolder"}, {UID: "newFolder2", ParentUID: "newFolder2"}, {UID: "newFolder3", ParentUID: "newFolder3"}, } - f, err := foldersvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) require.NoError(t, err) require.NotNil(t, f) }) @@ -594,12 +625,19 @@ func TestNestedFolderService(t *testing.T) { guardian.New = g }) - store.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - store.ExpectedError = folder.ErrCircularReference - f, err := foldersvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) + dashboardsvc := dashboards.FakeDashboardService{} + dashStore := &dashboards.FakeDashboardStore{} + + folderStore := NewFakeStore() + folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + folderStore.ExpectedError = folder.ErrCircularReference + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr}) require.Error(t, err, folder.ErrCircularReference) require.Nil(t, f) - store.ExpectedChildFolders = []*folder.Folder{} }) t.Run("move when new parentUID depth + subTree height bypassed maximum depth returns error", func(t *testing.T) { @@ -609,14 +647,21 @@ func TestNestedFolderService(t *testing.T) { guardian.New = g }) - store.ExpectedError = nil - store.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - store.ExpectedParentFolders = []*folder.Folder{ + dashboardsvc := dashboards.FakeDashboardService{} + dashStore := &dashboards.FakeDashboardStore{} + + folderStore := NewFakeStore() + folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + folderStore.ExpectedParentFolders = []*folder.Folder{ {UID: "newFolder", ParentUID: "newFolder"}, {UID: "newFolder2", ParentUID: "newFolder2"}, } - store.ExpectedFolderHeight = 5 - f, err := foldersvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr}) + folderStore.ExpectedFolderHeight = 5 + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr}) require.Error(t, err, folder.ErrMaximumDepthReached) require.Nil(t, f) }) @@ -628,33 +673,48 @@ func TestNestedFolderService(t *testing.T) { guardian.New = g }) - store.ExpectedError = nil - store.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - store.ExpectedParentFolders = []*folder.Folder{{UID: "myFolder", ParentUID: "12345"}, {UID: "12345", ParentUID: ""}} - f, err := foldersvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr}) + dashboardsvc := dashboards.FakeDashboardService{} + dashStore := &dashboards.FakeDashboardStore{} + + folderStore := NewFakeStore() + folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + folderStore.ExpectedParentFolders = []*folder.Folder{{UID: "myFolder", ParentUID: "12345"}, {UID: "12345", ParentUID: ""}} + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr}) require.Error(t, err, folder.ErrCircularReference) require.Nil(t, f) - store.ExpectedChildFolders = []*folder.Folder{} }) t.Run("delete with success", func(t *testing.T) { + g := guardian.New + guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true}) + t.Cleanup(func() { + guardian.New = g + }) + + dashboardsvc := dashboards.FakeDashboardService{} + + dashStore := &dashboards.FakeDashboardStore{} var actualCmd *models.DeleteDashboardCommand dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { actualCmd = args.Get(1).(*models.DeleteDashboardCommand) }).Return(nil).Once() - dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&models.Folder{}, nil) + dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) - g := guardian.New - guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true}) + folderStore := NewFakeStore() + folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} - err := foldersvc.Delete(context.Background(), &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID, SignedInUser: usr}) + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + err := folderSvc.Delete(context.Background(), &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID, SignedInUser: usr}) require.NoError(t, err) require.NotNil(t, actualCmd) - t.Cleanup(func() { - guardian.New = g - }) - require.True(t, store.DeleteCalled) + require.True(t, folderStore.DeleteCalled) }) t.Run("create returns error if maximum depth reached", func(t *testing.T) { @@ -665,8 +725,9 @@ func TestNestedFolderService(t *testing.T) { guardian.New = g }) - dashStore = &dashboards.FakeDashboardStore{} - foldersvc.dashboardStore = dashStore + dashboardsvc := dashboards.FakeDashboardService{} + + dashStore := &dashboards.FakeDashboardStore{} // dashboard store & service commands that should be called. dashboardsvc.On("BuildSaveDashboardCommand", mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"), @@ -683,10 +744,15 @@ func TestNestedFolderService(t *testing.T) { for i := 0; i < folder.MaxNestedFolderDepth; i++ { parents = append(parents, &folder.Folder{UID: fmt.Sprintf("folder%d", i)}) } - store.ExpectedParentFolders = parents - store.ExpectedError = nil - _, err := foldersvc.Create(context.Background(), &folder.CreateFolderCommand{ + folderStore := NewFakeStore() + //folderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} + folderStore.ExpectedParentFolders = parents + + folderSvc := setup(t, dashStore, folderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{ + ExpectedEvaluate: true, + }, &dashboardsvc) + _, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ Title: "folder", OrgID: orgID, ParentUID: parents[len(parents)-1].UID, @@ -698,3 +764,20 @@ func TestNestedFolderService(t *testing.T) { }) }) } + +func setup(t *testing.T, dashStore dashboards.Store, folderStore store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl, dashboardService dashboards.DashboardService) folder.Service { + t.Helper() + + // nothing enabled yet + cfg := setting.NewCfg() + cfg.RBACEnabled = false + return &Service{ + cfg: cfg, + log: log.New("test-folder-service"), + dashboardService: dashboardService, + dashboardStore: dashStore, + store: folderStore, + features: features, + accessControl: ac, + } +}