Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NestedFolders: Fix nested folder deletion #63572

Merged
merged 16 commits into from Mar 15, 2023
Merged

NestedFolders: Fix nested folder deletion #63572

merged 16 commits into from Mar 15, 2023

Conversation

ying-jeanne
Copy link
Contributor

@ying-jeanne ying-jeanne commented Feb 22, 2023

What is this feature?

It's a fix for a bug relating to the deletion of nested folders as described in the linked issue.

Which issue(s) does this PR fix?:

Fixes #63080

Special notes for your reviewer:
I (@suntala) still need to try out the fix by reproducing the issue, but hopefully the new integration test (TestIntegrationDeleteNestedFolders) makes it clear that the fix works. This integration test has also been intended as a replacement for two test cases (1, 2).

Additionally, I'd be curious to know if anyone has suggestions for how to better test deletion when the feature flag is off (old way, new way). The new approach is to create a folder with the feature flag on so that it gets added to both the dashboard table and the folder one. Then we proceed with deletion and check that the entry in the folder table hasn't been removed.

When coming up with this approach, a corner case for this bug became apparent. Someone could theoretically turn the nested folder feature flag on, create some nested folders, turn the flag off and then delete the parent folder. This deletion wouldn't be complete. It would leave behind entries in the folder table. A fix for this would be to modify the legacyDelete method to check the folder table for nested folders when performing a deletion. I didn't implement this because hopefully people wouldn't use the nested folder flag in this way. However, please let me know if you think it would be valuable to do so.

I suggest not going through the commits individually because they aren't well structured and there was a change of approach halfway through.

@suntala suntala force-pushed the bugfixingdelete branch 5 times, most recently from ec85cec to 41fb115 Compare March 2, 2023 17:12
@suntala suntala marked this pull request as ready for review March 2, 2023 17:48
@suntala suntala requested review from a team as code owners March 2, 2023 17:48
@suntala suntala requested review from sakjur, papagian and mildwonkey and removed request for a team March 2, 2023 17:48
@suntala suntala changed the title bug fixing delete Fix nested folder deletion Mar 2, 2023
@suntala suntala added no-backport Skip backport of PR no-changelog Skip including change in changelog/release notes labels Mar 2, 2023
@suntala suntala added this to the 9.5.0 milestone Mar 2, 2023
@suntala suntala changed the title Fix nested folder deletion NestedFolders: Fix nested folder deletion Mar 2, 2023
Copy link
Contributor

@papagian papagian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested and works as expected! Great work 🎉
I have left some minor suggestions.

How to better handle the corner case - deleting a parent folder (containing subfolders) with nested folders disabled - described by @suntala still an open question though. I'm tempted to suggest a common approach regardless of the feature flag as long as we discover nested folders.

@@ -2008,7 +2008,7 @@
"zipkindataquery",
"zipkindatasourcecfg"
],
"count": 66
"count": 67
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change seems irrelevant; do we actually need it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of the pipeline steps was failing, the one called verify-gen-cue. There were instructions to run make gen-cue (if I remember correctly) and add the resulting changes to source control. I see that, in the meantime, someone else added the change. I'm going to go ahead and rebase on main to eliminate this change.

In the future, I think a better way of handling it would have been for me to create a separate PR for this issue and rebase this PR on top of it. Happy to hear if you have another suggestion though.

Comment on lines 248 to 249
_, err := serviceWithFlagOn.getFolderByUID(context.Background(), orgID, uid)
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this assumes prior knowledge of the folder service getFolderByUID() internal implementation (that it searches in the dashboard store); it would be preferable to call the serviceWithFlagOn.dashboardFolderStore.GetFolderByUID() or directly folderStore.GetFolderByUID() (which is passed to the service)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do require that the dashboard store be searched and not some other store. To make sure I understand you fully, it's not the prior knowledge of which store is being searched that is of concern here. Rather it is the knowledge of the internal implementation. Is that correct? When using serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(), we don't require internal knowledge of which store is being searched because we know that dashboardFolderStore is dedicated to the dashboard store.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, here we want to test if there is no entry in the dashboard table but it requires knowledge of the folder service getFolderByUID() internal implementation to know that it checks the specific store and raise the specific error if no entry is not found there.

@@ -508,10 +520,11 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol
})
}

func (s *Service) nestedFolderDelete(ctx context.Context, cmd *folder.DeleteFolderCommand) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit; it would be nice to have a doc string describing what the function returns

@suntala
Copy link
Contributor

suntala commented Mar 7, 2023

Hi @papagian, thanks a lot for the review. All your comments make sense to me and I learned some things along the way. I have made the suggested changes.

I'm tempted to suggest a common approach regardless of the feature flag as long as we discover nested folders.

This does seem like the way to go for me as well. I propose tackling it as a separate PR to not make this one bigger. WDYT?

@suntala suntala force-pushed the bugfixingdelete branch 2 times, most recently from 7156f4c to 9a023c3 Compare March 7, 2023 08:54
Copy link
Contributor

@papagian papagian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some additional - minor - suggestions

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Suggested change
// the folder store and returns the UIDs for all its descendants.
// the folder store and returns the UIDs for all its direct descendants.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can tell, because nestedFolderDelete is recursive, it returns the UIDs of all its descendants, both direct and indirect. We're going progressively deeper into the nested folders and appending the UIDs of all the subfolders we find:

for _, f := range folders {
result = append(result, f.UID)
logger.Info("deleting subfolder", "org_id", f.OrgID, "uid", f.UID)
subfolders, err := s.nestedFolderDelete(ctx, &folder.DeleteFolderCommand{UID: f.UID, OrgID: f.OrgID, ForceDeleteRules: cmd.ForceDeleteRules, SignedInUser: cmd.SignedInUser})
if err != nil {
logger.Error("failed deleting subfolder", "org_id", f.OrgID, "uid", f.UID, "error", err)
return result, err
}
result = append(result, subfolders...)

Please let me know if I'm missing something though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, I had in mind only the first iteration

@@ -183,6 +196,113 @@ func TestIntegrationDelete(t *testing.T) {
})
}

func TestIntegrationDeleteNestedFolders(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file contains only tests for the nested folder store. Therefore I would suggest to move this to the folder service tests (since we test the folder service Delete() behaviour).

@@ -632,30 +756,35 @@ func CreateOrg(t *testing.T, db *sqlstore.SQLStore) int64 {
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, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function has become over-complicated: I would suggest leave this one as as is and introduce a similar that uses the service instead (or the dashboard folder store).

@suntala suntala force-pushed the bugfixingdelete branch 2 times, most recently from 33258a6 to ee574dd Compare March 8, 2023 08:25
@suntala
Copy link
Contributor

suntala commented Mar 8, 2023

Update: we're going to hold off on merging this for one week (source).

@amy-super
Copy link

@suntala I talked to the User Essentials squad about this and we are all of the mind that we shouldn't do much work to support cases that will come up when users toggle nested folders on/off. Instead, we'll shift our efforts to documenting a recommendation for users to take a backup before playing around with the toggle and document that they might end up with orphaned items that are hidden from the UI if they turn the toggle off after created a nested structure.

@suntala suntala requested a review from papagian March 14, 2023 15:48
@suntala
Copy link
Contributor

suntala commented Mar 14, 2023

@papagian I made the change you suggested regarding naming in 8e8c21b. I think all the suggested changes have been made. Thanks for the reviews! Do you consider the PR ready to merge?

@papagian
Copy link
Contributor

@papagian I made the change you suggested regarding naming 8e8c21b. I think all the changes suggested have been made. Thanks for the reviews! Do you consider the PR ready to merge?

yes, go ahead merging it!

@suntala suntala added type/bug add to changelog and removed no-changelog Skip including change in changelog/release notes labels Mar 15, 2023
@suntala suntala merged commit 6974f43 into main Mar 15, 2023
10 checks passed
@suntala suntala deleted the bugfixingdelete branch March 15, 2023 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Nested Folders: unable to delete a nested folder
5 participants