nested folders: support creation of nested folders in folder service when feature flag is set (#58364)

* nested folders: support creation of nested folders in folder service when feature flag is set
This commit is contained in:
Kristin Laemmert
2022-11-08 08:59:55 -05:00
committed by GitHub
parent 94573d3e61
commit a255c32e1a
9 changed files with 184 additions and 43 deletions

View File

@@ -7,12 +7,14 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/search" "github.com/grafana/grafana/pkg/services/search"
@@ -29,7 +31,7 @@ type Service struct {
dashboardService dashboards.DashboardService dashboardService dashboards.DashboardService
dashboardStore dashboards.Store dashboardStore dashboards.Store
searchService *search.SearchService searchService *search.SearchService
features featuremgmt.FeatureToggles features *featuremgmt.FeatureManager
permissions accesscontrol.FolderPermissionsService permissions accesscontrol.FolderPermissionsService
// bus is currently used to publish events that cause scheduler to update rules. // bus is currently used to publish events that cause scheduler to update rules.
@@ -42,17 +44,20 @@ func ProvideService(
cfg *setting.Cfg, cfg *setting.Cfg,
dashboardService dashboards.DashboardService, dashboardService dashboards.DashboardService,
dashboardStore dashboards.Store, dashboardStore dashboards.Store,
features featuremgmt.FeatureToggles, db db.DB, // DB for the (new) nested folder store
features *featuremgmt.FeatureManager,
folderPermissionsService accesscontrol.FolderPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService,
searchService *search.SearchService, searchService *search.SearchService,
) folder.Service { ) folder.Service {
ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(dashboardStore)) ac.RegisterScopeAttributeResolver(dashboards.NewFolderNameScopeResolver(dashboardStore))
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(dashboardStore)) ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(dashboardStore))
store := ProvideStore(db, cfg, features)
return &Service{ return &Service{
cfg: cfg, cfg: cfg,
log: log.New("folder-service"), log: log.New("folder-service"),
dashboardService: dashboardService, dashboardService: dashboardService,
dashboardStore: dashboardStore, dashboardStore: dashboardStore,
store: store,
searchService: searchService, searchService: searchService,
features: features, features: features,
permissions: folderPermissionsService, permissions: folderPermissionsService,
@@ -178,8 +183,8 @@ func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, org
return nil, toFolderError(err) return nil, toFolderError(err)
} }
var folder *models.Folder var createdFolder *models.Folder
folder, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id) createdFolder, err = s.dashboardStore.GetFolderByID(ctx, orgID, dash.Id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -198,16 +203,45 @@ func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, org
{BuiltinRole: string(org.RoleViewer), Permission: models.PERMISSION_VIEW.String()}, {BuiltinRole: string(org.RoleViewer), Permission: models.PERMISSION_VIEW.String()},
}...) }...)
_, permissionErr = s.permissions.SetPermissions(ctx, orgID, folder.Uid, permissions...) _, permissionErr = s.permissions.SetPermissions(ctx, orgID, createdFolder.Uid, permissions...)
} else if s.cfg.EditorsCanAdmin && user.IsRealUser() && !user.IsAnonymous { } else if s.cfg.EditorsCanAdmin && user.IsRealUser() && !user.IsAnonymous {
permissionErr = s.MakeUserAdmin(ctx, orgID, userID, folder.Id, true) permissionErr = s.MakeUserAdmin(ctx, orgID, userID, createdFolder.Id, true)
} }
if permissionErr != nil { if permissionErr != nil {
s.log.Error("Could not make user admin", "folder", folder.Title, "user", userID, "error", permissionErr) s.log.Error("Could not make user admin", "folder", createdFolder.Title, "user", userID, "error", permissionErr)
} }
return folder, nil if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
var description string
if dash.Data != nil {
description = dash.Data.Get("description").MustString()
}
_, err := s.store.Create(ctx, folder.CreateFolderCommand{
// TODO: Today, if a UID isn't specified, the dashboard store
// generates a new UID. The new folder store will need to do this as
// well, but for now we take the UID from the newly created folder.
UID: dash.Uid,
OrgID: orgID,
Title: title,
Description: description,
ParentUID: folder.RootFolderUID,
})
if err != nil {
// We'll log the error and also roll back the previously-created
// (legacy) folder.
s.log.Error("error saving folder to nested folder store", err)
_, err = s.DeleteFolder(ctx, user, orgID, createdFolder.Uid, true)
if err != nil {
s.log.Error("error deleting folder after failed save to nested folder store", err)
}
return createdFolder, err
}
// The folder UID is specified (or generated) during creation, so we'll
// stop here and return the created model.Folder.
}
return createdFolder, nil
} }
func (s *Service) UpdateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error { func (s *Service) UpdateFolder(ctx context.Context, user *user.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {

View File

@@ -2,6 +2,7 @@ package folderimpl
import ( import (
"context" "context"
"errors"
"math/rand" "math/rand"
"testing" "testing"
@@ -10,6 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@@ -19,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
@@ -34,7 +37,7 @@ func TestIntegrationProvideFolderService(t *testing.T) {
t.Run("should register scope resolvers", func(t *testing.T) { t.Run("should register scope resolvers", func(t *testing.T) {
cfg := setting.NewCfg() cfg := setting.NewCfg()
ac := acmock.New() ac := acmock.New()
ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, nil, nil, nil) ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, nil, nil, nil, &featuremgmt.FeatureManager{}, nil, nil)
require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2) require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 2)
}) })
@@ -45,20 +48,24 @@ func TestIntegrationFolderService(t *testing.T) {
t.Skip("skipping integration test") t.Skip("skipping integration test")
} }
t.Run("Folder service tests", func(t *testing.T) { t.Run("Folder service tests", func(t *testing.T) {
store := &dashboards.FakeDashboardStore{} dashStore := &dashboards.FakeDashboardStore{}
db := sqlstore.InitTestDB(t)
store := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures([]interface{}{"nestedFolders"}))
cfg := setting.NewCfg() cfg := setting.NewCfg()
cfg.RBACEnabled = false cfg.RBACEnabled = false
features := featuremgmt.WithFeatures() features := featuremgmt.WithFeatures()
cfg.IsFeatureToggleEnabled = features.IsEnabled cfg.IsFeatureToggleEnabled = features.IsEnabled
folderPermissions := acmock.NewMockedPermissionsService() folderPermissions := acmock.NewMockedPermissionsService()
dashboardPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService()
dashboardService := dashboardsvc.ProvideDashboardService(cfg, store, nil, features, folderPermissions, dashboardPermissions, acmock.New()) dashboardService := dashboardsvc.ProvideDashboardService(cfg, dashStore, nil, features, folderPermissions, dashboardPermissions, acmock.New())
service := &Service{ service := &Service{
cfg: cfg, cfg: cfg,
log: log.New("test-folder-service"), log: log.New("test-folder-service"),
dashboardService: dashboardService, dashboardService: dashboardService,
dashboardStore: store, dashboardStore: dashStore,
store: store,
searchService: nil, searchService: nil,
features: features, features: features,
permissions: folderPermissions, permissions: folderPermissions,
@@ -76,8 +83,8 @@ func TestIntegrationFolderService(t *testing.T) {
folder.Id = folderId folder.Id = folderId
folder.Uid = folderUID folder.Uid = folderUID
store.On("GetFolderByID", mock.Anything, orgID, folderId).Return(folder, nil) dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(folder, nil)
store.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(folder, nil) dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(folder, nil)
t.Run("When get folder by id should return access denied error", func(t *testing.T) { t.Run("When get folder by id should return access denied error", func(t *testing.T) {
_, err := service.GetFolderByID(context.Background(), usr, folderId, orgID) _, err := service.GetFolderByID(context.Background(), usr, folderId, orgID)
@@ -96,13 +103,13 @@ func TestIntegrationFolderService(t *testing.T) {
}) })
t.Run("When creating folder should return access denied error", func(t *testing.T) { t.Run("When creating folder should return access denied error", func(t *testing.T) {
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil).Times(2) dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil).Times(2)
_, err := service.CreateFolder(context.Background(), usr, orgID, folder.Title, folderUID) _, err := service.CreateFolder(context.Background(), usr, orgID, folder.Title, folderUID)
require.Equal(t, err, dashboards.ErrFolderAccessDenied) require.Equal(t, err, dashboards.ErrFolderAccessDenied)
}) })
t.Run("When updating folder should return access denied error", func(t *testing.T) { t.Run("When updating folder should return access denied error", func(t *testing.T) {
store.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) { dashStore.On("GetDashboard", mock.Anything, mock.AnythingOfType("*models.GetDashboardQuery")).Run(func(args mock.Arguments) {
folder := args.Get(1).(*models.GetDashboardQuery) folder := args.Get(1).(*models.GetDashboardQuery)
folder.Result = models.NewDashboard("dashboard-test") folder.Result = models.NewDashboard("dashboard-test")
folder.Result.IsFolder = true folder.Result.IsFolder = true
@@ -134,11 +141,11 @@ func TestIntegrationFolderService(t *testing.T) {
dash.Id = rand.Int63() dash.Id = rand.Int63()
f := models.DashboardToFolder(dash) f := models.DashboardToFolder(dash)
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
store.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once() dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dash, nil).Once()
store.On("GetFolderByID", mock.Anything, orgID, dash.Id).Return(f, nil) dashStore.On("GetFolderByID", mock.Anything, orgID, dash.Id).Return(f, nil)
actualFolder, err := service.CreateFolder(context.Background(), usr, orgID, dash.Title, "") actualFolder, err := service.CreateFolder(context.Background(), usr, orgID, dash.Title, "someuid")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, f, actualFolder) require.Equal(t, f, actualFolder)
}) })
@@ -157,9 +164,9 @@ func TestIntegrationFolderService(t *testing.T) {
dashboardFolder.Uid = util.GenerateShortUID() dashboardFolder.Uid = util.GenerateShortUID()
f := models.DashboardToFolder(dashboardFolder) f := models.DashboardToFolder(dashboardFolder)
store.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil) dashStore.On("ValidateDashboardBeforeSave", mock.Anything, mock.AnythingOfType("*models.Dashboard"), mock.AnythingOfType("bool")).Return(true, nil)
store.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dashboardFolder, nil) dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(dashboardFolder, nil)
store.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.Id).Return(f, nil) dashStore.On("GetFolderByID", mock.Anything, orgID, dashboardFolder.Id).Return(f, nil)
req := &models.UpdateFolderCommand{ req := &models.UpdateFolderCommand{
Uid: dashboardFolder.Uid, Uid: dashboardFolder.Uid,
@@ -175,10 +182,10 @@ func TestIntegrationFolderService(t *testing.T) {
f := models.NewFolder(util.GenerateShortUID()) f := models.NewFolder(util.GenerateShortUID())
f.Id = rand.Int63() f.Id = rand.Int63()
f.Uid = util.GenerateShortUID() f.Uid = util.GenerateShortUID()
store.On("GetFolderByUID", mock.Anything, orgID, f.Uid).Return(f, nil) dashStore.On("GetFolderByUID", mock.Anything, orgID, f.Uid).Return(f, nil)
var actualCmd *models.DeleteDashboardCommand var actualCmd *models.DeleteDashboardCommand
store.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
actualCmd = args.Get(1).(*models.DeleteDashboardCommand) actualCmd = args.Get(1).(*models.DeleteDashboardCommand)
}).Return(nil).Once() }).Return(nil).Once()
@@ -204,7 +211,7 @@ func TestIntegrationFolderService(t *testing.T) {
expected := models.NewFolder(util.GenerateShortUID()) expected := models.NewFolder(util.GenerateShortUID())
expected.Id = rand.Int63() expected.Id = rand.Int63()
store.On("GetFolderByID", mock.Anything, orgID, expected.Id).Return(expected, nil) dashStore.On("GetFolderByID", mock.Anything, orgID, expected.Id).Return(expected, nil)
actual, err := service.GetFolderByID(context.Background(), usr, expected.Id, orgID) actual, err := service.GetFolderByID(context.Background(), usr, expected.Id, orgID)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
@@ -215,7 +222,7 @@ func TestIntegrationFolderService(t *testing.T) {
expected := models.NewFolder(util.GenerateShortUID()) expected := models.NewFolder(util.GenerateShortUID())
expected.Uid = util.GenerateShortUID() expected.Uid = util.GenerateShortUID()
store.On("GetFolderByUID", mock.Anything, orgID, expected.Uid).Return(expected, nil) dashStore.On("GetFolderByUID", mock.Anything, orgID, expected.Uid).Return(expected, nil)
actual, err := service.GetFolderByUID(context.Background(), usr, orgID, expected.Uid) actual, err := service.GetFolderByUID(context.Background(), usr, orgID, expected.Uid)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
@@ -225,7 +232,7 @@ func TestIntegrationFolderService(t *testing.T) {
t.Run("When get folder by title should return folder", func(t *testing.T) { t.Run("When get folder by title should return folder", func(t *testing.T) {
expected := models.NewFolder("TEST-" + util.GenerateShortUID()) expected := models.NewFolder("TEST-" + util.GenerateShortUID())
store.On("GetFolderByTitle", mock.Anything, orgID, expected.Title).Return(expected, nil) dashStore.On("GetFolderByTitle", mock.Anything, orgID, expected.Title).Return(expected, nil)
actual, err := service.GetFolderByTitle(context.Background(), usr, orgID, expected.Title) actual, err := service.GetFolderByTitle(context.Background(), usr, orgID, expected.Title)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
@@ -326,3 +333,98 @@ func TestFolderService(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
}) })
} }
func TestCreate_NestedFolders(t *testing.T) {
t.Run("with feature flag unset", func(t *testing.T) {
ctx := appcontext.WithUser(context.Background(), usr)
store := &FakeStore{}
dashStore := dashboards.FakeDashboardStore{}
dashboardsvc := dashboards.FakeDashboardService{}
// nothing enabled yet
cfg := setting.NewCfg()
cfg.RBACEnabled = false
features := featuremgmt.WithFeatures()
cfg.IsFeatureToggleEnabled = features.IsEnabled
foldersvc := &Service{
cfg: cfg,
log: log.New("test-folder-service"),
dashboardService: &dashboardsvc,
dashboardStore: &dashStore,
store: store,
features: features,
}
// dashboard store & service commands that should be called.
dashboardsvc.On("BuildSaveDashboardCommand",
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
require.NoError(t, err)
// CreateFolder should not call the folder store create if the feature toggle is not enabled.
require.False(t, store.CreateCalled)
})
t.Run("with nested folder feature flag on", func(t *testing.T) {
ctx := appcontext.WithUser(context.Background(), usr)
store := &FakeStore{}
dashStore := &dashboards.FakeDashboardStore{}
dashboardsvc := &dashboards.FakeDashboardService{}
// nothing enabled yet
cfg := setting.NewCfg()
cfg.RBACEnabled = false
features := featuremgmt.WithFeatures("nestedFolders")
cfg.IsFeatureToggleEnabled = features.IsEnabled
foldersvc := &Service{
cfg: cfg,
log: log.New("test-folder-service"),
dashboardService: dashboardsvc,
dashboardStore: dashStore,
store: store,
features: features,
}
t.Run("create, no error", func(t *testing.T) {
// dashboard store & service commands that should be called.
dashboardsvc.On("BuildSaveDashboardCommand",
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
require.NoError(t, err)
// CreateFolder should also call the folder store's create method.
require.True(t, store.CreateCalled)
})
t.Run("create returns error from nested folder service", func(t *testing.T) {
// This test creates and deletes the dashboard, so needs some extra setup.
g := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{})
// dashboard store & service commands that should be called.
dashboardsvc.On("BuildSaveDashboardCommand",
mock.Anything, mock.AnythingOfType("*dashboards.SaveDashboardDTO"),
mock.AnythingOfType("bool"), mock.AnythingOfType("bool")).Return(&models.SaveDashboardCommand{}, nil)
dashStore.On("SaveDashboard", mock.Anything, mock.AnythingOfType("models.SaveDashboardCommand")).Return(&models.Dashboard{}, nil)
dashStore.On("GetFolderByID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("int64")).Return(&models.Folder{}, nil)
dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&models.Folder{}, nil)
// return an error from the folder store
store.ExpectedError = errors.New("FAILED")
// the service return success as long as the legacy create succeeds
_, err := foldersvc.CreateFolder(ctx, usr, orgID, "myFolder", "myFolder")
require.Error(t, err, "FAILED")
// CreateFolder should also call the folder store's create method.
require.True(t, store.CreateCalled)
t.Cleanup(func() {
guardian.New = g
})
})
})
}

View File

@@ -18,13 +18,13 @@ type sqlStore struct {
db db.DB db db.DB
log log.Logger log log.Logger
cfg *setting.Cfg cfg *setting.Cfg
fm featuremgmt.FeatureManager fm featuremgmt.FeatureToggles
} }
// sqlStore implements the store interface. // sqlStore implements the store interface.
var _ store = (*sqlStore)(nil) var _ store = (*sqlStore)(nil)
func ProvideStore(db db.DB, cfg *setting.Cfg, features featuremgmt.FeatureManager) *sqlStore { func ProvideStore(db db.DB, cfg *setting.Cfg, features featuremgmt.FeatureToggles) *sqlStore {
return &sqlStore{db: db, log: log.New("folder-store"), cfg: cfg, fm: features} return &sqlStore{db: db, log: log.New("folder-store"), cfg: cfg, fm: features}
} }

View File

@@ -6,6 +6,9 @@ import (
"testing" "testing"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
@@ -13,8 +16,6 @@ import (
"github.com/grafana/grafana/pkg/services/org/orgimpl" "github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestIntegrationCreate(t *testing.T) { func TestIntegrationCreate(t *testing.T) {
@@ -24,7 +25,7 @@ func TestIntegrationCreate(t *testing.T) {
t.Skip("skipping until folder migration is merged") t.Skip("skipping until folder migration is merged")
db := sqlstore.InitTestDB(t) db := sqlstore.InitTestDB(t)
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures()) folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
orgID := CreateOrg(t, db) orgID := CreateOrg(t, db)
@@ -162,7 +163,7 @@ func TestIntegrationDelete(t *testing.T) {
t.Skip("skipping until folder migration is merged") t.Skip("skipping until folder migration is merged")
db := sqlstore.InitTestDB(t) db := sqlstore.InitTestDB(t)
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures()) folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
orgID := CreateOrg(t, db) orgID := CreateOrg(t, db)
@@ -210,7 +211,7 @@ func TestIntegrationUpdate(t *testing.T) {
t.Skip("skipping until folder migration is merged") t.Skip("skipping until folder migration is merged")
db := sqlstore.InitTestDB(t) db := sqlstore.InitTestDB(t)
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures()) folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
orgID := CreateOrg(t, db) orgID := CreateOrg(t, db)
@@ -315,7 +316,7 @@ func TestIntegrationGet(t *testing.T) {
t.Skip("skipping until folder migration is merged") t.Skip("skipping until folder migration is merged")
db := sqlstore.InitTestDB(t) db := sqlstore.InitTestDB(t)
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures()) folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
orgID := CreateOrg(t, db) orgID := CreateOrg(t, db)
@@ -396,7 +397,7 @@ func TestIntegrationGetParents(t *testing.T) {
t.Skip("skipping until folder migration is merged") t.Skip("skipping until folder migration is merged")
db := sqlstore.InitTestDB(t) db := sqlstore.InitTestDB(t)
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures()) folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
orgID := CreateOrg(t, db) orgID := CreateOrg(t, db)
@@ -460,7 +461,7 @@ func TestIntegrationGetChildren(t *testing.T) {
t.Skip("skipping until folder migration is merged") t.Skip("skipping until folder migration is merged")
db := sqlstore.InitTestDB(t) db := sqlstore.InitTestDB(t)
folderStore := ProvideStore(db, db.Cfg, *featuremgmt.WithFeatures()) folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
orgID := CreateOrg(t, db) orgID := CreateOrg(t, db)

View File

@@ -10,6 +10,8 @@ type FakeStore struct {
ExpectedFolders []*folder.Folder ExpectedFolders []*folder.Folder
ExpectedFolder *folder.Folder ExpectedFolder *folder.Folder
ExpectedError error ExpectedError error
CreateCalled bool
} }
func NewFakeStore() *FakeStore { func NewFakeStore() *FakeStore {
@@ -19,6 +21,7 @@ func NewFakeStore() *FakeStore {
var _ store = (*FakeStore)(nil) var _ store = (*FakeStore)(nil)
func (f *FakeStore) Create(ctx context.Context, cmd folder.CreateFolderCommand) (*folder.Folder, error) { func (f *FakeStore) Create(ctx context.Context, cmd folder.CreateFolderCommand) (*folder.Folder, error) {
f.CreateCalled = true
return f.ExpectedFolder, f.ExpectedError return f.ExpectedFolder, f.ExpectedError
} }

View File

@@ -13,6 +13,7 @@ var ErrInternal = errutil.NewBase(errutil.StatusInternal, "folder.internal")
const ( const (
GeneralFolderUID = "general" GeneralFolderUID = "general"
RootFolderUID = ""
MaxNestedFolderDepth = 8 MaxNestedFolderDepth = 8
) )

View File

@@ -310,7 +310,7 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user user.S
cfg, dashboardStore, nil, cfg, dashboardStore, nil,
features, folderPermissions, dashboardPermissions, ac, features, folderPermissions, dashboardPermissions, ac,
) )
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, features, folderPermissions, nil) s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, nil, features, folderPermissions, nil)
t.Logf("Creating folder with title and UID %q", title) t.Logf("Creating folder with title and UID %q", title)
folder, err := s.CreateFolder(context.Background(), &user, user.OrgID, title, title) folder, err := s.CreateFolder(context.Background(), &user, user.OrgID, title, title)
require.NoError(t, err) require.NoError(t, err)
@@ -420,7 +420,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
service := LibraryElementService{ service := LibraryElementService{
Cfg: sqlStore.Cfg, Cfg: sqlStore.Cfg,
SQLStore: sqlStore, SQLStore: sqlStore,
folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardService, dashboardStore, features, folderPermissions, nil), folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), sqlStore.Cfg, dashboardService, dashboardStore, nil, features, folderPermissions, nil),
} }
usr := user.SignedInUser{ usr := user.SignedInUser{

View File

@@ -717,7 +717,7 @@ func createFolderWithACL(t *testing.T, sqlStore db.DB, title string, user *user.
dashboardPermissions := acmock.NewMockedPermissionsService() dashboardPermissions := acmock.NewMockedPermissionsService()
dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg)) dashboardStore := database.ProvideDashboardStore(sqlStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore, cfg))
d := dashboardservice.ProvideDashboardService(cfg, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac) d := dashboardservice.ProvideDashboardService(cfg, dashboardStore, nil, features, folderPermissions, dashboardPermissions, ac)
s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, features, folderPermissions, nil) s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, d, dashboardStore, nil, features, folderPermissions, nil)
t.Logf("Creating folder with title and UID %q", title) t.Logf("Creating folder with title and UID %q", title)
folder, err := s.CreateFolder(context.Background(), user, user.OrgID, title, title) folder, err := s.CreateFolder(context.Background(), user, user.OrgID, title, title)
@@ -819,7 +819,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo
cfg, dashboardStore, &alerting.DashAlertExtractorService{}, cfg, dashboardStore, &alerting.DashAlertExtractorService{},
features, folderPermissions, dashboardPermissions, ac, features, folderPermissions, dashboardPermissions, ac,
) )
folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardService, dashboardStore, features, folderPermissions, nil) folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), cfg, dashboardService, dashboardStore, nil, features, folderPermissions, nil)
elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService) elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService)
service := LibraryPanelService{ service := LibraryPanelService{

View File

@@ -89,7 +89,7 @@ func SetupTestEnv(tb testing.TB, baseInterval time.Duration) (*ngalert.AlertNG,
) )
bus := bus.ProvideBus(tracing.InitializeTracerForTest()) bus := bus.ProvideBus(tracing.InitializeTracerForTest())
folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardService, dashboardStore, features, folderPermissions, nil) folderService := folderimpl.ProvideService(ac, bus, cfg, dashboardService, dashboardStore, nil, features, folderPermissions, nil)
ng, err := ngalert.ProvideService( ng, err := ngalert.ProvideService(
cfg, &FakeFeatures{}, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, nil, cfg, &FakeFeatures{}, nil, nil, routing.NewRouteRegister(), sqlStore, nil, nil, nil, nil,