Skip to content

Conversation

@everettbu
Copy link
Contributor

@everettbu everettbu commented Jul 26, 2025

Test 8

Summary by CodeRabbit

  • New Features

    • Added pagination controls for managing group members in the admin interface.
    • Introduced dedicated actions to add or remove group members, including confirmation prompts for removals.
    • Improved accessibility and layout of group management UI, including clearer labels and structured forms.
  • Bug Fixes

    • Fixed case-insensitive search operator in group listing.
  • Style

    • Enhanced and consolidated styling for group and badge admin sections for improved consistency.
  • Documentation

    • Updated and expanded localization strings for group management features.
  • Tests

    • Reorganized and expanded test coverage for group creation, updating, deletion, and member management actions.

@coderabbitai
Copy link

coderabbitai bot commented Jul 26, 2025

Walkthrough

This change set overhauls group management in the admin interface by introducing paginated member lists, explicit member add/remove endpoints, and UI controls for pagination and member management. It refactors both client-side JavaScript and server-side Ruby code, updates templates, styles, localization, and tests to align with the new group membership workflows and API structure.

Changes

Cohort / File(s) Change Summary
Admin Group Controller & Model Refactor
app/assets/javascripts/admin/controllers/admin-group.js.es6, app/assets/javascripts/discourse/models/group.js
Refactored to support paginated member fetching, explicit add/remove member actions, simplified username handling, and updated save/create logic. Introduced computed properties and actions for pagination and member management.
Admin Group Route & Group Members Route
app/assets/javascripts/admin/routes/admin_group_route.js, app/assets/javascripts/discourse/routes/group-members.js.es6
Removed afterModel hooks and shifted member-fetching to setupController methods, simplifying asynchronous handling and delegating data loading to the model.
Admin Group Templates
app/assets/javascripts/admin/templates/group.hbs, app/assets/javascripts/admin/templates/group_member.hbs, app/assets/javascripts/discourse/templates/group/members.hbs
Updated templates for new form structure, accessibility, pagination controls, and modular group member rendering. Introduced a new group_member.hbs partial.
Admin Group Member View
app/assets/javascripts/admin/views/group-member.js.es6
Added a new view component for rendering group members with associated template.
User Selector & Autocomplete Templates
app/assets/javascripts/discourse/templates/components/admin-group-selector.hbs, app/assets/javascripts/discourse/templates/user-selector-autocomplete.raw.hbs
Adjusted whitespace, formatting, and improved HTML structure for user selection and autocomplete components.
Admin Group Styles
app/assets/stylesheets/common/admin/admin_base.scss
Consolidated and enhanced styles for admin group and badge forms, pagination controls, and member items.
Admin Groups Controller (Rails)
app/controllers/admin/groups_controller.rb
Refactored to remove patch-based logic, add explicit member add/remove actions, enforce restrictions on automatic groups, and clarify parameter handling.
Groups Controller (Rails)
app/controllers/groups_controller.rb
Changed group member fetching to always use limit/offset, return paginated metadata, and render JSON with member and meta keys.
Admin Groups Controller Spec
spec/controllers/admin/groups_controller_spec.rb
Reorganized and expanded tests for new group membership endpoints and behaviors, removing patch-based tests.
Routes Update
config/routes.rb
Replaced old GET-based member route with explicit PUT/DELETE routes for adding and removing members in the admin namespace.
Localization
config/locales/client.en.yml
Added and updated localization strings for group management UI elements and confirmation dialogs.

Sequence Diagram(s)

sequenceDiagram
    participant Admin as Admin UI
    participant GroupModel as Group Model
    participant Server as Rails Server

    Admin->>GroupModel: findMembers(offset, limit)
    GroupModel->>Server: GET /groups/:id/members?offset=&limit=
    Server-->>GroupModel: { members, meta }
    GroupModel-->>Admin: members, pagination info

    Admin->>GroupModel: addMembers(usernames)
    GroupModel->>Server: PUT /admin/groups/:id/members (usernames)
    Server-->>GroupModel: success
    GroupModel->>GroupModel: findMembers() (refresh)

    Admin->>GroupModel: removeMember(member)
    GroupModel->>Server: DELETE /admin/groups/:id/members (user_id)
    Server-->>GroupModel: success
    GroupModel->>GroupModel: findMembers() (refresh)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

Oh, what a leap for group control,
With pages to flip and buttons to scroll!
Add or remove, with a click or two,
The rabbit cheers for the things you can do.
Pagination, new routes, and styles so neat—
Admin group management is now a treat!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch group-dm-user-addition-post

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai
Copy link

coderabbitai bot commented Jul 29, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@everettbu
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jul 29, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🔭 Outside diff range comments (1)
spec/controllers/admin/groups_controller_spec.rb (1)

67-129: Update all remaining deprecated RSpec syntax

Multiple test contexts still use deprecated should syntax. Update all instances to modern expect syntax for consistency.

Examples of required changes:

# destroy context
-      response.status.should == 422
-      Group.where(id: group.id).count.should == 1
+      expect(response.status).to eq(422)
+      expect(Group.where(id: group.id).count).to eq(1)

# add_members context  
-      response.status.should == 422
+      expect(response.status).to eq(422)
-      response.should be_success
+      expect(response).to be_success
-      group.users.count.should == 2
+      expect(group.users.count).to eq(2)

# remove_member context
-      response.status.should == 422
+      expect(response.status).to eq(422)
-      response.should be_success
+      expect(response).to be_success
-      group.users.count.should == 0
+      expect(group.users.count).to eq(0)
🧹 Nitpick comments (5)
app/assets/stylesheets/common/admin/admin_base.scss (1)

462-481: Consider reducing !important declarations.

The group-specific styles look good overall, but the multiple !important declarations (lines 465, 468, 472, 474) suggest potential CSS specificity issues.

Consider refactoring to avoid !important declarations:

 .groups {
   .ac-wrap {
-    width: 100% !important;
+    width: 100%;
     .item {
       width: 190px;
-      margin-right: 0 !important;
+      margin-right: 0;
     }
   }
   .next, .previous {
-    color: #333 !important;
+    color: #333;
     &.disabled {
-      color: #aaa !important;
+      color: #aaa;
     }
   }

This would require ensuring the selectors have appropriate specificity without forcing precedence.

app/controllers/groups_controller.rb (1)

22-36: Well-implemented pagination with good response structure.

The pagination implementation is solid with sensible defaults and a clean JSON response format. The separate total count query and ordered results provide good UX for pagination controls.

Consider adding an upper limit validation for security:

-limit = (params[:limit] || 50).to_i
+limit = [(params[:limit] || 50).to_i, 200].min

This prevents potentially expensive queries from very large limit values.

spec/controllers/admin/groups_controller_spec.rb (1)

14-132: Clean up extra empty lines for consistent formatting

RuboCop identified multiple formatting inconsistencies with extra empty lines at block boundaries.

Remove the extra empty lines at the beginning and end of each context block to follow Ruby style conventions. For example:

   context ".index" do
-
     it "produces valid json for groups" do
app/assets/javascripts/discourse/models/group.js (1)

17-17: Use strict equality operator

-    if (c > 0) { return c; }
+    if (c > 0) { return c; }
app/controllers/admin/groups_controller.rb (1)

6-9: Clarify assignment in conditional

RuboCop flagged the assignment in the conditional. Add parentheses to make the intent clear.

-    if search = params[:search]
+    if (search = params[:search])
       search = search.to_s
       groups = groups.where("name ILIKE ?", "%#{search}%")
     end
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4975fc2 and 060cda7.

📒 Files selected for processing (16)
  • app/assets/javascripts/admin/controllers/admin-group.js.es6 (2 hunks)
  • app/assets/javascripts/admin/routes/admin_group_route.js (1 hunks)
  • app/assets/javascripts/admin/templates/group.hbs (1 hunks)
  • app/assets/javascripts/admin/templates/group_member.hbs (1 hunks)
  • app/assets/javascripts/admin/views/group-member.js.es6 (1 hunks)
  • app/assets/javascripts/discourse/models/group.js (1 hunks)
  • app/assets/javascripts/discourse/routes/group-members.js.es6 (1 hunks)
  • app/assets/javascripts/discourse/templates/components/admin-group-selector.hbs (0 hunks)
  • app/assets/javascripts/discourse/templates/group/members.hbs (1 hunks)
  • app/assets/javascripts/discourse/templates/user-selector-autocomplete.raw.hbs (1 hunks)
  • app/assets/stylesheets/common/admin/admin_base.scss (2 hunks)
  • app/controllers/admin/groups_controller.rb (2 hunks)
  • app/controllers/groups_controller.rb (1 hunks)
  • config/locales/client.en.yml (1 hunks)
  • config/routes.rb (1 hunks)
  • spec/controllers/admin/groups_controller_spec.rb (2 hunks)
💤 Files with no reviewable changes (1)
  • app/assets/javascripts/discourse/templates/components/admin-group-selector.hbs
🧰 Additional context used
🪛 RuboCop (1.76.1)
spec/controllers/admin/groups_controller_spec.rb

[convention] 14-15: Extra empty line detected at block body beginning.

(Layout/EmptyLinesAroundBlockBody)


[convention] 31-32: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)


[convention] 35-36: Extra empty line detected at block body beginning.

(Layout/EmptyLinesAroundBlockBody)


[convention] 46-47: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)


[convention] 50-51: Extra empty line detected at block body beginning.

(Layout/EmptyLinesAroundBlockBody)


[convention] 59-60: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)


[convention] 77-78: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)


[convention] 81-82: Extra empty line detected at block body beginning.

(Layout/EmptyLinesAroundBlockBody)


[convention] 88-89: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)


[convention] 92-93: Extra empty line detected at block body beginning.

(Layout/EmptyLinesAroundBlockBody)


[convention] 109-110: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)


[convention] 113-114: Extra empty line detected at block body beginning.

(Layout/EmptyLinesAroundBlockBody)


[convention] 131-132: Extra empty line detected at block body end.

(Layout/EmptyLinesAroundBlockBody)

app/controllers/admin/groups_controller.rb

[warning] 6-6: Use == if you meant to do a comparison or wrap the expression in parentheses to indicate you meant to assign in a condition.

(Lint/AssignmentInCondition)


[warning] 72-72: Use == if you meant to do a comparison or wrap the expression in parentheses to indicate you meant to assign in a condition.

(Lint/AssignmentInCondition)

🔇 Additional comments (13)
app/assets/javascripts/discourse/templates/group/members.hbs (1)

6-6: LGTM! Template iteration updated for paginated members.

The change from iterating over model to members aligns with the pagination refactoring described in the PR. This change ensures the template works with the new paginated member data structure.

app/assets/javascripts/admin/views/group-member.js.es6 (1)

1-4: LGTM! Well-structured view component.

The view component follows Discourse conventions correctly:

  • Properly extends Discourse.View
  • Uses appropriate CSS class naming
  • References the correct template path
app/assets/javascripts/admin/templates/group_member.hbs (1)

1-1: LGTM! Clean and functional member template.

The template correctly:

  • Displays member avatar and username
  • Conditionally shows remove link only for non-automatic members
  • Uses appropriate FontAwesome icon for the remove action
  • Properly binds the removeMember action with the member parameter
app/assets/javascripts/discourse/templates/user-selector-autocomplete.raw.hbs (1)

5-8: LGTM! Improved code formatting and readability.

The indentation changes enhance the template's readability without affecting functionality. The cleaner structure makes it easier to understand the HTML hierarchy.

Also applies to: 15-19

config/locales/client.en.yml (2)

1623-1623: LGTM! More specific placeholder text.

The change from "add users" to "enter username" provides clearer guidance to users about the expected input format.


1630-1633: LGTM! Comprehensive localization for new member management features.

The added localization keys properly support the new group member management functionality:

  • delete_member_confirm: Provides user confirmation for member removal
  • name: Labels the group name input field
  • add: Generic add button label
  • add_members: Descriptive label for the member addition section

These keys align well with the enhanced group management UI described in the PR.

config/routes.rb (1)

49-50: LGTM! RESTful route design for member management.

The new routes properly implement explicit member addition and removal endpoints following RESTful conventions. Using DELETE for remove_member and PUT for add_members is appropriate for these operations.

app/assets/javascripts/discourse/routes/group-members.js.es6 (1)

8-12: Good simplification of route lifecycle.

Moving member fetching from afterModel to setupController and delegating to model.findMembers() simplifies the route logic. This aligns well with the pagination refactor where the model handles member fetching responsibility.

app/assets/stylesheets/common/admin/admin_base.scss (1)

164-182: Good consolidation of form styles.

Consolidating the .form-horizontal styles for both .groups and .badges containers reduces code duplication and ensures consistent styling across the admin interface.

app/assets/javascripts/admin/routes/admin_group_route.js (1)

11-15: Consistent with the member fetching refactor.

The changes mirror those in the group-members route, maintaining consistency across the codebase. Delegating member fetching to the model's findMembers() method aligns with the pagination refactor.

app/controllers/groups_controller.rb (1)

40-45: Improved parameter handling and security.

The updated find_group method uses better parameter validation with params.require() and implements case-insensitive group lookup while maintaining proper authorization checks.

app/assets/javascripts/discourse/models/group.js (1)

82-82: Fix syntax error in destroy method

The early return statement has incorrect syntax.

   destroy: function(){
-    if (!this.get('id')) { return };
+    if (!this.get('id')) { return; }
     return Discourse.ajax("/admin/groups/" + this.get('id'), {type: "DELETE"});

Likely an incorrect or invalid review comment.

app/controllers/admin/groups_controller.rb (1)

1-104: Well-structured controller refactoring

The refactoring to split member management into dedicated actions improves clarity and follows REST principles. The consistent handling of automatic group restrictions is excellent.

Comment on lines +7 to +13
if (this.get("user_count") == 0) { return 0; }
return Math.floor(this.get("offset") / this.get("limit")) + 1;
}.property("limit", "offset", "user_count"),

totalPages: function() {
if (this.get("user_count") == 0) { return 0; }
return Math.floor(this.get("user_count") / this.get("limit")) + 1;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use strict equality operators

Replace loose equality operators with strict equality to avoid type coercion issues.

   currentPage: function() {
-    if (this.get("user_count") == 0) { return 0; }
+    if (this.get("user_count") === 0) { return 0; }
     return Math.floor(this.get("offset") / this.get("limit")) + 1;
   }.property("limit", "offset", "user_count"),

   totalPages: function() {
-    if (this.get("user_count") == 0) { return 0; }
+    if (this.get("user_count") === 0) { return 0; }
     return Math.floor(this.get("user_count") / this.get("limit")) + 1;
   }.property("limit", "user_count"),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (this.get("user_count") == 0) { return 0; }
return Math.floor(this.get("offset") / this.get("limit")) + 1;
}.property("limit", "offset", "user_count"),
totalPages: function() {
if (this.get("user_count") == 0) { return 0; }
return Math.floor(this.get("user_count") / this.get("limit")) + 1;
currentPage: function() {
if (this.get("user_count") === 0) { return 0; }
return Math.floor(this.get("offset") / this.get("limit")) + 1;
}.property("limit", "offset", "user_count"),
totalPages: function() {
if (this.get("user_count") === 0) { return 0; }
return Math.floor(this.get("user_count") / this.get("limit")) + 1;
}.property("limit", "user_count"),
🤖 Prompt for AI Agents
In app/assets/javascripts/admin/controllers/admin-group.js.es6 around lines 7 to
13, replace all instances of loose equality operators (==) with strict equality
operators (===) to prevent unintended type coercion and ensure accurate
comparisons.

Comment on lines +61 to +65
addMembers: function() {
// TODO: should clear the input
if (Em.isEmpty(this.get("usernames"))) { return; }
this.get("model").addMembers(this.get("usernames"));
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Address TODO: Clear usernames input after adding members

The TODO comment indicates missing functionality. The usernames input should be cleared after successfully adding members.

   addMembers: function() {
-    // TODO: should clear the input
     if (Em.isEmpty(this.get("usernames"))) { return; }
-    this.get("model").addMembers(this.get("usernames"));
+    var self = this;
+    this.get("model").addMembers(this.get("usernames")).then(function() {
+      self.set("usernames", null);
+    });
   },

Would you like me to create an issue to track this enhancement?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
addMembers: function() {
// TODO: should clear the input
if (Em.isEmpty(this.get("usernames"))) { return; }
this.get("model").addMembers(this.get("usernames"));
},
addMembers: function() {
if (Em.isEmpty(this.get("usernames"))) { return; }
var self = this;
this.get("model").addMembers(this.get("usernames")).then(function() {
self.set("usernames", null);
});
},
🤖 Prompt for AI Agents
In app/assets/javascripts/admin/controllers/admin-group.js.es6 around lines 61
to 65, the addMembers function currently does not clear the usernames input
after adding members, as noted by the TODO comment. To fix this, after calling
addMembers on the model with the usernames, reset the usernames property to an
empty string or appropriate empty value to clear the input field.

Comment on lines +16 to +21
<a {{bind-attr class=":previous showingFirst:disabled"}} {{action "previous"}}>{{fa-icon "fast-backward"}}</a>
{{currentPage}}/{{totalPages}}
<a {{bind-attr class=":next showingLast:disabled"}} {{action "next"}}>{{fa-icon "fast-forward"}}</a>
</div>
<div class="ac-wrap clearfix">
{{each member in members itemView="group-member"}}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update deprecated Ember.js syntax

The template uses deprecated Ember helpers that should be updated to modern syntax:

  • Replace bind-attr with direct attribute binding
  • Use modern each block syntax
-        <a {{bind-attr class=":previous showingFirst:disabled"}} {{action "previous"}}>{{fa-icon "fast-backward"}}</a>
+        <a class="previous {{if showingFirst 'disabled'}}" {{action "previous"}}>{{fa-icon "fast-backward"}}</a>
         {{currentPage}}/{{totalPages}}
-        <a {{bind-attr class=":next showingLast:disabled"}} {{action "next"}}>{{fa-icon "fast-forward"}}</a>
+        <a class="next {{if showingLast 'disabled'}}" {{action "next"}}>{{fa-icon "fast-forward"}}</a>
       </div>
       <div class="ac-wrap clearfix">
-        {{each member in members itemView="group-member"}}
+        {{#each members as |member|}}
+          {{group-member member=member}}
+        {{/each}}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a {{bind-attr class=":previous showingFirst:disabled"}} {{action "previous"}}>{{fa-icon "fast-backward"}}</a>
{{currentPage}}/{{totalPages}}
<a {{bind-attr class=":next showingLast:disabled"}} {{action "next"}}>{{fa-icon "fast-forward"}}</a>
</div>
<div class="ac-wrap clearfix">
{{each member in members itemView="group-member"}}
<a class="previous {{if showingFirst 'disabled'}}" {{action "previous"}}>{{fa-icon "fast-backward"}}</a>
{{currentPage}}/{{totalPages}}
<a class="next {{if showingLast 'disabled'}}" {{action "next"}}>{{fa-icon "fast-fast-forward"}}</a>
</div>
<div class="ac-wrap clearfix">
{{#each members as |member|}}
{{group-member member=member}}
{{/each}}
🤖 Prompt for AI Agents
In app/assets/javascripts/admin/templates/group.hbs around lines 16 to 21, the
template uses deprecated Ember.js syntax such as `bind-attr` and the old `each`
helper. Replace `bind-attr` with direct class attribute bindings using curly
braces and class concatenation or class helper. Update the `each` loop to use
the modern block form `{{#each members as |member|}}` instead of `{{each member
in members}}`.

</div>

<div class='buttons'>
<button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update deprecated bind-attr helper

Replace the deprecated bind-attr helper with modern attribute binding syntax.

-    <button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
+    <button {{action "save"}} disabled={{disableSave}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button {{action "save"}} {{bind-attr disabled="disableSave"}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
<button {{action "save"}} disabled={{disableSave}} class='btn btn-primary'>{{i18n 'admin.customize.save'}}</button>
🤖 Prompt for AI Agents
In app/assets/javascripts/admin/templates/group.hbs at line 47, the code uses
the deprecated bind-attr helper to bind the disabled attribute. Replace
bind-attr with the modern Ember attribute binding syntax by directly using
disabled={{disableSave}} inside the button tag to properly bind the disabled
attribute.

Comment on lines +40 to 60
removeMember: function(member) {
var self = this;
return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
type: "DELETE",
data: { user_id: member.get("id") }
}).then(function() {
// reload member list
self.findMembers();
});
},

asJSON: function() {
return { group: {
name: this.get('name'),
alias_level: this.get('alias_level'),
visible: !!this.get('visible'),
usernames: this.get('usernames') } };
addMembers: function(usernames) {
var self = this;
return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
type: "PUT",
data: { usernames: usernames }
}).then(function() {
// reload member list
self.findMembers();
})
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling to member management methods

Both removeMember and addMembers methods lack error handling. Also, the promise should be returned to allow proper chaining in the controller.

   removeMember: function(member) {
     var self = this;
     return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
       type: "DELETE",
       data: { user_id: member.get("id") }
     }).then(function() {
       // reload member list
-      self.findMembers();
+      return self.findMembers();
+    }).catch(function(error) {
+      bootbox.alert(I18n.t("generic_error"));
+      throw error;
     });
   },

   addMembers: function(usernames) {
     var self = this;
     return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
       type: "PUT",
       data: { usernames: usernames }
     }).then(function() {
       // reload member list
-      self.findMembers();
-    })
+      return self.findMembers();
+    }).catch(function(error) {
+      bootbox.alert(I18n.t("generic_error"));
+      throw error;
+    });
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
removeMember: function(member) {
var self = this;
return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
type: "DELETE",
data: { user_id: member.get("id") }
}).then(function() {
// reload member list
self.findMembers();
});
},
asJSON: function() {
return { group: {
name: this.get('name'),
alias_level: this.get('alias_level'),
visible: !!this.get('visible'),
usernames: this.get('usernames') } };
addMembers: function(usernames) {
var self = this;
return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
type: "PUT",
data: { usernames: usernames }
}).then(function() {
// reload member list
self.findMembers();
})
},
removeMember: function(member) {
var self = this;
return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
type: "DELETE",
data: { user_id: member.get("id") }
}).then(function() {
// reload member list
return self.findMembers();
}).catch(function(error) {
bootbox.alert(I18n.t("generic_error"));
throw error;
});
},
addMembers: function(usernames) {
var self = this;
return Discourse.ajax('/admin/groups/' + this.get('id') + '/members.json', {
type: "PUT",
data: { usernames: usernames }
}).then(function() {
// reload member list
return self.findMembers();
}).catch(function(error) {
bootbox.alert(I18n.t("generic_error"));
throw error;
});
},
🤖 Prompt for AI Agents
In app/assets/javascripts/discourse/models/group.js between lines 40 and 60, the
removeMember and addMembers methods lack error handling and do not properly
return promises for chaining. To fix this, add .catch handlers to both methods
to handle potential AJAX errors gracefully, and ensure the promise returned by
Discourse.ajax is returned from the methods so callers can chain further actions
or handle errors as needed.

Comment on lines +71 to +75
usernames.split(",").each do |username|
if user = User.find_by_username(username)
group.add(user)
end
end
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize user lookup and clarify assignment

The current implementation makes N database queries. Consider optimizing with a single query. Also, add parentheses to clarify the assignment.

     usernames.split(",").each do |username|
-      if user = User.find_by_username(username)
+      if (user = User.find_by_username(username))
         group.add(user)
       end
     end

For better performance with many usernames:

users = User.where(username: usernames.split(",").map(&:strip))
users.each { |user| group.add(user) }
🧰 Tools
🪛 RuboCop (1.76.1)

[warning] 72-72: Use == if you meant to do a comparison or wrap the expression in parentheses to indicate you meant to assign in a condition.

(Lint/AssignmentInCondition)

🤖 Prompt for AI Agents
In app/controllers/admin/groups_controller.rb around lines 71 to 75, the code
performs a database query for each username in the list, causing N queries. To
optimize, split the usernames string by commas, strip whitespace, and perform a
single query using User.where with the array of usernames. Then iterate over the
resulting users to add them to the group. Also, add parentheses around the
assignment in the if condition to clarify it or refactor to avoid assignment
inside the condition.

end

it "is a subclass of AdminController" do
(Admin::GroupsController < Admin::AdminController).should == true
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update deprecated RSpec syntax

Replace deprecated should syntax with modern expect syntax.

-    (Admin::GroupsController < Admin::AdminController).should == true
+    expect(Admin::GroupsController < Admin::AdminController).to eq(true)
🤖 Prompt for AI Agents
In spec/controllers/admin/groups_controller_spec.rb at line 10, the test uses
the deprecated RSpec `should` syntax to check inheritance. Replace this with the
modern `expect` syntax by using `expect(Admin::GroupsController <
Admin::AdminController).to eq(true)` to ensure compatibility with current RSpec
standards.

Comment on lines +21 to +29
response.status.should == 200
::JSON.parse(response.body).keep_if {|r| r["id"] == group.id }.should == [{
"id"=>group.id,
"name"=>group.name,
"user_count"=>1,
"automatic"=>false,
"alias_level"=>0,
"visible"=>true
}]
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update all deprecated RSpec syntax in index context

Replace all instances of deprecated should syntax with modern expect syntax.

       xhr :get, :index
-      response.status.should == 200
-      ::JSON.parse(response.body).keep_if {|r| r["id"] == group.id }.should == [{
+      expect(response.status).to eq(200)
+      expect(::JSON.parse(response.body).keep_if {|r| r["id"] == group.id }).to eq([{
         "id"=>group.id,
         "name"=>group.name,
         "user_count"=>1,
         "automatic"=>false,
         "alias_level"=>0,
         "visible"=>true
-      }]
+      }])
🤖 Prompt for AI Agents
In spec/controllers/admin/groups_controller_spec.rb around lines 21 to 29, the
test uses deprecated RSpec `should` syntax for expectations. Replace all
`should` calls with the modern `expect` syntax by wrapping the subject in
`expect()` and using `.to eq` or appropriate matchers instead of `should ==`.

Comment on lines +39 to +44
response.status.should == 200

groups = Group.where(name: "bob").to_a

groups.count.should == 1
groups[0].name.should == "bob"
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update deprecated RSpec syntax in create context

-      response.status.should == 200
+      expect(response.status).to eq(200)

       groups = Group.where(name: "bob").to_a

-      groups.count.should == 1
-      groups[0].name.should == "bob"
+      expect(groups.count).to eq(1)
+      expect(groups[0].name).to eq("bob")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response.status.should == 200
groups = Group.where(name: "bob").to_a
groups.count.should == 1
groups[0].name.should == "bob"
expect(response.status).to eq(200)
groups = Group.where(name: "bob").to_a
expect(groups.count).to eq(1)
expect(groups[0].name).to eq("bob")
🤖 Prompt for AI Agents
In spec/controllers/admin/groups_controller_spec.rb around lines 39 to 44, the
RSpec syntax used for expectations is deprecated. Replace the old `should`
syntax with the newer `expect` syntax by changing lines like
`response.status.should == 200` to `expect(response.status).to eq(200)`, and
similarly update the other assertions to use `expect` with appropriate matchers.

Comment on lines +53 to +57
response.should be_success

group = Group.find(1)
group.name.should_not == "WAT"
group.visible.should == true
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Update deprecated RSpec syntax in update context

       xhr :put, :update, id: 1, name: "WAT", visible: "true"
-      response.should be_success
+      expect(response).to be_success

       group = Group.find(1)
-      group.name.should_not == "WAT"
-      group.visible.should == true
+      expect(group.name).not_to eq("WAT")
+      expect(group.visible).to eq(true)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
response.should be_success
group = Group.find(1)
group.name.should_not == "WAT"
group.visible.should == true
xhr :put, :update, id: 1, name: "WAT", visible: "true"
expect(response).to be_success
group = Group.find(1)
expect(group.name).not_to eq("WAT")
expect(group.visible).to eq(true)
🤖 Prompt for AI Agents
In spec/controllers/admin/groups_controller_spec.rb around lines 53 to 57, the
RSpec syntax used is deprecated. Replace the old `should` syntax with the newer
`expect` syntax by changing `response.should be_success` to `expect(response).to
be_successful`, `group.name.should_not == "WAT"` to `expect(group.name).not_to
eq("WAT")`, and `group.visible.should == true` to `expect(group.visible).to
eq(true)`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants