Admin Area: Bulk assign user roles (#19733)
* Bulk Assign Roles UI * Renaming roles to role * Added role to multiple users without using note. * Role note added * Role blank key fix * Removed roles * typo fix * AuditLog Added * Optimising code * Good standing role issue * Good standing role issue fix * Minor PR changes based on review * Added cypress tests * Added spec tests for BulkAssignRole * Minor fixes
This commit is contained in:
parent
20b643ac8f
commit
f2cc2ae774
11 changed files with 296 additions and 0 deletions
70
app/controllers/admin/bulk_assign_role_controller.rb
Normal file
70
app/controllers/admin/bulk_assign_role_controller.rb
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
module Admin
|
||||
class BulkAssignRoleController < Admin::ApplicationController
|
||||
layout "admin"
|
||||
include Admin::UsersHelper
|
||||
|
||||
def assign_role
|
||||
if permitted_params[:role].blank?
|
||||
raise ArgumentError,
|
||||
I18n.t("admin.bulk_assign_role_controller.role_blank")
|
||||
end
|
||||
|
||||
role = permitted_params[:role]
|
||||
usernames = permitted_params[:usernames].downcase.split(/\s*,\s*/)
|
||||
note = permitted_params[:note_for_current_role].presence
|
||||
note ||= I18n.t("admin.bulk_assign_role_controller.role_assigment", role: role)
|
||||
|
||||
begin
|
||||
usernames.each do |username|
|
||||
user = User.find_by(username: username)
|
||||
user_action_status = user_action_status(user, role)
|
||||
if user
|
||||
Moderator::ManageActivityAndRoles.handle_user_roles(
|
||||
admin: current_user,
|
||||
user: user,
|
||||
user_params: { user_status: role, note_for_current_role: note },
|
||||
)
|
||||
end
|
||||
|
||||
AuditLog.create(
|
||||
category: "admin.bulk_assign_role.add_role",
|
||||
user: current_user,
|
||||
roles: current_user.roles_name,
|
||||
slug: "bulk_assign_role",
|
||||
data: {
|
||||
role: role,
|
||||
username: username,
|
||||
user_action_status: user_action_status
|
||||
},
|
||||
)
|
||||
end
|
||||
flash[:success] = I18n.t("admin.bulk_assign_role_controller.success_message")
|
||||
rescue StandardError => e
|
||||
flash[:danger] = e.message
|
||||
end
|
||||
redirect_to admin_bulk_assign_role_index_path
|
||||
rescue ArgumentError => e
|
||||
flash[:danger] = e.message
|
||||
redirect_to admin_bulk_assign_role_index_path
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# We need to override this method from Admin::ApplicationController since
|
||||
# there is no resource to authorize.
|
||||
def authorization_resource; end
|
||||
|
||||
def user_action_status(user, role)
|
||||
if user
|
||||
return "user_already_has_the_role" if user_status(user) == role
|
||||
|
||||
return "role_applied_successfully"
|
||||
end
|
||||
"user_not_found"
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:usernames, :role, :note_for_current_role)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8,6 +8,7 @@ class AdminMenu
|
|||
item(name: "members", controller: "users"),
|
||||
item(name: "invited members", controller: "invitations"),
|
||||
item(name: "gdpr actions", controller: "gdpr_delete_requests"),
|
||||
item(name: "bulk assign role", controller: "bulk_assign_role"),
|
||||
]
|
||||
|
||||
scope :content_manager, "dashboard-line", [
|
||||
|
|
|
|||
25
app/views/admin/bulk_assign_role/index.html.erb
Normal file
25
app/views/admin/bulk_assign_role/index.html.erb
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<header class="flex justify-between items-center mb-4">
|
||||
<h1 class="crayons-title"><%= t("views.admin.users.actions.bulk_assign_role.text") %></h1>
|
||||
</header>
|
||||
<div class="crayons-card p-6">
|
||||
<%= form_with(url: admin_bulk_assign_role_path, local: true, class: "flex flex-col gap-4") do |f| %>
|
||||
<div class="crayons-field">
|
||||
<%= f.label :role, t("views.admin.users.actions.assign_role.role"), class: "crayons-field__label" %>
|
||||
<%= f.select("role", role_options(current_user)["Statuses"], { include_blank: t("views.admin.users.actions.assign_role.select") }, class: "crayons-select") %>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<%= f.label :usernames, "Usernames", class: "crayons-field__label" %>
|
||||
<p class="crayons-field__description">
|
||||
<%= t("views.admin.users.actions.assign_role.warning") %>
|
||||
</p>
|
||||
<%= f.text_area :usernames, placeholder: "username1, username2, username3", required: true, size: "40x3", class: "crayons-textfield" %>
|
||||
</div>
|
||||
<div class="crayons-field">
|
||||
<%= f.label :note_for_current_role, t("views.admin.users.actions.assign_role.note"), class: "crayons-field__label" %>
|
||||
<%= f.text_area :note_for_current_role, required: true, class: "crayons-textfield", placeholder: "" %>
|
||||
</div>
|
||||
<div>
|
||||
<%= f.submit t("views.admin.users.actions.assign_role.text"), class: "c-btn c-btn--primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
@ -19,6 +19,10 @@ en:
|
|||
deleted: Broadcast has been deleted!
|
||||
updated: Broadcast has been updated!
|
||||
wrong: Something went wrong with deleting the broadcast.
|
||||
bulk_assign_role_controller:
|
||||
role_assigment: Role %{role} assigned to user.
|
||||
role_blank: Please choose a role to add.
|
||||
success_message: Roles are being added. The task will finish shortly.
|
||||
consumer_apps_controller:
|
||||
created: '%{app} has been created!'
|
||||
deleted: '%{app} has been deleted!'
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ fr:
|
|||
deleted: Le Broadcast a été supprimée !
|
||||
updated: Le Broadcast a été mise à jour !
|
||||
wrong: Quelque chose s'est mal passé avec la suppression du Broadcast.
|
||||
bulk_assign_role_controller:
|
||||
role_assigment: Role %{role} assigned to user.
|
||||
role_blank: Please choose a role to add.
|
||||
success_message: Roles are being added. The task will finish shortly.
|
||||
consumer_apps_controller:
|
||||
created: '%{app} a été créé !'
|
||||
deleted: '%{app} a été supprimé !'
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ en:
|
|||
select: Select role
|
||||
note: "Add a note to this action:"
|
||||
submit: Add
|
||||
warning: "Comma Delimited, All Lowercase, No @ Symbol Required In Front Of Usernames Warning: Usernames are case-sensitive and must be all lowercase! This task will fail silently when supplied with incorrect usernames."
|
||||
add_org:
|
||||
text: Add organization
|
||||
heading: Add organization
|
||||
|
|
@ -136,6 +137,8 @@ en:
|
|||
aria_credits: Amount of credits to add or remove
|
||||
note: "Add a note to this action:"
|
||||
submit: Adjust balance
|
||||
bulk_assign_role:
|
||||
text: Bulk Assign Role
|
||||
copy_email: Copy email address
|
||||
danger:
|
||||
desc1: This action is irreversible.
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ fr:
|
|||
select: Select role
|
||||
note: "Add a note to this action:"
|
||||
submit: Add
|
||||
warning: "Comma Delimited, All Lowercase, No @ Symbol Required In Front Of Usernames Warning: Usernames are case-sensitive and must be all lowercase! This task will fail silently when supplied with incorrect usernames."
|
||||
add_org:
|
||||
text: Add organization
|
||||
heading: Add organization
|
||||
|
|
@ -136,6 +137,8 @@ fr:
|
|||
aria_credits: Amount of credits to add or remove
|
||||
note: "Add a note to this action:"
|
||||
submit: Adjust balance
|
||||
bulk_assign_role:
|
||||
text: Bulk Assign Role
|
||||
copy_email: Copy email address
|
||||
danger:
|
||||
desc1: This action is irreversible.
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ namespace :admin do
|
|||
end
|
||||
|
||||
resources :gdpr_delete_requests, only: %i[index destroy]
|
||||
|
||||
resources :bulk_assign_role, only: %i[index]
|
||||
post "/bulk_assign_role", to: "bulk_assign_role#assign_role"
|
||||
end
|
||||
|
||||
scope :content_manager do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
import {
|
||||
verifyAndDismissUserUpdatedMessage,
|
||||
verifyAndDismissDangerMessage,
|
||||
} from '../users/userAdminUtilitites';
|
||||
describe('Bulk Assign Role', () => {
|
||||
beforeEach(() => {
|
||||
cy.testSetup();
|
||||
cy.fixture('users/adminUser.json').as('user');
|
||||
|
||||
cy.get('@user').then((user) => {
|
||||
cy.loginAndVisit(user, '/admin/member_manager/bulk_assign_role');
|
||||
});
|
||||
});
|
||||
|
||||
it('should show success message for correct fields ', () => {
|
||||
cy.get('.crayons-select').select('Trusted');
|
||||
|
||||
cy.get('.crayons-textfield[name="usernames"]').type('test1, test2, test3');
|
||||
|
||||
cy.get('.crayons-textfield[name="note_for_current_role"]').type(
|
||||
'Sample note',
|
||||
);
|
||||
|
||||
cy.findByText('Assign role').click();
|
||||
|
||||
verifyAndDismissUserUpdatedMessage(
|
||||
'Roles are being added. The task will finish shortly.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should show error message if role is not selected ', () => {
|
||||
cy.get('.crayons-textfield[name="usernames"]').type('test1, test2, test3');
|
||||
|
||||
cy.get('.crayons-textfield[name="note_for_current_role"]').type(
|
||||
'Sample note',
|
||||
);
|
||||
|
||||
cy.findByText('Assign role').click();
|
||||
|
||||
verifyAndDismissDangerMessage('Please choose a role to add.');
|
||||
});
|
||||
});
|
||||
|
|
@ -20,3 +20,26 @@ export function verifyAndDismissUserUpdatedMessage(
|
|||
|
||||
cy.findByTestId('flash-success').should('not.exist');
|
||||
}
|
||||
|
||||
/**
|
||||
* E2E helper function for user admin tests that validates the correct flash danger message appears
|
||||
*
|
||||
* @param {string} [message="There was some error"] The expected flash message text
|
||||
*/
|
||||
export function verifyAndDismissDangerMessage(
|
||||
message = 'There was some error',
|
||||
) {
|
||||
cy.findByTestId('flash-danger')
|
||||
.as('danger')
|
||||
.then((element) => {
|
||||
expect(element.text().trim()).equal(message);
|
||||
});
|
||||
|
||||
cy.get('@danger').within(() => {
|
||||
cy.findByRole('button', { name: 'Dismiss message' })
|
||||
.should('have.focus')
|
||||
.click();
|
||||
});
|
||||
|
||||
cy.findByTestId('flash-danger').should('not.exist');
|
||||
}
|
||||
|
|
|
|||
118
spec/requests/admin/bulk_assign_role_spec.rb
Normal file
118
spec/requests/admin/bulk_assign_role_spec.rb
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Admin::BulkAssignRole" do
|
||||
let(:admin) { create(:user, :super_admin) }
|
||||
let(:user1) { create(:user) }
|
||||
let(:user2) { create(:user) }
|
||||
let(:user3) { create(:user) }
|
||||
let(:usernames_string) { "#{user1.username}, #{user2.username}, #{user3.username}" }
|
||||
let(:role) { "Trusted" }
|
||||
let(:note_for_current_role) { "Assigned Trusted role" }
|
||||
let(:extra_space_usernames_string) { "#{user1.username}, #{user2.username}, #{user3.username}" }
|
||||
let(:invalid_role) { "Invalid Role" }
|
||||
|
||||
describe "POST /admin/member_manager/bulk_assign_role" do
|
||||
before do
|
||||
sign_in admin
|
||||
end
|
||||
|
||||
it "adds trusted role successfully" do
|
||||
params = { usernames: usernames_string, role: role, note_for_current_role: note_for_current_role }
|
||||
post admin_bulk_assign_role_path, params: params
|
||||
|
||||
expect(response).to redirect_to(admin_bulk_assign_role_index_path)
|
||||
expect(flash[:success]).to eq(I18n.t("admin.bulk_assign_role_controller.success_message"))
|
||||
|
||||
expect(user1.roles.count).to eq(1)
|
||||
expect(user1.roles.last.name).to eq(role.downcase)
|
||||
|
||||
expect(user2.roles.count).to eq(1)
|
||||
expect(user2.roles.last.name).to eq(role.downcase)
|
||||
|
||||
expect(user3.roles.count).to eq(1)
|
||||
expect(user3.roles.last.name).to eq(role.downcase)
|
||||
end
|
||||
|
||||
it "adds role with extra whitespace in usernames" do
|
||||
params = { usernames: extra_space_usernames_string, role: role, note_for_current_role: note_for_current_role }
|
||||
post admin_bulk_assign_role_path, params: params
|
||||
|
||||
expect(user1.roles.count).to eq(1)
|
||||
expect(user1.roles.last.name).to eq(role.downcase)
|
||||
|
||||
expect(user2.roles.count).to eq(1)
|
||||
expect(user2.roles.last.name).to eq(role.downcase)
|
||||
|
||||
expect(user3.roles.count).to eq(1)
|
||||
expect(user3.roles.last.name).to eq(role.downcase)
|
||||
end
|
||||
|
||||
it "shows error if role is blank" do
|
||||
params = { usernames: usernames_string, note_for_current_role: note_for_current_role }
|
||||
post admin_bulk_assign_role_path, params: params
|
||||
|
||||
expect(response).to redirect_to(admin_bulk_assign_role_index_path)
|
||||
expect(flash[:danger]).to eq(I18n.t("admin.bulk_assign_role_controller.role_blank"))
|
||||
|
||||
expect(user1.roles.count).to eq(0)
|
||||
expect(user2.roles.count).to eq(0)
|
||||
expect(user3.roles.count).to eq(0)
|
||||
end
|
||||
|
||||
it "adds default note if user input is empty" do
|
||||
params = { usernames: usernames_string, role: role }
|
||||
post admin_bulk_assign_role_path, params: params
|
||||
|
||||
expect(user1.notes.last.content).to eq(I18n.t("admin.bulk_assign_role_controller.role_assigment", role: role))
|
||||
end
|
||||
|
||||
it "adds role to valid usernames only and creates user_not_found AuditLog" do
|
||||
invalid_username = "invalidusername"
|
||||
params = { usernames: "#{user1.username}, #{user2.username}, #{invalid_username}", role: role,
|
||||
note_for_current_role: note_for_current_role }
|
||||
post admin_bulk_assign_role_path, params: params
|
||||
|
||||
expect(user1.roles.count).to eq(1)
|
||||
expect(user1.roles.last.name).to eq(role.downcase)
|
||||
|
||||
expect(user2.roles.count).to eq(1)
|
||||
expect(user2.roles.last.name).to eq(role.downcase)
|
||||
|
||||
log = AuditLog.last
|
||||
expect(log.data["role"]).to eq(role)
|
||||
expect(log.data["username"]).to eq(invalid_username)
|
||||
expect(log.data["user_action_status"]).to eq("user_not_found")
|
||||
end
|
||||
|
||||
it "adds successful AuditLog" do
|
||||
params = { usernames: usernames_string, role: role, note_for_current_role: note_for_current_role }
|
||||
post admin_bulk_assign_role_path, params: params
|
||||
|
||||
logs = AuditLog.last(3)
|
||||
expect(logs[0].category).to eq("admin.bulk_assign_role.add_role")
|
||||
expect(logs[0].user).to eq(admin)
|
||||
|
||||
expect(logs[0].data["username"]).to eq(user1.username)
|
||||
expect(logs[0].data["user_action_status"]).to eq("role_applied_successfully")
|
||||
|
||||
expect(logs[1].data["username"]).to eq(user2.username)
|
||||
expect(logs[1].data["user_action_status"]).to eq("role_applied_successfully")
|
||||
|
||||
expect(logs[2].data["username"]).to eq(user3.username)
|
||||
expect(logs[2].data["user_action_status"]).to eq("role_applied_successfully")
|
||||
end
|
||||
|
||||
it "adds role already applied AuditLog" do
|
||||
params = { usernames: user1.username.to_s, role: role, note_for_current_role: note_for_current_role }
|
||||
post admin_bulk_assign_role_path, params: params
|
||||
|
||||
# Adding the role again to trigger the "user_already_has_the_role" AuditLog.
|
||||
post admin_bulk_assign_role_path, params: params
|
||||
|
||||
log = AuditLog.last
|
||||
expect(log.data["role"]).to eq(role)
|
||||
expect(log.data["username"]).to eq(user1.username)
|
||||
expect(log.data["user_action_status"]).to eq("user_already_has_the_role")
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue