mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NestedFolders: Fix nested folder deletion (#63572)
--------- Co-authored-by: suntala <arati.rana@grafana.com> Co-authored-by: ying-jeanne <ying-jeanne@users.noreply.github.com> Co-authored-by: jeanne0731 <jeanne0731@users.noreply.github.com>
This commit is contained in:
parent
ec003d502b
commit
6974f4340b
@ -25,8 +25,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
store store
|
store store
|
||||||
|
db db.DB
|
||||||
log log.Logger
|
log log.Logger
|
||||||
cfg *setting.Cfg
|
cfg *setting.Cfg
|
||||||
dashboardStore dashboards.Store
|
dashboardStore dashboards.Store
|
||||||
@ -57,6 +57,7 @@ func ProvideService(
|
|||||||
features: features,
|
features: features,
|
||||||
accessControl: ac,
|
accessControl: ac,
|
||||||
bus: bus,
|
bus: bus,
|
||||||
|
db: db,
|
||||||
}
|
}
|
||||||
if features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
if features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
||||||
srv.DBMigration(db)
|
srv.DBMigration(db)
|
||||||
@ -423,33 +424,44 @@ func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) e
|
|||||||
if cmd.SignedInUser == nil {
|
if cmd.SignedInUser == nil {
|
||||||
return folder.ErrBadRequest.Errorf("missing signed in user")
|
return folder.ErrBadRequest.Errorf("missing signed in user")
|
||||||
}
|
}
|
||||||
|
result := []string{cmd.UID}
|
||||||
|
err := s.db.InTransaction(ctx, func(ctx context.Context) error {
|
||||||
|
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
||||||
|
subfolders, err := s.nestedFolderDelete(ctx, cmd)
|
||||||
|
|
||||||
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
|
if err != nil {
|
||||||
err := s.nestedFolderDelete(ctx, cmd)
|
logger.Error("the delete folder on folder table failed with err: ", "error", err)
|
||||||
if err != nil {
|
return err
|
||||||
logger.Error("the delete folder on folder table failed with err: ", "error", err)
|
}
|
||||||
return err
|
result = append(result, subfolders...)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, cmd.OrgID, cmd.UID)
|
for _, folder := range result {
|
||||||
if err != nil {
|
dashFolder, err := s.dashboardFolderStore.GetFolderByUID(ctx, cmd.OrgID, folder)
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
guard, err := guardian.NewByUID(ctx, dashFolder.UID, cmd.OrgID, cmd.SignedInUser)
|
guard, err := guardian.NewByUID(ctx, dashFolder.UID, cmd.OrgID, cmd.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if canSave, err := guard.CanDelete(); err != nil || !canSave {
|
if canSave, err := guard.CanDelete(); err != nil || !canSave {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return toFolderError(err)
|
return toFolderError(err)
|
||||||
|
}
|
||||||
|
return dashboards.ErrFolderAccessDenied
|
||||||
|
}
|
||||||
|
err = s.legacyDelete(ctx, cmd, dashFolder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return dashboards.ErrFolderAccessDenied
|
return nil
|
||||||
}
|
})
|
||||||
|
|
||||||
return s.legacyDelete(ctx, cmd, dashFolder)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderCommand, dashFolder *folder.Folder) error {
|
func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderCommand, dashFolder *folder.Folder) error {
|
||||||
@ -508,10 +520,14 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
|
// nestedFolderDelete inspects the folder referenced by the cmd argument, deletes all the entries for
|
||||||
|
// its descendant folders (folders which are nested within it either directly or indirectly) from
|
||||||
|
// the folder store and returns the UIDs for all its descendants.
|
||||||
|
func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFolderCommand) ([]string, error) {
|
||||||
logger := s.log.FromContext(ctx)
|
logger := s.log.FromContext(ctx)
|
||||||
|
result := []string{}
|
||||||
if cmd.SignedInUser == nil {
|
if cmd.SignedInUser == nil {
|
||||||
return folder.ErrBadRequest.Errorf("missing signed in user")
|
return result, folder.ErrBadRequest.Errorf("missing signed in user")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := s.Get(ctx, &folder.GetFolderQuery{
|
_, err := s.Get(ctx, &folder.GetFolderQuery{
|
||||||
@ -520,28 +536,30 @@ func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFold
|
|||||||
SignedInUser: cmd.SignedInUser,
|
SignedInUser: cmd.SignedInUser,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
folders, err := s.store.GetChildren(ctx, folder.GetChildrenQuery{UID: cmd.UID, OrgID: cmd.OrgID})
|
folders, err := s.store.GetChildren(ctx, folder.GetChildrenQuery{UID: cmd.UID, OrgID: cmd.OrgID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return result, err
|
||||||
}
|
}
|
||||||
for _, f := range folders {
|
for _, f := range folders {
|
||||||
|
result = append(result, f.UID)
|
||||||
logger.Info("deleting subfolder", "org_id", f.OrgID, "uid", f.UID)
|
logger.Info("deleting subfolder", "org_id", f.OrgID, "uid", f.UID)
|
||||||
err := s.nestedFolderDelete(ctx, &folder.DeleteFolderCommand{UID: f.UID, OrgID: f.OrgID, ForceDeleteRules: cmd.ForceDeleteRules, SignedInUser: cmd.SignedInUser})
|
subfolders, err := s.nestedFolderDelete(ctx, &folder.DeleteFolderCommand{UID: f.UID, OrgID: f.OrgID, ForceDeleteRules: cmd.ForceDeleteRules, SignedInUser: cmd.SignedInUser})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("failed deleting subfolder", "org_id", f.OrgID, "uid", f.UID, "error", err)
|
logger.Error("failed deleting subfolder", "org_id", f.OrgID, "uid", f.UID, "error", err)
|
||||||
return err
|
return result, err
|
||||||
}
|
}
|
||||||
|
result = append(result, subfolders...)
|
||||||
}
|
}
|
||||||
logger.Info("deleting folder", "org_id", cmd.OrgID, "uid", cmd.UID)
|
logger.Info("deleting folder", "org_id", cmd.OrgID, "uid", cmd.UID)
|
||||||
err = s.store.Delete(ctx, cmd.UID, cmd.OrgID)
|
err = s.store.Delete(ctx, cmd.UID, cmd.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Info("failed deleting folder", "org_id", cmd.OrgID, "uid", cmd.UID, "err", err)
|
logger.Info("failed deleting folder", "org_id", cmd.OrgID, "uid", cmd.UID, "err", err)
|
||||||
return err
|
return result, err
|
||||||
}
|
}
|
||||||
return nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeUserAdmin is copy of DashboardServiceImpl.MakeUserAdmin
|
// MakeUserAdmin is copy of DashboardServiceImpl.MakeUserAdmin
|
||||||
|
@ -12,17 +12,22 @@ 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/db"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/db/dbtest"
|
||||||
"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/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
|
"github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
"github.com/grafana/grafana/pkg/services/guardian"
|
||||||
|
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
|
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||||
"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"
|
||||||
@ -67,6 +72,7 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
store: nestedFolderStore,
|
store: nestedFolderStore,
|
||||||
features: features,
|
features: features,
|
||||||
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||||
|
db: db,
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("Given user has no permissions", func(t *testing.T) {
|
t.Run("Given user has no permissions", func(t *testing.T) {
|
||||||
@ -312,6 +318,113 @@ func TestIntegrationFolderService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntegrationDeleteNestedFolders(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping integration test")
|
||||||
|
}
|
||||||
|
db := sqlstore.InitTestDB(t)
|
||||||
|
quotaService := quotatest.New(false, nil)
|
||||||
|
folderStore := ProvideDashboardFolderStore(db)
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
|
||||||
|
featuresFlagOn := featuremgmt.WithFeatures("nestedFolders")
|
||||||
|
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOn, tagimpl.ProvideService(db, db.Cfg), quotaService)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOn)
|
||||||
|
|
||||||
|
serviceWithFlagOn := &Service{
|
||||||
|
cfg: cfg,
|
||||||
|
log: log.New("test-folder-service"),
|
||||||
|
dashboardStore: dashStore,
|
||||||
|
dashboardFolderStore: folderStore,
|
||||||
|
store: nestedFolderStore,
|
||||||
|
features: featuresFlagOn,
|
||||||
|
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
|
||||||
|
signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID}
|
||||||
|
createCmd := folder.CreateFolderCommand{
|
||||||
|
OrgID: orgID,
|
||||||
|
ParentUID: "",
|
||||||
|
SignedInUser: &signedInUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("With nested folder feature flag on", func(t *testing.T) {
|
||||||
|
origNewGuardian := guardian.New
|
||||||
|
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||||
|
|
||||||
|
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 3, "", createCmd)
|
||||||
|
|
||||||
|
deleteCmd := folder.DeleteFolderCommand{
|
||||||
|
UID: ancestorUIDs[0],
|
||||||
|
OrgID: orgID,
|
||||||
|
SignedInUser: &signedInUser,
|
||||||
|
}
|
||||||
|
err = serviceWithFlagOn.Delete(context.Background(), &deleteCmd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i, uid := range ancestorUIDs {
|
||||||
|
// dashboard table
|
||||||
|
_, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||||
|
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||||
|
// folder table
|
||||||
|
_, err = serviceWithFlagOn.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||||
|
require.ErrorIs(t, err, folder.ErrFolderNotFound)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
guardian.New = origNewGuardian
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("With feature flag unset", func(t *testing.T) {
|
||||||
|
featuresFlagOff := featuremgmt.WithFeatures()
|
||||||
|
dashStore, err := database.ProvideDashboardStore(db, db.Cfg, featuresFlagOff, tagimpl.ProvideService(db, db.Cfg), quotaService)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nestedFolderStore := ProvideStore(db, db.Cfg, featuresFlagOff)
|
||||||
|
|
||||||
|
serviceWithFlagOff := &Service{
|
||||||
|
cfg: cfg,
|
||||||
|
log: log.New("test-folder-service"),
|
||||||
|
dashboardStore: dashStore,
|
||||||
|
dashboardFolderStore: folderStore,
|
||||||
|
store: nestedFolderStore,
|
||||||
|
features: featuresFlagOff,
|
||||||
|
bus: bus.ProvideBus(tracing.InitializeTracerForTest()),
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
|
||||||
|
origNewGuardian := guardian.New
|
||||||
|
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
||||||
|
|
||||||
|
ancestorUIDs := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, 1, "", createCmd)
|
||||||
|
|
||||||
|
deleteCmd := folder.DeleteFolderCommand{
|
||||||
|
UID: ancestorUIDs[0],
|
||||||
|
OrgID: orgID,
|
||||||
|
SignedInUser: &signedInUser,
|
||||||
|
}
|
||||||
|
err = serviceWithFlagOff.Delete(context.Background(), &deleteCmd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i, uid := range ancestorUIDs {
|
||||||
|
// dashboard table
|
||||||
|
_, err := serviceWithFlagOff.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, uid)
|
||||||
|
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||||
|
// folder table
|
||||||
|
_, err = serviceWithFlagOff.store.Get(context.Background(), folder.GetFolderQuery{UID: &ancestorUIDs[i], OrgID: orgID})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
guardian.New = origNewGuardian
|
||||||
|
for _, uid := range ancestorUIDs {
|
||||||
|
err := serviceWithFlagOff.store.Delete(context.Background(), uid, orgID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestNestedFolderServiceFeatureToggle(t *testing.T) {
|
func TestNestedFolderServiceFeatureToggle(t *testing.T) {
|
||||||
g := guardian.New
|
g := guardian.New
|
||||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
|
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
|
||||||
@ -366,7 +479,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
nestedFolderStore := NewFakeStore()
|
nestedFolderStore := NewFakeStore()
|
||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures(), nil)
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures(), nil, dbtest.NewFakeDB())
|
||||||
_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
Title: "myFolder",
|
Title: "myFolder",
|
||||||
@ -377,26 +490,6 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
// CreateFolder should not call the folder store create if the feature toggle is not enabled.
|
// CreateFolder should not call the folder store create if the feature toggle is not enabled.
|
||||||
require.False(t, nestedFolderStore.CreateCalled)
|
require.False(t, nestedFolderStore.CreateCalled)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("When delete folder, no delete in folder table done", func(t *testing.T) {
|
|
||||||
var actualCmd *dashboards.DeleteDashboardCommand
|
|
||||||
dashStore := &dashboards.FakeDashboardStore{}
|
|
||||||
dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
|
||||||
actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand)
|
|
||||||
}).Return(nil).Once()
|
|
||||||
|
|
||||||
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
|
|
||||||
dashboardFolderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil)
|
|
||||||
|
|
||||||
nestedFolderStore := NewFakeStore()
|
|
||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures(), nil)
|
|
||||||
err := folderSvc.Delete(context.Background(), &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID, SignedInUser: usr})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, actualCmd)
|
|
||||||
|
|
||||||
require.False(t, nestedFolderStore.DeleteCalled)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("with nested folder feature flag on", func(t *testing.T) {
|
t.Run("with nested folder feature flag on", func(t *testing.T) {
|
||||||
@ -419,7 +512,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
Title: "myFolder",
|
Title: "myFolder",
|
||||||
@ -450,7 +543,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
f, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
f, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
Title: "myFolder",
|
Title: "myFolder",
|
||||||
@ -504,7 +597,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
_, err := folderSvc.Create(context.Background(), &cmd)
|
_, err := folderSvc.Create(context.Background(), &cmd)
|
||||||
require.Error(t, err, folder.ErrCircularReference)
|
require.Error(t, err, folder.ErrCircularReference)
|
||||||
// CreateFolder should not call the folder store's create method.
|
// CreateFolder should not call the folder store's create method.
|
||||||
@ -539,7 +632,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
// the service return success as long as the legacy create succeeds
|
// the service return success as long as the legacy create succeeds
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
Title: "myFolder",
|
Title: "myFolder",
|
||||||
@ -569,7 +662,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr})
|
_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr})
|
||||||
require.Error(t, err, dashboards.ErrFolderAccessDenied)
|
require.Error(t, err, dashboards.ErrFolderAccessDenied)
|
||||||
})
|
})
|
||||||
@ -590,7 +683,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr})
|
_, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr})
|
||||||
require.Error(t, err, dashboards.ErrFolderAccessDenied)
|
require.Error(t, err, dashboards.ErrFolderAccessDenied)
|
||||||
})
|
})
|
||||||
@ -616,7 +709,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr})
|
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, f)
|
require.NotNil(t, f)
|
||||||
@ -638,7 +731,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr})
|
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder", OrgID: orgID, SignedInUser: usr})
|
||||||
require.Error(t, err, folder.ErrCircularReference)
|
require.Error(t, err, folder.ErrCircularReference)
|
||||||
require.Nil(t, f)
|
require.Nil(t, f)
|
||||||
@ -664,7 +757,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr})
|
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr})
|
||||||
require.Error(t, err, folder.ErrMaximumDepthReached)
|
require.Error(t, err, folder.ErrMaximumDepthReached)
|
||||||
require.Nil(t, f)
|
require.Nil(t, f)
|
||||||
@ -686,41 +779,12 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr})
|
f, err := folderSvc.Move(context.Background(), &folder.MoveFolderCommand{UID: "myFolder", NewParentUID: "newFolder2", OrgID: orgID, SignedInUser: usr})
|
||||||
require.Error(t, err, folder.ErrCircularReference)
|
require.Error(t, err, folder.ErrCircularReference)
|
||||||
require.Nil(t, f)
|
require.Nil(t, f)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("delete with success", func(t *testing.T) {
|
|
||||||
g := guardian.New
|
|
||||||
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true, CanViewValue: true})
|
|
||||||
t.Cleanup(func() {
|
|
||||||
guardian.New = g
|
|
||||||
})
|
|
||||||
|
|
||||||
dashStore := &dashboards.FakeDashboardStore{}
|
|
||||||
var actualCmd *dashboards.DeleteDashboardCommand
|
|
||||||
dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
|
||||||
actualCmd = args.Get(1).(*dashboards.DeleteDashboardCommand)
|
|
||||||
}).Return(nil).Once()
|
|
||||||
|
|
||||||
dashboardFolderStore := foldertest.NewFakeFolderStore(t)
|
|
||||||
dashboardFolderStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&folder.Folder{}, nil)
|
|
||||||
|
|
||||||
nestedFolderStore := NewFakeStore()
|
|
||||||
nestedFolderStore.ExpectedFolder = &folder.Folder{UID: "myFolder", ParentUID: "newFolder"}
|
|
||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
|
||||||
ExpectedEvaluate: true,
|
|
||||||
})
|
|
||||||
err := folderSvc.Delete(context.Background(), &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID, SignedInUser: usr})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, actualCmd)
|
|
||||||
|
|
||||||
require.True(t, nestedFolderStore.DeleteCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("create returns error if maximum depth reached", func(t *testing.T) {
|
t.Run("create returns error if maximum depth reached", func(t *testing.T) {
|
||||||
// This test creates and deletes the dashboard, so needs some extra setup.
|
// This test creates and deletes the dashboard, so needs some extra setup.
|
||||||
g := guardian.New
|
g := guardian.New
|
||||||
@ -752,7 +816,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
_, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
||||||
Title: "folder",
|
Title: "folder",
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
@ -781,7 +845,7 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
|
|
||||||
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
folderSvc := setup(t, dashStore, dashboardFolderStore, nestedFolderStore, featuremgmt.WithFeatures("nestedFolders"), actest.FakeAccessControl{
|
||||||
ExpectedEvaluate: true,
|
ExpectedEvaluate: true,
|
||||||
})
|
}, dbtest.NewFakeDB())
|
||||||
_, err := folderSvc.Get(context.Background(), &folder.GetFolderQuery{
|
_, err := folderSvc.Get(context.Background(), &folder.GetFolderQuery{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
ID: &folder.GeneralFolder.ID,
|
ID: &folder.GeneralFolder.ID,
|
||||||
@ -792,7 +856,44 @@ func TestNestedFolderService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder.FolderStore, nestedFolderStore store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service {
|
func CreateSubtreeInStore(t *testing.T, store *sqlStore, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
ancestorUIDs := []string{}
|
||||||
|
if cmd.ParentUID != "" {
|
||||||
|
ancestorUIDs = append(ancestorUIDs, cmd.ParentUID)
|
||||||
|
}
|
||||||
|
for i := 0; i < depth; i++ {
|
||||||
|
title := fmt.Sprintf("%sfolder-%d", prefix, i)
|
||||||
|
cmd.Title = title
|
||||||
|
cmd.UID = util.GenerateShortUID()
|
||||||
|
|
||||||
|
f, err := service.Create(context.Background(), &cmd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, title, f.Title)
|
||||||
|
require.NotEmpty(t, f.ID)
|
||||||
|
require.NotEmpty(t, f.UID)
|
||||||
|
|
||||||
|
parents, err := store.GetParents(context.Background(), folder.GetParentsQuery{
|
||||||
|
UID: f.UID,
|
||||||
|
OrgID: cmd.OrgID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
parentUIDs := []string{}
|
||||||
|
for _, p := range parents {
|
||||||
|
parentUIDs = append(parentUIDs, p.UID)
|
||||||
|
}
|
||||||
|
require.Equal(t, ancestorUIDs, parentUIDs)
|
||||||
|
|
||||||
|
ancestorUIDs = append(ancestorUIDs, f.UID)
|
||||||
|
|
||||||
|
cmd.ParentUID = f.UID
|
||||||
|
}
|
||||||
|
|
||||||
|
return ancestorUIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder.FolderStore, nestedFolderStore store, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl, db db.DB) folder.Service {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// nothing enabled yet
|
// nothing enabled yet
|
||||||
@ -806,5 +907,6 @@ func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder
|
|||||||
store: nestedFolderStore,
|
store: nestedFolderStore,
|
||||||
features: features,
|
features: features,
|
||||||
accessControl: ac,
|
accessControl: ac,
|
||||||
|
db: db,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ func TestIntegrationDelete(t *testing.T) {
|
|||||||
})
|
})
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ancestorUIDs := CreateSubTree(t, folderStore, orgID, "", folder.MaxNestedFolderDepth, "")
|
ancestorUIDs := CreateSubtree(t, folderStore, orgID, "", folder.MaxNestedFolderDepth, "")
|
||||||
require.Len(t, ancestorUIDs, folder.MaxNestedFolderDepth)
|
require.Len(t, ancestorUIDs, folder.MaxNestedFolderDepth)
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
@ -603,8 +603,7 @@ func TestIntegrationGetHeight(t *testing.T) {
|
|||||||
UID: uid1,
|
UID: uid1,
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
subTree := CreateSubTree(t, folderStore, orgID, parent.UID, 4, "sub")
|
subTree := CreateSubtree(t, folderStore, orgID, parent.UID, 4, "sub")
|
||||||
|
|
||||||
t.Run("should successfully get height", func(t *testing.T) {
|
t.Run("should successfully get height", func(t *testing.T) {
|
||||||
height, err := folderStore.GetHeight(context.Background(), parent.UID, orgID, nil)
|
height, err := folderStore.GetHeight(context.Background(), parent.UID, orgID, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -632,7 +631,7 @@ func CreateOrg(t *testing.T, db *sqlstore.SQLStore) int64 {
|
|||||||
return orgID
|
return orgID
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateSubTree(t *testing.T, store *sqlStore, orgID int64, parentUID string, depth int, prefix string) []string {
|
func CreateSubtree(t *testing.T, store *sqlStore, orgID int64, parentUID string, depth int, prefix string) []string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
ancestorUIDs := []string{}
|
ancestorUIDs := []string{}
|
||||||
|
Loading…
Reference in New Issue
Block a user