mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Nested folders: Fix moving folder under root (#65684)
* Nested folders: Fix moving folder under root * Add store test for not empty parent after update * Modify folder and document store update implementation Move folder only if NewParentUID is not nil * Apply suggestion from code review
This commit is contained in:
parent
ac09372abf
commit
a270188f0c
@ -543,10 +543,14 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newParentUID := ""
|
||||||
|
if cmd.NewParentUID != "" {
|
||||||
|
newParentUID = cmd.NewParentUID
|
||||||
|
}
|
||||||
return s.store.Update(ctx, folder.UpdateFolderCommand{
|
return s.store.Update(ctx, folder.UpdateFolderCommand{
|
||||||
UID: cmd.UID,
|
UID: cmd.UID,
|
||||||
OrgID: cmd.OrgID,
|
OrgID: cmd.OrgID,
|
||||||
NewParentUID: &cmd.NewParentUID,
|
NewParentUID: &newParentUID,
|
||||||
SignedInUser: cmd.SignedInUser,
|
SignedInUser: cmd.SignedInUser,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,10 @@ func (ss *sqlStore) Update(ctx context.Context, cmd folder.UpdateFolderCommand)
|
|||||||
uid := cmd.UID
|
uid := cmd.UID
|
||||||
|
|
||||||
var foldr *folder.Folder
|
var foldr *folder.Folder
|
||||||
|
|
||||||
|
if cmd.NewDescription == nil && cmd.NewTitle == nil && cmd.NewUID == nil && cmd.NewParentUID == nil {
|
||||||
|
return nil, folder.ErrBadRequest.Errorf("nothing to update")
|
||||||
|
}
|
||||||
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
err := ss.db.WithDbSession(ctx, func(sess *db.Session) error {
|
||||||
sql := strings.Builder{}
|
sql := strings.Builder{}
|
||||||
sql.Write([]byte("UPDATE folder SET "))
|
sql.Write([]byte("UPDATE folder SET "))
|
||||||
@ -114,8 +118,12 @@ func (ss *sqlStore) Update(ctx context.Context, cmd folder.UpdateFolderCommand)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cmd.NewParentUID != nil {
|
if cmd.NewParentUID != nil {
|
||||||
columnsToUpdate = append(columnsToUpdate, "parent_uid = ?")
|
if *cmd.NewParentUID == "" {
|
||||||
args = append(args, *cmd.NewParentUID)
|
columnsToUpdate = append(columnsToUpdate, "parent_uid = NULL")
|
||||||
|
} else {
|
||||||
|
columnsToUpdate = append(columnsToUpdate, "parent_uid = ?")
|
||||||
|
args = append(args, *cmd.NewParentUID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(columnsToUpdate) == 0 {
|
if len(columnsToUpdate) == 0 {
|
||||||
|
@ -27,7 +27,7 @@ func TestIntegrationCreate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
folderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ func TestIntegrationDelete(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
folderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@ -189,18 +189,30 @@ func TestIntegrationUpdate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
folderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
// create folder
|
// create parent folder
|
||||||
f, err := folderStore.Create(context.Background(), folder.CreateFolderCommand{
|
parent, err := folderStore.Create(context.Background(), folder.CreateFolderCommand{
|
||||||
Title: folderTitle,
|
Title: folderTitle,
|
||||||
Description: folderDsc,
|
Description: folderDsc,
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
UID: util.GenerateShortUID(),
|
UID: util.GenerateShortUID(),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create subfolder
|
||||||
|
f, err := folderStore.Create(context.Background(), folder.CreateFolderCommand{
|
||||||
|
Title: folderTitle,
|
||||||
|
Description: folderDsc,
|
||||||
|
OrgID: orgID,
|
||||||
|
UID: util.GenerateShortUID(),
|
||||||
|
ParentUID: parent.UID,
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, f.ParentUID, parent.UID)
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
err := folderStore.Delete(context.Background(), f.UID, orgID)
|
err := folderStore.Delete(context.Background(), f.UID, orgID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -250,6 +262,7 @@ func TestIntegrationUpdate(t *testing.T) {
|
|||||||
assert.Equal(t, f.UID, updated.UID)
|
assert.Equal(t, f.UID, updated.UID)
|
||||||
assert.Equal(t, newTitle, updated.Title)
|
assert.Equal(t, newTitle, updated.Title)
|
||||||
assert.Equal(t, newDesc, updated.Description)
|
assert.Equal(t, newDesc, updated.Description)
|
||||||
|
assert.Equal(t, parent.UID, updated.ParentUID)
|
||||||
// assert.GreaterOrEqual(t, updated.Updated.UnixNano(), existingUpdated.UnixNano())
|
// assert.GreaterOrEqual(t, updated.Updated.UnixNano(), existingUpdated.UnixNano())
|
||||||
|
|
||||||
updated, err = folderStore.Get(context.Background(), folder.GetFolderQuery{
|
updated, err = folderStore.Get(context.Background(), folder.GetFolderQuery{
|
||||||
@ -259,6 +272,8 @@ func TestIntegrationUpdate(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, newTitle, updated.Title)
|
assert.Equal(t, newTitle, updated.Title)
|
||||||
assert.Equal(t, newDesc, updated.Description)
|
assert.Equal(t, newDesc, updated.Description)
|
||||||
|
// parent should not change
|
||||||
|
assert.Equal(t, parent.UID, updated.ParentUID)
|
||||||
|
|
||||||
f = updated
|
f = updated
|
||||||
})
|
})
|
||||||
@ -285,6 +300,82 @@ func TestIntegrationUpdate(t *testing.T) {
|
|||||||
assert.Equal(t, existingTitle, updated.Title)
|
assert.Equal(t, existingTitle, updated.Title)
|
||||||
assert.Equal(t, existingDesc, updated.Description)
|
assert.Equal(t, existingDesc, updated.Description)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("updating folder parent UID", func(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
reqNewParentUID *string
|
||||||
|
expectedError error
|
||||||
|
expectedParentUIDFunc func(existing string) string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "should succeed when moving to other folder",
|
||||||
|
reqNewParentUID: util.Pointer("new"),
|
||||||
|
expectedParentUIDFunc: func(_ string) string { return "new" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should succeed when moving to root folder (NewParentUID is empty)",
|
||||||
|
reqNewParentUID: util.Pointer(""),
|
||||||
|
expectedParentUIDFunc: func(_ string) string { return "" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "should do nothing when NewParentUID is nil",
|
||||||
|
reqNewParentUID: nil,
|
||||||
|
expectedError: folder.ErrBadRequest,
|
||||||
|
expectedParentUIDFunc: func(existingParentUID string) string { return existingParentUID },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.desc, func(t *testing.T) {
|
||||||
|
// create parent folder
|
||||||
|
parentUID := util.GenerateShortUID()
|
||||||
|
_, err := folderStore.Create(context.Background(), folder.CreateFolderCommand{
|
||||||
|
Title: "parent",
|
||||||
|
Description: "parent",
|
||||||
|
OrgID: orgID,
|
||||||
|
UID: parentUID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// create subfolder
|
||||||
|
UID := util.GenerateShortUID()
|
||||||
|
f, err = folderStore.Create(context.Background(), folder.CreateFolderCommand{
|
||||||
|
Title: "subfolder",
|
||||||
|
Description: "subfolder",
|
||||||
|
OrgID: orgID,
|
||||||
|
UID: UID,
|
||||||
|
ParentUID: parentUID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
existingTitle := f.Title
|
||||||
|
existingDesc := f.Description
|
||||||
|
existingUID := f.UID
|
||||||
|
updated, err := folderStore.Update(context.Background(), folder.UpdateFolderCommand{
|
||||||
|
UID: f.UID,
|
||||||
|
OrgID: f.OrgID,
|
||||||
|
NewParentUID: tc.reqNewParentUID,
|
||||||
|
})
|
||||||
|
if tc.expectedError == nil {
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expectedParentUIDFunc(parentUID), updated.ParentUID)
|
||||||
|
} else {
|
||||||
|
assert.ErrorIs(t, err, tc.expectedError)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, err = folderStore.Get(context.Background(), folder.GetFolderQuery{
|
||||||
|
UID: &f.UID,
|
||||||
|
OrgID: orgID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, tc.expectedParentUIDFunc(parentUID), updated.ParentUID)
|
||||||
|
assert.Equal(t, existingTitle, updated.Title)
|
||||||
|
assert.Equal(t, existingDesc, updated.Description)
|
||||||
|
assert.Equal(t, existingUID, updated.UID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationGet(t *testing.T) {
|
func TestIntegrationGet(t *testing.T) {
|
||||||
@ -293,7 +384,7 @@ func TestIntegrationGet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
folderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@ -371,7 +462,7 @@ func TestIntegrationGetParents(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
folderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@ -390,7 +481,7 @@ func TestIntegrationGetParents(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("get parents of root folder should should be empty", func(t *testing.T) {
|
t.Run("get parents of root folder should be empty", func(t *testing.T) {
|
||||||
parents, err := folderStore.GetParents(context.Background(), folder.GetParentsQuery{})
|
parents, err := folderStore.GetParents(context.Background(), folder.GetParentsQuery{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, parents)
|
require.Empty(t, parents)
|
||||||
@ -438,7 +529,7 @@ func TestIntegrationGetChildren(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
folderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
@ -591,7 +682,7 @@ func TestIntegrationGetHeight(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db := sqlstore.InitTestDB(t)
|
db := sqlstore.InitTestDB(t)
|
||||||
folderStore := ProvideStore(db, db.Cfg, &featuremgmt.FeatureManager{})
|
folderStore := ProvideStore(db, db.Cfg, featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders))
|
||||||
|
|
||||||
orgID := CreateOrg(t, db)
|
orgID := CreateOrg(t, db)
|
||||||
|
|
||||||
|
@ -14,8 +14,10 @@ type store interface {
|
|||||||
// Delete deletes a folder from the folder store.
|
// Delete deletes a folder from the folder store.
|
||||||
Delete(ctx context.Context, uid string, orgID int64) error
|
Delete(ctx context.Context, uid string, orgID int64) error
|
||||||
|
|
||||||
// Update updates the given folder's UID, Title, and Description.
|
// Update updates the given folder's UID, Title, and Description (update mode).
|
||||||
// Use Move to change a dashboard's parent ID.
|
// If the NewParentUID field is not nil, it updates also the parent UID (move mode).
|
||||||
|
// If it's a non empty string, it moves the folder under the folder with the specific UID
|
||||||
|
// otherwise, it moves the folder under the root folder (parent_uid column is set to NULL).
|
||||||
Update(ctx context.Context, cmd folder.UpdateFolderCommand) (*folder.Folder, error)
|
Update(ctx context.Context, cmd folder.UpdateFolderCommand) (*folder.Folder, error)
|
||||||
|
|
||||||
// Get returns a folder.
|
// Get returns a folder.
|
||||||
|
Loading…
Reference in New Issue
Block a user