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

4503 super admin create users #5097

Open
wants to merge 35 commits into
base: main
Choose a base branch
from

Conversation

Benjamin-Couey
Copy link
Contributor

Resolves #4503

Description

This PR modifies app/controllers/admin/users_controller.rband app/views/admin/users/new.html.erb so that the super admin form for inviting a new user lets one select both the role the new user should have and the resource that role should be associated with if necessary. For the super admin role, no resource is required and so the resource dropdown is hidden via a new stimulus controller.

This PR also modifies UserInviteService in order to fascilitate the above behavior. The service now permits nil as a resource when the sole role being added is super admin, and will automatically add the org user role if only the org admin role is specified.

It is possible that some of this logic does not belong in the UserInviteService. Notably, the AddRoleService already contains logic to automatically add the org user role when the org admin role is added. In theory, the UserInviteService could call the
AddRoleService instead of directly calling add_role. However, AddRoleService relies on being passed the id of the user to add roles to, and newly created users won't have an id until after the invitation. While it would be possible to refactor AddRoleService, that seemed outside the scope of this PR and so the current solution was selected.

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

This was manually tested to verify that the form worked as expected when creating all the different roles.

Tests were added to spec/requests/admin/users_requests_spec.rb to verify that post requests could create new users with all of the possible roles (org user, org admin, partner, super admin) and that the request would fail and report an error if a role wasn't specified, or a resource wasn't specified for a role which required one. Additionally, tests were modified and added to verify that the available roles were loaded for the new and create actions.

Tests were added to spec/services/user_invite_service_spec.rb to verify that the service will accept a nil resource for the super admin role, raise an error for a nil resource for other roles, and that it will automatically add the org user role if only the org admin role is specified.

Tests were added to spec/system/admin/users_system_spec.rb to verify the workflow of a super admin creating an org user and super admin user. Tests were also added to verify that the resource dropdown is hidden when the super admin role is selected, and a flash message is shown if the form is submitted without selecting a resource with a role that requires one.

Screenshots

The new user form, showing the resource dropdown for an organization role

NewUserFormOrgRole

The new user form, hiding the resource dropdown for a the super admin role

NewUserFormSuperAdminRole

The new user form, showing the organizations in the dropdown

NewUserFormOrgResourceDropdown

The new user form, showing the partners in the dropdown

NewUserFormPartnerResourceDropdown

Benjamin-Couey and others added 19 commits March 2, 2025 08:06
…esources should be available seems like it would come down to the user's permissions, which a controller should handle
…der of role-resource and double-select controller action doesn't impact behavior
…s super admin, added comments explaining constraints
…checking for error messages on missing role type or resoruce id
…verify organizations are preloaded for the new user page as this is no longer the case
…sers, automatically add the ORG_USER role, and raise an error when no resource is provided
cielf
cielf previously requested changes Mar 17, 2025
Copy link
Collaborator

@cielf cielf left a comment

Choose a reason for hiding this comment

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

Thanks Benjamin...
Please address this failing sequence.....

Sign in as superadmin
Add a superuser (entirely new user -- I used myself)
accept the invitation (you'll want to sign out first -- I use a different browser as my default so I didn't have to)
As that new user
Users|All Users
find the [email protected] user
Edit

BOOM!

Edit -- you don't have to go into the new user -- you can just try editing any superuser, including the one you just added.

@Benjamin-Couey
Copy link
Contributor Author

Benjamin-Couey commented Mar 19, 2025

Thanks Benjamin... Please address this failing sequence.....

Sign in as superadmin Add a superuser (entirely new user -- I used myself) accept the invitation (you'll want to sign out first -- I use a different browser as my default so I didn't have to) As that new user Users|All Users find the [email protected] user Edit

BOOM!

Edit -- you don't have to go into the new user -- you can just try editing any superuser, including the one you just added.

Apologies for missing this. The error you're describing I also encountered while working on PR #5047 and since that PR fixed the bug and I didn't replicate the fix for this one. #5047 is merged into main now so it should be working.

There was also a test failing due to a small mistake I made in #5047 though that is fixed now as well. I suspect the remaining failing test, rspec ./spec/services/itemizable_update_service_spec.rb:128, is a flaky one as it's unrelated to any changes this PR makes, the test is passing locally for me, and that test has previously been reported as flaky here.

@Benjamin-Couey
Copy link
Contributor Author

Again, apologies for missing this but looks like the failing test I mentioned fixing was already addressed by @coalest here.

Since this fix is really unrelated to this PR (it's an old issue I missed somehow) does it make sense to revert my commit and use coalest's PR for the fix?

@coalest
Copy link
Collaborator

coalest commented Mar 19, 2025

@Benjamin-Couey Whoops, didn't realize you had already fixed that test. Let me know if I should close my PR

@Benjamin-Couey
Copy link
Contributor Author

Sorry, I'm going back and forth on this. I still think the fix is tangential to the issue in this PR but maybe that's fine? It makes it easier for reviewers to test the behavior? That's probably the reason to keep the commit and delete the PR.

@cielf
Copy link
Collaborator

cielf commented Mar 20, 2025

LGTM functionally -- over to @dorner or @awwaiid for technical review.

@cielf cielf dismissed their stale review March 20, 2025 13:46

Addressed

@cielf cielf requested review from awwaiid and dorner March 20, 2025 13:46
raise "Please select an organization for the user." if organization_id.blank?

organization_id
raise "Please select an associated resource for the role." if params[:resource_type].to_s != Role::SUPER_ADMIN.to_s && params[:resource_id].blank?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Rather than hardcoding SUPER_ADMIN, can we have a set of non-resource roles in the Role class which right now would only have SUPER_ADMIN as a member of the set? If we ever add roles to this set (which we have discussed in the past) it would mean not having to change this.

Copy link
Contributor Author

@Benjamin-Couey Benjamin-Couey Mar 25, 2025

Choose a reason for hiding this comment

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

Makes sense. These two commits address this.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Stimulus controllers should be defined generically whenever possible. Here this has nothing to do with roles and more to do with toggling classes on the destination target. You should be able to pass in the set of "toggle values" to the controller.

Copy link
Contributor Author

@Benjamin-Couey Benjamin-Couey Mar 25, 2025

Choose a reason for hiding this comment

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

These recent commits make this change and rename the controller to be more generic and descriptive.


def load_resources
@resources = Role::TITLES.invert
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you not just reference this in the view? Seems odd to have a separate callback to "load" these in the controller - this is not hitting the database.

Copy link
Contributor Author

@Benjamin-Couey Benjamin-Couey Mar 25, 2025

Choose a reason for hiding this comment

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

This recent commit makes this change.

# multiple roles being based makes sense is ORG_USER and ORG_ADMIN.

# Resource can be nil when the only role being added is the SUPER_ADMIN role.
# binding.pry
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove comment please

Copy link
Contributor Author

@Benjamin-Couey Benjamin-Couey Mar 25, 2025

Choose a reason for hiding this comment

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

This is addressed in the recent commit.


# Resource can be nil when the only role being added is the SUPER_ADMIN role.
# binding.pry
raise "Resource not found!" if resource.nil? && roles.first.to_s != Role::SUPER_ADMIN.to_s
Copy link
Collaborator

Choose a reason for hiding this comment

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

See above - we should use the same list of "no-resource roles".

Copy link
Contributor Author

@Benjamin-Couey Benjamin-Couey Mar 25, 2025

Choose a reason for hiding this comment

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

This recent commit makes this change.

# The logic is placed here instead of relying on the AddRoleService, as that
# currently only accepts users by id, and new user will not have an id
# until after they are invited.
if roles.all? { |role| role.to_s == Role::ORG_ADMIN.to_s }
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems odd because it implies that we can give users multiple org admin roles, which we shouldn't be able to do in a single call.

Copy link
Contributor Author

@Benjamin-Couey Benjamin-Couey Mar 25, 2025

Choose a reason for hiding this comment

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

I had done this because I needed to cast all the roles to string for the comparison (there are examples of roles being passed as string and symbol) but I agree it creates a strange implication.

This recent commit address this.

@Benjamin-Couey
Copy link
Contributor Author

@dorner Apologies for the wait, but I believe my recent commits address your comments.

Copy link
Collaborator

@dorner dorner left a comment

Choose a reason for hiding this comment

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

Getting closer!

export default class extends Controller {
static targets = ["source", "destination"]
static values = {
valuestohide: Array
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is JavaScript, so this should be camelCase: valuesToHide. You would call it as values-to-hide in the HTML.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be addressed in the recent commit.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["source", "destination"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

The spacing in this file seems very out of whack.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be addressed in the recent commit.

sourceChanged() {
const val = $(this.sourceTarget).val()
this.destinationTargets.forEach(
destination_target => { $(destination_target).toggleClass("d-none", this.valuestohideValue.includes(val)); }
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
destination_target => { $(destination_target).toggleClass("d-none", this.valuestohideValue.includes(val)); }
destinationTarget => { $(destinationTarget).toggleClass("d-none", this.valuesToHideValue.includes(val)); }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be addressed in the recent commit.

# The logic is placed here instead of relying on the AddRoleService, as that
# currently only accepts users by id, and new user will not have an id
# until after they are invited.
if roles.map(&:to_s).include?(Role::ORG_ADMIN.to_s)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need to cast both the role and the comparison to string, if they're both symbols?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pushing back on this. I had got it in my head that UserInviteService.invite was called with role as both a symbol and string. Looking more closely, it was only my modifications to Admin::UsersController that were submitting a string.

Should be addressed in the recent commit.

<%= render 'admin/users/user_form_fields', f: f %>

<div data-controller="double-select" data-double-select-url-value="<%= resource_ids_admin_users_url %>">

<div data-controller="hide-by-source-val" data-hide-by-source-val-valuestohide-value="<%= Role::ROLES_WITHOUT_RESOURCE.to_json %>">
Copy link
Collaborator

Choose a reason for hiding this comment

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

You don't need a second div, you can add all the values to the same div:

<div data-controller="double-select hide-by-source-val" 
  data-double-select-url-value="<%= resource_ids_admin_users_url %>"
  data-hide-by-source-val-values-to-hide-value="<%= Role::ROLES_WITHOUT_RESOURCE.to_json %>">

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should be addressed in the recent commit.

@Benjamin-Couey
Copy link
Contributor Author

@dorner my recent commits should address your latest comments. There is a failing test, rspec ./spec/system/adjustment_system_spec.rb[1:5:2:1], but it appears unrelated to the changes I made and is passing locally which makes me suspect it might just be flaky?

I will look further into it and report what I find.

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.

Super admin should be able to add different kinds of users
4 participants