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

FEATURE: Add bulk action to bookmark #26856

Merged
merged 6 commits into from May 22, 2024

Conversation

branquinhoaa
Copy link
Contributor

This PR aims to add bulk actions to the user's bookmarks.

After this feature, all users should be able to select multiple bookmarks and perform the actions of "deleting" or "clear reminders" as shown below:

bookmarks

@github-actions github-actions bot added the i18n PRs which update English locale files or i18n related code label May 2, 2024
Copy link
Contributor Author

@branquinhoaa branquinhoaa left a comment

Choose a reason for hiding this comment

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

Self-review.

@@ -42,6 +42,18 @@ export default class Bookmark extends RestModel {
});
}

static bulkOperation(bookmarks, operation) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a method to support bulk operations on bookmarks.

@@ -1,3 +1,3 @@
<button class='btn-transparent {{class}}' title='{{i18n "topics.bulk.toggle"}}'>
<button class='btn-transparent {{class}}' title='{{i18n title}}'>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was just a minor refactoring to get the title dynamically instead of always getting the hardcoded for topics.

Copy link
Contributor

Choose a reason for hiding this comment

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

Probably don't need to include this change either now that bookmarks don't use the raw button.

);
});

test("bulk select - clear reminders", async function (assert) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the test I mentioned before; it is currently flakey because of the dialog service.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, this is tricky. I am not sure it ahs to do with the dialog service, that service should be available at all times. I suspect it has to do with raw handlebars and the fact that here two tests are loading the same endpoint.

Once we move away from handlebars, this should clear. If it doesn't, we can sidestep tihs issue by having just one test that covers both the deleting and the reminder clearing checks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated the templates, but the test was still flakey, so I combined all the tests into one, as you suggested.

app/controllers/bookmarks_controller.rb Show resolved Hide resolved
@@ -24,8 +24,6 @@ def send_notification
end
end

private
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm using this method to clear the reminders for bookmarks.

describe "#create" do
before { sign_in(current_user) }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to move this inside the describe blocks to add tests for signed out users.

@branquinhoaa branquinhoaa marked this pull request as ready for review May 2, 2024 23:06
Copy link
Contributor

@pmusaraj pmusaraj left a comment

Choose a reason for hiding this comment

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

Thanks @branquinhoaa, this is a great start. I left some comments from a first review pass.

I also took it for a spin locally, and it works well. The main comment is regarding raw handlebars templates, we should try and avoid them as much as possible. (I know you're basing this off the existing raw templates, but that's legacy code that we plan to retire in the near future.)

{{#if this.bulkSelectEnabled}}
<span class="bulk-select-topics">
{{~#if this.canDoBulkActions}}
{{raw
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you try to use the Glimmer component directly here? For context, raw handlebars components are legacy elements, we are rtying to move away from them. (There's no way for you to have known this before...)

I suspect this may also help with the dialog test issue.

}}</button>
</span>
{{else}}
{{raw
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, het's try to avoid any raw components.

);
});

test("bulk select - clear reminders", async function (assert) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, this is tricky. I am not sure it ahs to do with the dialog service, that service should be available at all times. I suspect it has to do with raw handlebars and the fact that here two tests are loading the same endpoint.

Once we move away from handlebars, this should clear. If it doesn't, we can sidestep tihs issue by having just one test that covers both the deleting and the reminder clearing checks.

});

test("bulk select - options", async function (assert) {
updateCurrentUser({ moderator: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

Very minor, but you likely don't ned to set moderator to true here. Regular users should be able to update their own bookmarks.

},

showBulkBookmarksActionsModal(operationType, description, successMessage) {
this.dialog.yesNoConfirm({
Copy link
Contributor

Choose a reason for hiding this comment

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

For the "delete bookmark" use case, it may be better to use the delete confirm dialog.

end

it "can clear reminder for the given bookmarks" do
Guardian.any_instance.stubs(:can_edit?).returns(true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of stubbing the guardian check, it might be better to run this call as the correct user. And also run it as a second user (which should fail to update the bookmarks for user 1.)

end

it "can delete bookmarks" do
Guardian.any_instance.stubs(:can_edit?).returns(true)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, given this is a destructire operation, we want to make sure the only right users can do this.

@operations ||= %w[clear_reminder delete]
end

def self.register_operation(name, &block)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need to add an extension point at this time.

operation =
params
.require(:operation)
.permit(:type, *DiscoursePluginRegistry.permitted_bulk_action_parameters)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can keep the bookmark operation types separate from the topic bulk action operation types here.

describe "when user can't delete" do
it "does NOT delete the bookmarks" do
Guardian.any_instance.stubs(:can_edit?).returns(false)
Guardian.any_instance.stubs(:can_delete?).returns(false)
Copy link
Contributor

Choose a reason for hiding this comment

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

Here too we should also have a few tests with the bookmark edit permission.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I checked the bookmark guardian implementation, and it seems that currently, all users who can edit can also delete. So, the tests will be the same. I still added them to avoid future regressions, though.

- Replace raw handlebars components with glimmer components
- Remove stubs from rspec tests
- Add more test cases
- Fix flakey acceptance tests
@branquinhoaa
Copy link
Contributor Author

@pmusaraj Thanks for the review!

I've applied your suggestions and left some comments on them.

Could you take another look? Please let me know if you have anything else in mind.

Copy link
Contributor

@pmusaraj pmusaraj left a comment

Choose a reason for hiding this comment

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

Thanks for the updates, a few more comments.

@@ -1,3 +1,3 @@
<button class='btn-transparent {{class}}' title='{{i18n "topics.bulk.toggle"}}'>
<button class='btn-transparent {{class}}' title='{{i18n title}}'>
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably don't need to include this change either now that bookmarks don't use the raw button.

})
.then(() => {
this.router.refresh();
this.bulkSelectHelper.toggleBulkSelect();
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure we need to toggle the bulk select state here. Maybe the user wants to continue bulk selecting and editing other bookmarks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems that the toggle is not necessary since the router refresh is already cleaning up the selection.

In terms of user experience, however, I can't see a specific case where keeping the selection would be helpful for the current "bookmark's bulk operations."

If the user deletes the bookmark, he can't clean up the reminders, and if they spend effort cleaning the reminders, I don't think they will want to delete the same bookmarks in a subsequent operation.

Are you thinking about a specific workflow? Maybe I'm missing something; I will be happy to fix this; just thought it could be something to think about and discuss :)

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, if you have say 20 bookmarks, you might want to:

  • select 5, clear reminders
  • select 2, delete
  • continue with more selections

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ooh, sorry for the confusion! I misunderstood your suggestion. I thought you wanted to keep the checkboxes selected, which is not what you are suggesting 😅
So, this is what we have at this point - I guess it matches your initial idea:
Bookmarks - Activity - amandaalves15 - Discourse 2024-05-07 at 10 57 17 PM

spec/lib/bookmarks_bulk_action_spec.rb Outdated Show resolved Hide resolved
spec/lib/bookmarks_bulk_action_spec.rb Outdated Show resolved Hide resolved
spec/lib/bookmarks_bulk_action_spec.rb Outdated Show resolved Hide resolved
spec/lib/bookmarks_bulk_action_spec.rb Outdated Show resolved Hide resolved
@branquinhoaa branquinhoaa changed the title Add bulk action to bookmark FEATURE: Add bulk action to bookmark May 7, 2024
@branquinhoaa
Copy link
Contributor Author

Hey @pmusaraj, thank you again for your time here; I really appreciate it.

I added some notes for context - just to clarify some of the decisions I made, but please let me know if you disagree 😃

Copy link
Contributor

@pmusaraj pmusaraj left a comment

Choose a reason for hiding this comment

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

A few minor cleanups, but otherwise this is looking great, thanks!

_removeBookmarkFromList(bookmark) {
this.content.removeObject(bookmark);
},

_toggleBookmark(target, bookmark, isSelectingRange) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be named _toggleSelection, because it doesn't change the bookmark status, it just changes the selected checkboxes.

@@ -0,0 +1 @@
{{{view.html}}}
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be dropped as well?

BookmarkReminderNotificationHandler.new(b).clear_reminder
@changed_ids << b.id
else
raise Discourse::InvalidAccess.new
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, that makes sense, we are doing the same for deletes. I think you're right to keep the consistency. Maybe we move the check inside of BookmarkReminderNotificationHandler.new(b).clear_reminder so that individual reminder clearances also get the access check?

_customActions[name] = customAction;
}

export function addBulkDropdownButton(opts) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need this for bookmarks. In the topics list, this is linked to a plugin API endpoint that lets plugins add their own buttons and actions.

- Remove unused file, unused functions, rename functions
@branquinhoaa
Copy link
Contributor Author

Hey @pmusaraj! These are done 😄 let me know what you think about the update for BookmarkReminderNotificationHandler

@pmusaraj
Copy link
Contributor

@branquinhoaa I ran the specs here multiple times today (because I wanted to merge the PR), but they fail regularly, at the same place. 🤔

@branquinhoaa
Copy link
Contributor Author

@pmusaraj I will check today!

@branquinhoaa
Copy link
Contributor Author

Hey @pmusaraj, I have checked my commit where it started failing, and I didn't find anything that could justify the error, so I'm updating my branch with the latest changes pushed to main to see if this could help.

I have enabled Github actions on my fork to check if tests are passing. It is taking a while because of my GitHub plan. I will let you know the results once it is done 👍

Do you know if there is an easier way to run these tests locally? I saw quite a bit of setup on the CI file.

@pmusaraj
Copy link
Contributor

pmusaraj commented May 22, 2024

Sorry for my delay here @branquinhoaa, I think we're good to merge this now and keep an eye on flakey tests. (The license ❌ is unrelated, it should be fixed in main already.)

Thanks for your work! LGTM!

I'll let you click the Merge button, you should have that power now 😄

@branquinhoaa branquinhoaa merged commit b0d95c8 into discourse:main May 22, 2024
14 of 16 checks passed
@branquinhoaa
Copy link
Contributor Author

Yeey!! I just merged my first PR into Discourse's codebase 🥳 thank you @pmusaraj

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
i18n PRs which update English locale files or i18n related code
2 participants