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
FEATURE: Add bulk action to bookmark #26856
Conversation
c12b2c6
to
57ecf3c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self-review.
app/assets/javascripts/discourse/app/controllers/user-activity-bookmarks.js
Show resolved
Hide resolved
@@ -42,6 +42,18 @@ export default class Bookmark extends RestModel { | |||
}); | |||
} | |||
|
|||
static bulkOperation(bookmarks, operation) { |
There was a problem hiding this comment.
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}}'> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@@ -24,8 +24,6 @@ def send_notification | |||
end | |||
end | |||
|
|||
private |
There was a problem hiding this comment.
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) } |
There was a problem hiding this comment.
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.
There was a problem hiding this 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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 }); |
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
lib/bookmarks_bulk_action.rb
Outdated
@operations ||= %w[clear_reminder delete] | ||
end | ||
|
||
def self.register_operation(name, &block) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
@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. |
There was a problem hiding this 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.
app/assets/javascripts/discourse/app/components/bookmark-list.hbs
Outdated
Show resolved
Hide resolved
app/assets/javascripts/discourse/app/raw-views/bookmark-bulk-select-dropdown.gjs
Outdated
Show resolved
Hide resolved
@@ -1,3 +1,3 @@ | |||
<button class='btn-transparent {{class}}' title='{{i18n "topics.bulk.toggle"}}'> | |||
<button class='btn-transparent {{class}}' title='{{i18n title}}'> |
There was a problem hiding this comment.
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.
app/assets/javascripts/select-kit/addon/components/bulk-select-bookmarks-dropdown.js
Outdated
Show resolved
Hide resolved
}) | ||
.then(() => { | ||
this.router.refresh(); | ||
this.bulkSelectHelper.toggleBulkSelect(); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
app/assets/javascripts/discourse/tests/acceptance/user-bookmark-bulk-actions-test.js
Outdated
Show resolved
Hide resolved
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 😃 |
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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}}} |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
Hey @pmusaraj! These are done 😄 let me know what you think about the update for |
@branquinhoaa I ran the specs here multiple times today (because I wanted to merge the PR), but they fail regularly, at the same place. 🤔 |
@pmusaraj I will check today! |
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. |
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 Thanks for your work! LGTM! I'll let you click the Merge button, you should have that power now 😄 |
Yeey!! I just merged my first PR into Discourse's codebase 🥳 thank you @pmusaraj |
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: