diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index 9eaa2cefceb..735aa557fc5 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -40,6 +40,7 @@ import ( dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/licensing" @@ -410,7 +411,7 @@ func setupHTTPServerWithCfgDb( teamPermissionsService: teamPermissionService, searchUsersService: searchusers.ProvideUsersService(filters.ProvideOSSSearchUserFilter(), usertest.NewUserServiceFake()), DashboardService: dashboardservice.ProvideDashboardService( - cfg, dashboardsStore, dashboardsStore, nil, features, + cfg, dashboardsStore, folderimpl.ProvideDashboardFolderStore(db), nil, features, folderPermissionsService, dashboardPermissionsService, ac, folderSvc, ), diff --git a/pkg/api/dashboard_permission_test.go b/pkg/api/dashboard_permission_test.go index 0d2955b58a2..7bbfe2e481e 100644 --- a/pkg/api/dashboard_permission_test.go +++ b/pkg/api/dashboard_permission_test.go @@ -21,6 +21,7 @@ import ( dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/service" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder/folderimpl" + "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/setting" @@ -40,13 +41,13 @@ func TestDashboardPermissionAPIEndpoint(t *testing.T) { folderPermissions := accesscontrolmock.NewMockedPermissionsService() dashboardPermissions := accesscontrolmock.NewMockedPermissionsService() - folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), settings, dashboardStore, dashboards.NewFakeFolderStore(t), mockSQLStore, featuremgmt.WithFeatures()) + folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), settings, dashboardStore, foldertest.NewFakeFolderStore(t), mockSQLStore, featuremgmt.WithFeatures()) hs := &HTTPServer{ Cfg: settings, SQLStore: mockSQLStore, Features: features, DashboardService: dashboardservice.ProvideDashboardService( - settings, dashboardStore, dashboards.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac, + settings, dashboardStore, foldertest.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac, folderSvc, ), AccessControl: accesscontrolmock.New().WithDisabled(), diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index e5bbf001564..806bf7c05a4 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -960,7 +960,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { }) } -func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, provisioningService provisioning.ProvisioningService, dashboardStore dashboards.Store, dashboardService dashboards.DashboardService, folderStore dashboards.FolderStore) dtos.DashboardFullWithMeta { +func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, provisioningService provisioning.ProvisioningService, dashboardStore dashboards.Store, dashboardService dashboards.DashboardService, folderStore folder.FolderStore) dtos.DashboardFullWithMeta { t.Helper() if provisioningService == nil { diff --git a/pkg/api/folder_permission_test.go b/pkg/api/folder_permission_test.go index 03eeed7abe8..9f2190aa1f0 100644 --- a/pkg/api/folder_permission_test.go +++ b/pkg/api/folder_permission_test.go @@ -45,7 +45,7 @@ func TestFolderPermissionAPIEndpoint(t *testing.T) { folderPermissionsService: folderPermissions, dashboardPermissionsService: dashboardPermissions, DashboardService: service.ProvideDashboardService( - settings, dashboardStore, dashboards.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac, + settings, dashboardStore, foldertest.NewFakeFolderStore(t), nil, features, folderPermissions, dashboardPermissions, ac, folderService, ), AccessControl: accesscontrolmock.New().WithDisabled(), diff --git a/pkg/cmd/grafana-cli/runner/wire.go b/pkg/cmd/grafana-cli/runner/wire.go index 559a82139f4..3314bacc7c8 100644 --- a/pkg/cmd/grafana-cli/runner/wire.go +++ b/pkg/cmd/grafana-cli/runner/wire.go @@ -5,6 +5,8 @@ package runner import ( "context" + "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/services/folder/folderimpl" "github.com/google/wire" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" @@ -257,7 +259,8 @@ var wireSet = wire.NewSet( dashboardservice.ProvideDashboardProvisioningService, dashboardservice.ProvideDashboardPluginService, wire.Bind(new(dashboards.Store), new(*dashboardstore.DashboardStore)), - wire.Bind(new(dashboards.FolderStore), new(*dashboardstore.DashboardStore)), + folderimpl.ProvideDashboardFolderStore, + wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), dashboardimportservice.ProvideService, wire.Bind(new(dashboardimport.Service), new(*dashboardimportservice.ImportDashboardService)), plugindashboardsservice.ProvideService, diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 52898adfddf..f1e387d57f7 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -6,6 +6,7 @@ package server import ( "github.com/google/wire" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/api/avatar" @@ -290,11 +291,12 @@ var wireBasicSet = wire.NewSet( dashboardservice.ProvideDashboardService, // DashboardServiceImpl dashboardstore.ProvideDashboardStore, folderimpl.ProvideService, + folderimpl.ProvideDashboardFolderStore, dashboardservice.ProvideSimpleDashboardService, dashboardservice.ProvideDashboardProvisioningService, dashboardservice.ProvideDashboardPluginService, wire.Bind(new(dashboards.Store), new(*dashboardstore.DashboardStore)), - wire.Bind(new(dashboards.FolderStore), new(*dashboardstore.DashboardStore)), + wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), dashboardimportservice.ProvideService, wire.Bind(new(dashboardimport.Service), new(*dashboardimportservice.ImportDashboardService)), plugindashboardsservice.ProvideService, diff --git a/pkg/services/dashboards/accesscontrol.go b/pkg/services/dashboards/accesscontrol.go index 16ea72b2d59..fef2c57390b 100644 --- a/pkg/services/dashboards/accesscontrol.go +++ b/pkg/services/dashboards/accesscontrol.go @@ -39,7 +39,7 @@ var ( ) // NewFolderNameScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:name:" into an uid based scope. -func NewFolderNameScopeResolver(db Store, folderDB FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { +func NewFolderNameScopeResolver(db Store, folderDB folder.FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { prefix := ScopeFoldersProvider.GetResourceScopeName("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { if !strings.HasPrefix(scope, prefix) { @@ -65,7 +65,7 @@ func NewFolderNameScopeResolver(db Store, folderDB FolderStore, folderSvc folder } // NewFolderIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "folders:id:" into an uid based scope. -func NewFolderIDScopeResolver(db Store, folderDB FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { +func NewFolderIDScopeResolver(db Store, folderDB folder.FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { prefix := ScopeFoldersProvider.GetResourceScope("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { if !strings.HasPrefix(scope, prefix) { @@ -98,7 +98,7 @@ func NewFolderIDScopeResolver(db Store, folderDB FolderStore, folderSvc folder.S // NewDashboardIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:id:" // into uid based scopes for both dashboard and folder -func NewDashboardIDScopeResolver(db Store, folderDB FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { +func NewDashboardIDScopeResolver(db Store, folderDB folder.FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { prefix := ScopeDashboardsProvider.GetResourceScope("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { if !strings.HasPrefix(scope, prefix) { @@ -121,7 +121,7 @@ func NewDashboardIDScopeResolver(db Store, folderDB FolderStore, folderSvc folde // NewDashboardUIDScopeResolver provides an ScopeAttributeResolver that is able to convert a scope prefixed with "dashboards:uid:" // into uid based scopes for both dashboard and folder -func NewDashboardUIDScopeResolver(db Store, folderDB FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { +func NewDashboardUIDScopeResolver(db Store, folderDB folder.FolderStore, folderSvc folder.Service) (string, ac.ScopeAttributeResolver) { prefix := ScopeDashboardsProvider.GetResourceScopeUID("") return prefix, ac.ScopeAttributeResolverFunc(func(ctx context.Context, orgID int64, scope string) ([]string, error) { if !strings.HasPrefix(scope, prefix) { @@ -142,7 +142,7 @@ func NewDashboardUIDScopeResolver(db Store, folderDB FolderStore, folderSvc fold }) } -func resolveDashboardScope(ctx context.Context, db Store, folderDB FolderStore, orgID int64, dashboard *Dashboard, folderSvc folder.Service) ([]string, error) { +func resolveDashboardScope(ctx context.Context, db Store, folderDB folder.FolderStore, orgID int64, dashboard *Dashboard, folderSvc folder.Service) ([]string, error) { var folderUID string if dashboard.FolderID < 0 { return []string{ScopeDashboardsProvider.GetResourceScopeUID(dashboard.UID)}, nil diff --git a/pkg/services/dashboards/accesscontrol_test.go b/pkg/services/dashboards/accesscontrol_test.go index 19e78f48c81..1410cc7381a 100644 --- a/pkg/services/dashboards/accesscontrol_test.go +++ b/pkg/services/dashboards/accesscontrol_test.go @@ -19,7 +19,7 @@ import ( func TestNewFolderNameScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewFolderNameScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService()) + prefix, _ := NewFolderNameScopeResolver(&FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) require.Equal(t, "folders:name:", prefix) }) @@ -30,7 +30,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) { db := &folder.Folder{Title: title, ID: rand.Int63(), UID: util.GenerateShortUID()} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) folderStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once() scope := "folders:name:" + title @@ -52,7 +52,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) { db := &folder.Folder{Title: title, ID: rand.Int63(), UID: util.GenerateShortUID()} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) folderStore.On("GetFolderByTitle", mock.Anything, mock.Anything, mock.Anything).Return(db, nil).Once() scope := "folders:name:" + title @@ -84,14 +84,14 @@ func TestNewFolderNameScopeResolver(t *testing.T) { }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - _, resolver := NewFolderNameScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService()) + _, resolver := NewFolderNameScopeResolver(dashboardStore, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - _, resolver := NewFolderNameScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService()) + _, resolver := NewFolderNameScopeResolver(dashboardStore, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:name:") require.ErrorIs(t, err, ac.ErrInvalidScope) @@ -99,7 +99,7 @@ func TestNewFolderNameScopeResolver(t *testing.T) { t.Run("returns 'not found' if folder does not exist", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) _, resolver := NewFolderNameScopeResolver(dashboardStore, folderStore, foldertest.NewFakeService()) orgId := rand.Int63() @@ -115,13 +115,13 @@ func TestNewFolderNameScopeResolver(t *testing.T) { func TestNewFolderIDScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewFolderIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService()) + prefix, _ := NewFolderIDScopeResolver(&FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) require.Equal(t, "folders:id:", prefix) }) t.Run("resolver should convert to uid scope", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) _, resolver := NewFolderIDScopeResolver(dashboardStore, folderStore, foldertest.NewFakeService()) @@ -142,7 +142,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) { }) t.Run("resolver should should include inherited scopes if any", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) folderSvc := foldertest.NewFakeService() folderSvc.ExpectedFolders = []*folder.Folder{ @@ -179,7 +179,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) { }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - _, resolver := NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService()) + _, resolver := NewFolderIDScopeResolver(dashboardStore, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:uid:123") require.ErrorIs(t, err, ac.ErrInvalidScope) @@ -190,7 +190,7 @@ func TestNewFolderIDScopeResolver(t *testing.T) { dashboardStore = &FakeDashboardStore{} orgId = rand.Int63() scope = "folders:id:0" - _, resolver = NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService()) + _, resolver = NewFolderIDScopeResolver(dashboardStore, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) ) resolved, err := resolver.Resolve(context.Background(), orgId, scope) @@ -202,14 +202,14 @@ func TestNewFolderIDScopeResolver(t *testing.T) { t.Run("resolver should fail if resource of input scope is empty", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - _, resolver := NewFolderIDScopeResolver(dashboardStore, NewFakeFolderStore(t), foldertest.NewFakeService()) + _, resolver := NewFolderIDScopeResolver(dashboardStore, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "folders:id:") require.ErrorIs(t, err, ac.ErrInvalidScope) }) t.Run("returns 'not found' if folder does not exist", func(t *testing.T) { dashboardStore := &FakeDashboardStore{} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) _, resolver := NewFolderIDScopeResolver(dashboardStore, folderStore, foldertest.NewFakeService()) @@ -225,13 +225,13 @@ func TestNewFolderIDScopeResolver(t *testing.T) { func TestNewDashboardIDScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService()) + prefix, _ := NewDashboardIDScopeResolver(&FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) require.Equal(t, "dashboards:id:", prefix) }) t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) { store := &FakeDashboardStore{} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) _, resolver := NewDashboardIDScopeResolver(store, folderStore, foldertest.NewFakeService()) @@ -252,7 +252,7 @@ func TestNewDashboardIDScopeResolver(t *testing.T) { t.Run("resolver should inlude inherited scopes if any", func(t *testing.T) { store := &FakeDashboardStore{} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) folderSvc := foldertest.NewFakeService() folderSvc.ExpectedFolders = []*folder.Folder{ @@ -288,14 +288,14 @@ func TestNewDashboardIDScopeResolver(t *testing.T) { }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { - _, resolver := NewDashboardIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService()) + _, resolver := NewDashboardIDScopeResolver(&FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:uid:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) t.Run("resolver should convert folderID 0 to general uid scope for the folder scope", func(t *testing.T) { store := &FakeDashboardStore{} - _, resolver := NewDashboardIDScopeResolver(store, NewFakeFolderStore(t), foldertest.NewFakeService()) + _, resolver := NewDashboardIDScopeResolver(store, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"} store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil) @@ -310,13 +310,13 @@ func TestNewDashboardIDScopeResolver(t *testing.T) { func TestNewDashboardUIDScopeResolver(t *testing.T) { t.Run("prefix should be expected", func(t *testing.T) { - prefix, _ := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService()) + prefix, _ := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) require.Equal(t, "dashboards:uid:", prefix) }) t.Run("resolver should convert to uid dashboard and folder scope", func(t *testing.T) { store := &FakeDashboardStore{} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) _, resolver := NewDashboardUIDScopeResolver(store, folderStore, foldertest.NewFakeService()) orgID := rand.Int63() @@ -336,7 +336,7 @@ func TestNewDashboardUIDScopeResolver(t *testing.T) { t.Run("resolver should include inherited scopes if any", func(t *testing.T) { store := &FakeDashboardStore{} - folderStore := NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) folderSvc := foldertest.NewFakeService() folderSvc.ExpectedFolders = []*folder.Folder{ @@ -373,14 +373,14 @@ func TestNewDashboardUIDScopeResolver(t *testing.T) { }) t.Run("resolver should fail if input scope is not expected", func(t *testing.T) { - _, resolver := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, NewFakeFolderStore(t), foldertest.NewFakeService()) + _, resolver := NewDashboardUIDScopeResolver(&FakeDashboardStore{}, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) _, err := resolver.Resolve(context.Background(), rand.Int63(), "dashboards:id:123") require.ErrorIs(t, err, ac.ErrInvalidScope) }) t.Run("resolver should convert folderID 0 to general uid scope for the folder scope", func(t *testing.T) { store := &FakeDashboardStore{} - _, resolver := NewDashboardUIDScopeResolver(store, NewFakeFolderStore(t), foldertest.NewFakeService()) + _, resolver := NewDashboardUIDScopeResolver(store, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService()) dashboard := &Dashboard{ID: 1, FolderID: 0, UID: "1"} store.On("GetDashboard", mock.Anything, mock.Anything).Return(dashboard, nil) diff --git a/pkg/services/dashboards/dashboard.go b/pkg/services/dashboards/dashboard.go index 54092578208..81f9d1db611 100644 --- a/pkg/services/dashboards/dashboard.go +++ b/pkg/services/dashboards/dashboard.go @@ -84,15 +84,3 @@ type Store interface { // the given parent folder ID. CountDashboardsInFolder(ctx context.Context, request *CountDashboardsInFolderRequest) (int64, error) } - -// FolderStore is a folder store. -// -//go:generate mockery --name FolderStore --structname FakeFolderStore --inpackage --filename folder_store_mock.go -type FolderStore interface { - // GetFolderByTitle retrieves a folder by its title - GetFolderByTitle(ctx context.Context, orgID int64, title string) (*folder.Folder, error) - // GetFolderByUID retrieves a folder by its UID - GetFolderByUID(ctx context.Context, orgID int64, uid string) (*folder.Folder, error) - // GetFolderByID retrieves a folder by its ID - GetFolderByID(ctx context.Context, orgID int64, id int64) (*folder.Folder, error) -} diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 1d8b2bc0d47..36c15597985 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -16,7 +16,6 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" dashver "github.com/grafana/grafana/pkg/services/dashboardversion" "github.com/grafana/grafana/pkg/services/featuremgmt" - "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore/migrator" @@ -93,73 +92,6 @@ func (d *DashboardStore) ValidateDashboardBeforeSave(ctx context.Context, dashbo return isParentFolderChanged, nil } -func (d *DashboardStore) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*folder.Folder, error) { - if title == "" { - return nil, dashboards.ErrFolderTitleEmpty - } - - // there is a unique constraint on org_id, folder_id, title - // there are no nested folders so the parent folder id is always 0 - dashboard := dashboards.Dashboard{OrgID: orgID, FolderID: 0, Title: title} - err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - has, err := sess.Table(&dashboards.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard) - if err != nil { - return err - } - if !has { - return dashboards.ErrFolderNotFound - } - dashboard.SetID(dashboard.ID) - dashboard.SetUID(dashboard.UID) - return nil - }) - return dashboards.FromDashboard(&dashboard), err -} - -func (d *DashboardStore) GetFolderByID(ctx context.Context, orgID int64, id int64) (*folder.Folder, error) { - dashboard := dashboards.Dashboard{OrgID: orgID, FolderID: 0, ID: id} - err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - has, err := sess.Table(&dashboards.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard) - if err != nil { - return err - } - if !has { - return dashboards.ErrFolderNotFound - } - dashboard.SetID(dashboard.ID) - dashboard.SetUID(dashboard.UID) - return nil - }) - if err != nil { - return nil, err - } - return dashboards.FromDashboard(&dashboard), nil -} - -func (d *DashboardStore) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*folder.Folder, error) { - if uid == "" { - return nil, dashboards.ErrDashboardIdentifierNotSet - } - - dashboard := dashboards.Dashboard{OrgID: orgID, FolderID: 0, UID: uid} - err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error { - has, err := sess.Table(&dashboards.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard) - if err != nil { - return err - } - if !has { - return dashboards.ErrFolderNotFound - } - dashboard.SetID(dashboard.ID) - dashboard.SetUID(dashboard.UID) - return nil - }) - if err != nil { - return nil, err - } - return dashboards.FromDashboard(&dashboard), nil -} - func (d *DashboardStore) GetProvisionedDataByDashboardID(ctx context.Context, dashboardID int64) (*dashboards.DashboardProvisioning, error) { var data dashboards.DashboardProvisioning err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error { diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index a63517b83d4..09a1e0a1af9 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -474,78 +474,6 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) { }) }) }) - - t.Run("Given dashboard and folder with the same title", func(t *testing.T) { - var orgId int64 = 1 - title := "Very Unique Name" - var sqlStore *sqlstore.SQLStore - var folder1, folder2 *dashboards.Dashboard - sqlStore = db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) - folder2 = insertTestDashboard(t, dashboardStore, "TEST", orgId, 0, true, "prod") - _ = insertTestDashboard(t, dashboardStore, title, orgId, folder2.ID, false, "prod") - folder1 = insertTestDashboard(t, dashboardStore, title, orgId, 0, true, "prod") - - t.Run("GetFolderByTitle should find the folder", func(t *testing.T) { - result, err := dashboardStore.GetFolderByTitle(context.Background(), orgId, title) - require.NoError(t, err) - require.Equal(t, folder1.ID, result.ID) - }) - }) - - t.Run("GetFolderByUID", func(t *testing.T) { - var orgId int64 = 1 - sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) - folder := insertTestDashboard(t, dashboardStore, "TEST", orgId, 0, true, "prod") - dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, false, "prod") - - t.Run("should return folder by UID", func(t *testing.T) { - d, err := dashboardStore.GetFolderByUID(context.Background(), orgId, folder.UID) - require.Equal(t, folder.ID, d.ID) - require.NoError(t, err) - }) - 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, 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, dashboards.ErrFolderNotFound) - }) - }) - - t.Run("GetFolderByID", func(t *testing.T) { - var orgId int64 = 1 - sqlStore := db.InitTestDB(t) - quotaService := quotatest.New(false, nil) - dashboardStore, err := ProvideDashboardStore(sqlStore, sqlStore.Cfg, testFeatureToggles, tagimpl.ProvideService(sqlStore, sqlStore.Cfg), quotaService) - require.NoError(t, err) - folder := insertTestDashboard(t, dashboardStore, "TEST", orgId, 0, true, "prod") - dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, false, "prod") - - t.Run("should return folder by ID", func(t *testing.T) { - d, err := dashboardStore.GetFolderByID(context.Background(), orgId, folder.ID) - require.Equal(t, folder.ID, d.ID) - require.NoError(t, err) - }) - 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, 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, dashboards.ErrFolderNotFound) - }) - }) }) } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index ca46e7beb35..6dba87c13af 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -40,7 +40,7 @@ type DashboardServiceImpl struct { cfg *setting.Cfg log log.Logger dashboardStore dashboards.Store - folderStore dashboards.FolderStore + folderStore folder.FolderStore dashAlertExtractor alerting.DashAlertExtractor features featuremgmt.FeatureToggles folderPermissions accesscontrol.FolderPermissionsService @@ -50,7 +50,7 @@ type DashboardServiceImpl struct { // This is the uber service that implements a three smaller services func ProvideDashboardService( - cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore dashboards.FolderStore, dashAlertExtractor alerting.DashAlertExtractor, + cfg *setting.Cfg, dashboardStore dashboards.Store, folderStore folder.FolderStore, dashAlertExtractor alerting.DashAlertExtractor, features featuremgmt.FeatureToggles, folderPermissionsService accesscontrol.FolderPermissionsService, dashboardPermissionsService accesscontrol.DashboardPermissionsService, ac accesscontrol.AccessControl, folderSvc folder.Service, diff --git a/pkg/services/dashboards/service/dashboard_service_integration_test.go b/pkg/services/dashboards/service/dashboard_service_integration_test.go index 5a3c59233c0..f91ffbe27c3 100644 --- a/pkg/services/dashboards/service/dashboard_service_integration_test.go +++ b/pkg/services/dashboards/service/dashboard_service_integration_test.go @@ -15,6 +15,7 @@ import ( "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/folder/folderimpl" "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/org" @@ -826,8 +827,9 @@ func permissionScenario(t *testing.T, desc string, canSave bool, fn permissionSc quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) service := ProvideDashboardService( - cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), @@ -886,8 +888,9 @@ func callSaveWithResult(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSt quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) service := ProvideDashboardService( - cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), @@ -908,8 +911,9 @@ func callSaveWithError(t *testing.T, cmd dashboards.SaveDashboardCommand, sqlSto quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) service := ProvideDashboardService( - cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), @@ -948,8 +952,9 @@ func saveTestDashboard(t *testing.T, title string, orgID, folderID int64, sqlSto quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) service := ProvideDashboardService( - cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), @@ -989,8 +994,9 @@ func saveTestFolder(t *testing.T, title string, orgID int64, sqlStore db.DB) *da quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) service := ProvideDashboardService( - cfg, dashboardStore, dashboardStore, &dummyDashAlertExtractor{}, + cfg, dashboardStore, folderStore, &dummyDashAlertExtractor{}, featuremgmt.WithFeatures(), accesscontrolmock.NewMockedPermissionsService(), accesscontrolmock.NewMockedPermissionsService(), diff --git a/pkg/services/dashboards/service/dashboard_service_test.go b/pkg/services/dashboards/service/dashboard_service_test.go index 2f3b80a0be4..853ec701bbe 100644 --- a/pkg/services/dashboards/service/dashboard_service_test.go +++ b/pkg/services/dashboards/service/dashboard_service_test.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/services/folder/foldertest" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/setting" @@ -24,7 +25,7 @@ func TestDashboardService(t *testing.T) { fakeStore := dashboards.FakeDashboardStore{} defer fakeStore.AssertExpectations(t) - folderStore := dashboards.NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) service := &DashboardServiceImpl{ cfg: setting.NewCfg(), diff --git a/pkg/services/folder/folderimpl/dashboard_folder_store.go b/pkg/services/folder/folderimpl/dashboard_folder_store.go new file mode 100644 index 00000000000..d498e97176e --- /dev/null +++ b/pkg/services/folder/folderimpl/dashboard_folder_store.go @@ -0,0 +1,86 @@ +package folderimpl + +import ( + "context" + + "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/folder" +) + +// DashboardStore implements the FolderStore interface +// It fetches folders from the dashboard DB table +type DashboardFolderStoreImpl struct { + store db.DB +} + +func ProvideDashboardFolderStore(sqlStore db.DB) *DashboardFolderStoreImpl { + return &DashboardFolderStoreImpl{store: sqlStore} +} + +func (d *DashboardFolderStoreImpl) GetFolderByTitle(ctx context.Context, orgID int64, title string) (*folder.Folder, error) { + if title == "" { + return nil, dashboards.ErrFolderTitleEmpty + } + + // there is a unique constraint on org_id, folder_id, title + // there are no nested folders so the parent folder id is always 0 + dashboard := dashboards.Dashboard{OrgID: orgID, FolderID: 0, Title: title} + err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + has, err := sess.Table(&dashboards.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard) + if err != nil { + return err + } + if !has { + return dashboards.ErrFolderNotFound + } + dashboard.SetID(dashboard.ID) + dashboard.SetUID(dashboard.UID) + return nil + }) + return dashboards.FromDashboard(&dashboard), err +} + +func (d *DashboardFolderStoreImpl) GetFolderByID(ctx context.Context, orgID int64, id int64) (*folder.Folder, error) { + dashboard := dashboards.Dashboard{OrgID: orgID, FolderID: 0, ID: id} + err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + has, err := sess.Table(&dashboards.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard) + if err != nil { + return err + } + if !has { + return dashboards.ErrFolderNotFound + } + dashboard.SetID(dashboard.ID) + dashboard.SetUID(dashboard.UID) + return nil + }) + if err != nil { + return nil, err + } + return dashboards.FromDashboard(&dashboard), nil +} + +func (d *DashboardFolderStoreImpl) GetFolderByUID(ctx context.Context, orgID int64, uid string) (*folder.Folder, error) { + if uid == "" { + return nil, dashboards.ErrDashboardIdentifierNotSet + } + + dashboard := dashboards.Dashboard{OrgID: orgID, FolderID: 0, UID: uid} + err := d.store.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + has, err := sess.Table(&dashboards.Dashboard{}).Where("is_folder = " + d.store.GetDialect().BooleanStr(true)).Where("folder_id=0").Get(&dashboard) + if err != nil { + return err + } + if !has { + return dashboards.ErrFolderNotFound + } + dashboard.SetID(dashboard.ID) + dashboard.SetUID(dashboard.UID) + return nil + }) + if err != nil { + return nil, err + } + return dashboards.FromDashboard(&dashboard), nil +} diff --git a/pkg/services/folder/folderimpl/dashboard_folder_store_test.go b/pkg/services/folder/folderimpl/dashboard_folder_store_test.go new file mode 100644 index 00000000000..dc0c5b040c9 --- /dev/null +++ b/pkg/services/folder/folderimpl/dashboard_folder_store_test.go @@ -0,0 +1,137 @@ +package folderimpl + +import ( + "context" + "testing" + + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/infra/db" + "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/quota/quotatest" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/tag/tagimpl" + "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/require" +) + +func TestIntegrationDashboardFolderStore(t *testing.T) { + var sqlStore *sqlstore.SQLStore + var cfg *setting.Cfg + var dashboardStore *database.DashboardStore + + setup := func() { + sqlStore, cfg = db.InitTestDBwithCfg(t) + quotaService := quotatest.New(false, nil) + var err error + dashboardStore, err = database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch), tagimpl.ProvideService(sqlStore, cfg), quotaService) + require.NoError(t, err) + } + t.Run("Given dashboard and folder with the same title", func(t *testing.T) { + setup() + var orgId int64 = 1 + title := "Very Unique Name" + var sqlStore *sqlstore.SQLStore + var folder1, folder2 *dashboards.Dashboard + sqlStore = db.InitTestDB(t) + folderStore := ProvideDashboardFolderStore(sqlStore) + folder2 = insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "prod") + _ = insertTestDashboard(t, dashboardStore, title, orgId, folder2.ID, "prod") + folder1 = insertTestFolder(t, dashboardStore, title, orgId, 0, "prod") + + t.Run("GetFolderByTitle should find the folder", func(t *testing.T) { + result, err := folderStore.GetFolderByTitle(context.Background(), orgId, title) + require.NoError(t, err) + require.Equal(t, folder1.ID, result.ID) + }) + }) + + t.Run("GetFolderByUID", func(t *testing.T) { + var orgId int64 = 1 + sqlStore := db.InitTestDB(t) + folderStore := ProvideDashboardFolderStore(sqlStore) + folder := insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "prod") + dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, "prod") + + t.Run("should return folder by UID", func(t *testing.T) { + d, err := folderStore.GetFolderByUID(context.Background(), orgId, folder.UID) + require.Equal(t, folder.ID, d.ID) + require.NoError(t, err) + }) + t.Run("should not find dashboard", func(t *testing.T) { + d, err := folderStore.GetFolderByUID(context.Background(), orgId, dash.UID) + require.Nil(t, d) + require.ErrorIs(t, err, dashboards.ErrFolderNotFound) + }) + t.Run("should search in organization", func(t *testing.T) { + d, err := folderStore.GetFolderByUID(context.Background(), orgId+1, folder.UID) + require.Nil(t, d) + require.ErrorIs(t, err, dashboards.ErrFolderNotFound) + }) + }) + + t.Run("GetFolderByID", func(t *testing.T) { + var orgId int64 = 1 + sqlStore := db.InitTestDB(t) + folderStore := ProvideDashboardFolderStore(sqlStore) + folder := insertTestFolder(t, dashboardStore, "TEST", orgId, 0, "prod") + dash := insertTestDashboard(t, dashboardStore, "Very Unique Name", orgId, folder.ID, "prod") + + t.Run("should return folder by ID", func(t *testing.T) { + d, err := folderStore.GetFolderByID(context.Background(), orgId, folder.ID) + require.Equal(t, folder.ID, d.ID) + require.NoError(t, err) + }) + t.Run("should not find dashboard", func(t *testing.T) { + d, err := folderStore.GetFolderByID(context.Background(), orgId, dash.ID) + require.Nil(t, d) + require.ErrorIs(t, err, dashboards.ErrFolderNotFound) + }) + t.Run("should search in organization", func(t *testing.T) { + d, err := folderStore.GetFolderByID(context.Background(), orgId+1, folder.ID) + require.Nil(t, d) + require.ErrorIs(t, err, dashboards.ErrFolderNotFound) + }) + }) +} + +func insertTestDashboard(t *testing.T, dashboardStore *database.DashboardStore, title string, orgId int64, folderId int64, tags ...interface{}) *dashboards.Dashboard { + t.Helper() + cmd := dashboards.SaveDashboardCommand{ + OrgID: orgId, + FolderID: folderId, + IsFolder: false, + Dashboard: simplejson.NewFromAny(map[string]interface{}{ + "id": nil, + "title": title, + "tags": tags, + }), + } + dash, err := dashboardStore.SaveDashboard(context.Background(), cmd) + require.NoError(t, err) + require.NotNil(t, dash) + dash.Data.Set("id", dash.ID) + dash.Data.Set("uid", dash.UID) + return dash +} + +func insertTestFolder(t *testing.T, dashboardStore *database.DashboardStore, title string, orgId int64, folderId int64, tags ...interface{}) *dashboards.Dashboard { + t.Helper() + cmd := dashboards.SaveDashboardCommand{ + OrgID: orgId, + FolderID: folderId, + IsFolder: true, + Dashboard: simplejson.NewFromAny(map[string]interface{}{ + "id": nil, + "title": title, + "tags": tags, + }), + } + dash, err := dashboardStore.SaveDashboard(context.Background(), cmd) + require.NoError(t, err) + require.NotNil(t, dash) + dash.Data.Set("id", dash.ID) + dash.Data.Set("uid", dash.UID) + return dash +} diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index e9da3fa4cec..fa7702dbcbe 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -30,7 +30,7 @@ type Service struct { log log.Logger cfg *setting.Cfg dashboardStore dashboards.Store - dashboardFolderStore dashboards.FolderStore + dashboardFolderStore folder.FolderStore features featuremgmt.FeatureToggles accessControl accesscontrol.AccessControl @@ -43,7 +43,7 @@ func ProvideService( bus bus.Bus, cfg *setting.Cfg, dashboardStore dashboards.Store, - folderStore dashboards.FolderStore, + folderStore folder.FolderStore, db db.DB, // DB for the (new) nested folder store features featuremgmt.FeatureToggles, ) folder.Service { diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 3f989943f06..b7f02a8f828 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -20,6 +20,7 @@ import ( "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/folder/foldertest" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/user" @@ -52,7 +53,7 @@ func TestIntegrationFolderService(t *testing.T) { db := sqlstore.InitTestDB(t) nestedFolderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures([]interface{}{"nestedFolders"})) - folderStore := dashboards.NewFakeFolderStore(t) + folderStore := foldertest.NewFakeFolderStore(t) cfg := setting.NewCfg() cfg.RBACEnabled = false @@ -325,7 +326,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) { 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(&dashboards.Dashboard{}, nil) - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) cfg := setting.NewCfg() @@ -361,7 +362,7 @@ func TestNestedFolderService(t *testing.T) { 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(&dashboards.Dashboard{}, nil) - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) nestedFolderStore := NewFakeStore() @@ -385,7 +386,7 @@ func TestNestedFolderService(t *testing.T) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) dashboardFolderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) nestedFolderStore := NewFakeStore() @@ -412,7 +413,7 @@ func TestNestedFolderService(t *testing.T) { 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(&dashboards.Dashboard{}, nil) - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) nestedFolderStore := NewFakeStore() @@ -443,7 +444,7 @@ func TestNestedFolderService(t *testing.T) { 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(&dashboards.Dashboard{UID: "newUID"}, nil) - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) nestedFolderStore := NewFakeStore() @@ -483,7 +484,7 @@ func TestNestedFolderService(t *testing.T) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) dashboardFolderStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.ID).Return(f, nil) nestedFolderStore := NewFakeStore() @@ -529,7 +530,7 @@ func TestNestedFolderService(t *testing.T) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) // return an error from the folder store @@ -562,7 +563,7 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) nestedFolderStore := NewFakeStore() nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} @@ -583,7 +584,7 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) nestedFolderStore := NewFakeStore() nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} @@ -604,7 +605,7 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) nestedFolderStore := NewFakeStore() nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} @@ -630,7 +631,7 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) nestedFolderStore := NewFakeStore() nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} @@ -652,7 +653,7 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) nestedFolderStore := NewFakeStore() nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} @@ -678,7 +679,7 @@ func TestNestedFolderService(t *testing.T) { }) dashStore := &dashboards.FakeDashboardStore{} - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) nestedFolderStore := NewFakeStore() nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"} @@ -705,7 +706,7 @@ func TestNestedFolderService(t *testing.T) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) dashboardFolderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil) nestedFolderStore := NewFakeStore() @@ -738,7 +739,7 @@ func TestNestedFolderService(t *testing.T) { actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand) }).Return(nil).Once() - dashboardFolderStore := dashboards.NewFakeFolderStore(t) + dashboardFolderStore := foldertest.NewFakeFolderStore(t) dashboardFolderStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&folder.Folder{}, nil) parents := make([]*folder.Folder, 0, folder.MaxNestedFolderDepth) @@ -766,7 +767,7 @@ func TestNestedFolderService(t *testing.T) { }) } -func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore dashboards.FolderStore, nestedFolderStore store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service { +func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder.FolderStore, nestedFolderStore store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service { t.Helper() // nothing enabled yet diff --git a/pkg/services/dashboards/folder_store_mock.go b/pkg/services/folder/foldertest/folder_store_mock.go similarity index 99% rename from pkg/services/dashboards/folder_store_mock.go rename to pkg/services/folder/foldertest/folder_store_mock.go index 47fdf2baa87..7f9ad2502b3 100644 --- a/pkg/services/dashboards/folder_store_mock.go +++ b/pkg/services/folder/foldertest/folder_store_mock.go @@ -1,13 +1,12 @@ // Code generated by mockery v2.16.0. DO NOT EDIT. -package dashboards +package foldertest import ( context "context" - mock "github.com/stretchr/testify/mock" - folder "github.com/grafana/grafana/pkg/services/folder" + mock "github.com/stretchr/testify/mock" ) // FakeFolderStore is an autogenerated mock type for the FolderStore type diff --git a/pkg/services/folder/service.go b/pkg/services/folder/service.go index f981ea8a946..068588fba17 100644 --- a/pkg/services/folder/service.go +++ b/pkg/services/folder/service.go @@ -26,3 +26,15 @@ type Service interface { // Move changes a folder's parent folder to the requested new parent. Move(ctx context.Context, cmd *MoveFolderCommand) (*Folder, error) } + +// FolderStore is a folder store. +// +//go:generate mockery --name FolderStore --structname FakeFolderStore --outpkg foldertest --output foldertest --filename folder_store_mock.go +type FolderStore interface { + // GetFolderByTitle retrieves a folder by its title + GetFolderByTitle(ctx context.Context, orgID int64, title string) (*Folder, error) + // GetFolderByUID retrieves a folder by its UID + GetFolderByUID(ctx context.Context, orgID int64, uid string) (*Folder, error) + // GetFolderByID retrieves a folder by its ID + GetFolderByID(ctx context.Context, orgID int64, id int64) (*Folder, error) +} diff --git a/pkg/services/guardian/accesscontrol_guardian_test.go b/pkg/services/guardian/accesscontrol_guardian_test.go index f64b3ddc7cc..0eb5b7eebfc 100644 --- a/pkg/services/guardian/accesscontrol_guardian_test.go +++ b/pkg/services/guardian/accesscontrol_guardian_test.go @@ -602,7 +602,8 @@ func setupAccessControlGuardianTest(t *testing.T, uid string, permissions []acce }) require.NoError(t, err) ac := accesscontrolmock.New().WithPermissions(permissions) - ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(dashStore, dashStore, foldertest.NewFakeService())) + // TODO replace with actual folder store implementation after resolving import cycles + ac.RegisterScopeAttributeResolver(dashboards.NewDashboardUIDScopeResolver(dashStore, foldertest.NewFakeFolderStore(t), foldertest.NewFakeService())) license := licensingtest.NewFakeLicensing() license.On("FeatureEnabled", "accesscontrol.enforcement").Return(true).Maybe() teamSvc := teamimpl.ProvideService(store, store.Cfg) diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index cce5e99f181..662c6b3e70d 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -293,8 +293,9 @@ func createDashboard(t *testing.T, sqlStore db.DB, user user.SignedInUser, dash ac := acmock.New() folderPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService() + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) service := dashboardservice.ProvideDashboardService( - cfg, dashboardStore, dashboardStore, dashAlertExtractor, + cfg, dashboardStore, folderStore, dashAlertExtractor, features, folderPermissions, dashboardPermissions, ac, foldertest.NewFakeService(), ) @@ -317,7 +318,8 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.S dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) - s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features) + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) + s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features) t.Logf("Creating folder with title and UID %q", title) ctx := appcontext.WithUser(context.Background(), &user) folder, err := s.Create(ctx, &folder.CreateFolderCommand{ @@ -437,8 +439,9 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo sqlStore.Cfg.RBACEnabled = false folderPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService() + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) dashboardService := dashboardservice.ProvideDashboardService( - sqlStore.Cfg, dashboardStore, dashboardStore, nil, + sqlStore.Cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions, ac, foldertest.NewFakeService(), ) @@ -446,7 +449,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo service := LibraryElementService{ Cfg: sqlStore.Cfg, SQLStore: sqlStore, - folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, dashboardStore, nil, features), + folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardStore, folderStore, nil, features), } // deliberate difference between signed in user and user in db to make it crystal clear diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index c6110ce11f5..5e83e48b370 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -705,8 +705,9 @@ func createDashboard(t *testing.T, sqlStore db.DB, user *user.SignedInUser, dash require.NoError(t, err) dashAlertService := alerting.ProvideDashAlertExtractorService(nil, nil, nil) ac := acmock.New() + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) service := dashboardservice.ProvideDashboardService( - cfg, dashboardStore, dashboardStore, dashAlertService, + cfg, dashboardStore, folderStore, dashAlertService, featuremgmt.WithFeatures(), acmock.NewMockedPermissionsService(), acmock.NewMockedPermissionsService(), ac, foldertest.NewFakeService(), ) @@ -728,7 +729,8 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user. quotaService := quotatest.New(false, nil) dashboardStore, err := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg), quotaService) require.NoError(t, err) - s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features) + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) + s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features) t.Logf("Creating folder with title and UID %q", title) ctx := appcontext.WithUser(context.Background(), user) @@ -836,7 +838,9 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo features := featuremgmt.WithFeatures() ac := acmock.New() - folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, dashboardStore, nil, features) + + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) + folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardStore, folderStore, nil, features) elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService) service := LibraryPanelService{ diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index 23b1bda57fa..7c8728a3485 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -79,15 +79,17 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG, folderPermissions.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil) dashboardPermissions := acmock.NewMockedPermissionsService() + folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) + dashboardService := dashboardservice.ProvideDashboardService( - cfg, dashboardStore, dashboardStore, nil, + cfg, dashboardStore, folderStore, nil, features, folderPermissions, dashboardPermissions, ac, foldertest.NewFakeService(), ) tracer := tracing.InitializeTracerForTest() bus := bus.ProvideBus(tracer) - folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, dashboardStore, nil, features) + folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardStore, folderStore, nil, features) ng, err := ngalert.ProvideService( cfg, featuremgmt.WithFeatures(), nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, quotatest.New(false, nil),