[Nested Folder] Delete folder methode (#58444)

* transfer DeleteFolder changes from larger PR

* finish some thingies

* add the simplest delete logics

* some intermedia steps

* fix tests

* add test

* fix some comments

Co-authored-by: yangkb09 <yangkb09@gmail.com>
This commit is contained in:
ying-jeanne
2022-11-10 09:42:32 +01:00
committed by GitHub
parent 1201170724
commit accb4dea55
10 changed files with 139 additions and 70 deletions

View File

@@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/events"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
@@ -232,7 +233,8 @@ func (s *Service) CreateFolder(ctx context.Context, user *user.SignedInUser, org
// 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)
err = s.DeleteFolder(ctx, &folder.DeleteFolderCommand{UID: createdFolder.Uid, OrgID: orgID, ForceDeleteRules: true})
if err != nil {
s.log.Error("error deleting folder after failed save to nested folder store", err)
}
@@ -298,27 +300,37 @@ func (s *Service) UpdateFolder(ctx context.Context, user *user.SignedInUser, org
return nil
}
func (s *Service) DeleteFolder(ctx context.Context, user *user.SignedInUser, orgID int64, uid string, forceDeleteRules bool) (*models.Folder, error) {
dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, orgID, uid)
func (s *Service) DeleteFolder(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
if s.features.IsEnabled(featuremgmt.FlagNestedFolders) {
err := s.Delete(ctx, cmd)
if err != nil {
s.log.Error("the delete folder on folder table failed with err: ", err.Error())
}
}
user, err := appcontext.User(ctx)
if err != nil {
return nil, err
return err
}
guard := guardian.New(ctx, dashFolder.Id, orgID, user)
dashFolder, err := s.dashboardStore.GetFolderByUID(ctx, cmd.OrgID, cmd.UID)
if err != nil {
return err
}
guard := guardian.New(ctx, dashFolder.Id, cmd.OrgID, user)
if canSave, err := guard.CanDelete(); err != nil || !canSave {
if err != nil {
return nil, toFolderError(err)
return toFolderError(err)
}
return nil, dashboards.ErrFolderAccessDenied
return dashboards.ErrFolderAccessDenied
}
deleteCmd := models.DeleteDashboardCommand{OrgId: orgID, Id: dashFolder.Id, ForceDeleteFolderRules: forceDeleteRules}
deleteCmd := models.DeleteDashboardCommand{OrgId: cmd.OrgID, Id: dashFolder.Id, ForceDeleteFolderRules: cmd.ForceDeleteRules}
if err := s.dashboardStore.DeleteDashboard(ctx, &deleteCmd); err != nil {
return nil, toFolderError(err)
return toFolderError(err)
}
return dashFolder, nil
return nil
}
func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) (*folder.Folder, error) {
@@ -354,23 +366,30 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
})
}
func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) (*folder.Folder, error) {
// check the flag, if old - do whatever did before
// for new only the store
// check if dashboard exists
foldr, err := s.Get(ctx, &folder.GetFolderQuery{
func (s *Service) Delete(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
_, err := s.Get(ctx, &folder.GetFolderQuery{
UID: &cmd.UID,
OrgID: cmd.OrgID,
})
if err != nil {
return nil, err
return err
}
folders, err := s.store.GetChildren(ctx, folder.GetTreeQuery{UID: cmd.UID, OrgID: cmd.OrgID})
if err != nil {
return err
}
for _, f := range folders {
err := s.Delete(ctx, &folder.DeleteFolderCommand{UID: f.UID, OrgID: f.OrgID, ForceDeleteRules: cmd.ForceDeleteRules})
if err != nil {
return err
}
}
err = s.store.Delete(ctx, cmd.UID, cmd.OrgID)
if err != nil {
return nil, err
return err
}
return foldr, nil
return nil
}
func (s *Service) Get(ctx context.Context, cmd *folder.GetFolderQuery) (*folder.Folder, error) {

View File

@@ -28,7 +28,7 @@ import (
)
var orgID = int64(1)
var usr = &user.SignedInUser{UserID: 1}
var usr = &user.SignedInUser{UserID: 1, OrgID: orgID}
func TestIntegrationProvideFolderService(t *testing.T) {
if testing.Short() {
@@ -79,12 +79,12 @@ func TestIntegrationFolderService(t *testing.T) {
folderId := rand.Int63()
folderUID := util.GenerateShortUID()
folder := models.NewFolder("Folder")
folder.Id = folderId
folder.Uid = folderUID
newFolder := models.NewFolder("Folder")
newFolder.Id = folderId
newFolder.Uid = folderUID
dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(folder, nil)
dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(folder, nil)
dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(newFolder, nil)
dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(newFolder, nil)
t.Run("When get folder by id should return access denied error", func(t *testing.T) {
_, err := service.GetFolderByID(context.Background(), usr, folderId, orgID)
@@ -104,7 +104,7 @@ func TestIntegrationFolderService(t *testing.T) {
t.Run("When creating folder should return access denied error", func(t *testing.T) {
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, newFolder.Title, folderUID)
require.Equal(t, err, dashboards.ErrFolderAccessDenied)
})
@@ -122,7 +122,20 @@ func TestIntegrationFolderService(t *testing.T) {
})
t.Run("When deleting folder by uid should return access denied error", func(t *testing.T) {
_, err := service.DeleteFolder(context.Background(), usr, orgID, folderUID, false)
ctx := context.Background()
ctx = appcontext.WithUser(ctx, usr)
newFolder := models.NewFolder("Folder")
newFolder.Uid = folderUID
dashStore.On("GetFolderByID", mock.Anything, orgID, folderId).Return(newFolder, nil)
dashStore.On("GetFolderByUID", mock.Anything, orgID, folderUID).Return(newFolder, nil)
err := service.DeleteFolder(ctx, &folder.DeleteFolderCommand{
UID: folderUID,
OrgID: orgID,
ForceDeleteRules: false,
})
require.Error(t, err)
require.Equal(t, err, dashboards.ErrFolderAccessDenied)
})
@@ -190,7 +203,13 @@ func TestIntegrationFolderService(t *testing.T) {
}).Return(nil).Once()
expectedForceDeleteRules := rand.Int63()%2 == 0
_, err := service.DeleteFolder(context.Background(), usr, orgID, f.Uid, expectedForceDeleteRules)
ctx := context.Background()
ctx = appcontext.WithUser(ctx, usr)
err := service.DeleteFolder(ctx, &folder.DeleteFolderCommand{
UID: f.Uid,
OrgID: orgID,
ForceDeleteRules: expectedForceDeleteRules,
})
require.NoError(t, err)
require.NotNil(t, actualCmd)
require.Equal(t, f.Id, actualCmd.Id)
@@ -288,7 +307,7 @@ func TestFolderService(t *testing.T) {
t.Run("delete folder", func(t *testing.T) {
folderStore.ExpectedFolder = &folder.Folder{}
_, err := folderService.Delete(context.Background(), &folder.DeleteFolderCommand{})
err := folderService.Delete(context.Background(), &folder.DeleteFolderCommand{})
require.NoError(t, err)
})
@@ -334,7 +353,7 @@ func TestFolderService(t *testing.T) {
})
}
func TestCreate_NestedFolders(t *testing.T) {
func TestNestedFolderService(t *testing.T) {
t.Run("with feature flag unset", func(t *testing.T) {
ctx := appcontext.WithUser(context.Background(), usr)
store := &FakeStore{}
@@ -354,17 +373,39 @@ func TestCreate_NestedFolders(t *testing.T) {
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)
t.Run("When create folder, no create in folder table done", 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 not call the folder store create if the feature toggle is not enabled.
require.False(t, store.CreateCalled)
_, 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("When delete folder, no delete in folder table done", func(t *testing.T) {
var actualCmd *models.DeleteDashboardCommand
dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
actualCmd = args.Get(1).(*models.DeleteDashboardCommand)
}).Return(nil).Once()
dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&models.Folder{}, nil)
g := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
err := foldersvc.DeleteFolder(ctx, &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID})
require.NoError(t, err)
require.NotNil(t, actualCmd)
t.Cleanup(func() {
guardian.New = g
})
require.False(t, store.DeleteCalled)
})
})
t.Run("with nested folder feature flag on", func(t *testing.T) {
@@ -426,5 +467,27 @@ func TestCreate_NestedFolders(t *testing.T) {
guardian.New = g
})
})
t.Run("delete with success", func(t *testing.T) {
var actualCmd *models.DeleteDashboardCommand
dashStore.On("DeleteDashboard", mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
actualCmd = args.Get(1).(*models.DeleteDashboardCommand)
}).Return(nil).Once()
dashStore.On("GetFolderByUID", mock.Anything, mock.AnythingOfType("int64"), mock.AnythingOfType("string")).Return(&models.Folder{}, nil)
g := guardian.New
guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanSaveValue: true})
store.ExpectedError = nil
err := foldersvc.DeleteFolder(ctx, &folder.DeleteFolderCommand{UID: "myFolder", OrgID: orgID})
require.NoError(t, err)
require.NotNil(t, actualCmd)
t.Cleanup(func() {
guardian.New = g
})
require.True(t, store.DeleteCalled)
})
})
}

View File

@@ -79,15 +79,6 @@ func (ss *sqlStore) Delete(ctx context.Context, uid string, orgID int64) error {
if err != nil {
return folder.ErrDatabaseError.Errorf("failed to delete folder: %w", err)
}
/*
affected, err := res.RowsAffected()
if err != nil {
return folder.ErrDatabaseError.Errorf("failed to get affected rows: %w", err)
}
if affected == 0 {
return folder.ErrFolderNotFound.Errorf("folder not found uid:%s org_id:%d", uid, orgID)
}
*/
return nil
})
}
@@ -161,7 +152,6 @@ func (ss *sqlStore) Get(ctx context.Context, q folder.GetFolderQuery) (*folder.F
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
exists := false
var err error
switch {
case q.ID != nil:
exists, err = sess.SQL("SELECT * FROM folder WHERE id = ?", q.ID).Get(foldr)

View File

@@ -12,6 +12,7 @@ type FakeStore struct {
ExpectedError error
CreateCalled bool
DeleteCalled bool
}
func NewFakeStore() *FakeStore {
@@ -26,6 +27,7 @@ func (f *FakeStore) Create(ctx context.Context, cmd folder.CreateFolderCommand)
}
func (f *FakeStore) Delete(ctx context.Context, uid string, orgID int64) error {
f.DeleteCalled = true
return f.ExpectedError
}