diff --git a/app/controllers/admin/admins_controller.rb b/app/controllers/admin/admins_controller.rb index 10cadf06f..0120049cf 100644 --- a/app/controllers/admin/admins_controller.rb +++ b/app/controllers/admin/admins_controller.rb @@ -1,5 +1,10 @@ class Admin::AdminsController < Admin::UsersController before_action :find_resource, except: [:index, :new, :create, :login_as_assessor, :login_as_user] + + expose(:collaborators) do + nil + end + def index params[:search] ||= AdminSearch::DEFAULT_SEARCH params[:search].permit! diff --git a/app/controllers/admin/assessors_controller.rb b/app/controllers/admin/assessors_controller.rb index b232f3ddf..35720c3f2 100644 --- a/app/controllers/admin/assessors_controller.rb +++ b/app/controllers/admin/assessors_controller.rb @@ -15,6 +15,10 @@ class Admin::AssessorsController < Admin::UsersController :bulk_deactivate_dt, ] + expose(:collaborators) do + nil + end + def index params[:search] ||= AssessorSearch::DEFAULT_SEARCH params[:search].permit! diff --git a/app/controllers/admin/judges_controller.rb b/app/controllers/admin/judges_controller.rb index 558eeba3a..d0c1df729 100644 --- a/app/controllers/admin/judges_controller.rb +++ b/app/controllers/admin/judges_controller.rb @@ -1,4 +1,8 @@ class Admin::JudgesController < Admin::UsersController + expose(:collaborators) do + nil + end + def index params[:search] ||= JudgeSearch::DEFAULT_SEARCH params[:search].permit! diff --git a/app/controllers/admin/users/collaborators_controller.rb b/app/controllers/admin/users/collaborators_controller.rb new file mode 100644 index 000000000..90226c958 --- /dev/null +++ b/app/controllers/admin/users/collaborators_controller.rb @@ -0,0 +1,52 @@ +class Admin::Users::CollaboratorsController < Admin::BaseController + expose(:user) do + User.find(params[:user_id]) + end + + expose(:collaborator) do + User.find(params[:collaborator_id]) + end + + expose(:search_users) do + AdminActions::SearchCollaboratorCandidates.new(existing_collaborators: user.account.users, params: search_params) + end + + expose(:add_collaborator_interactor) do + AdminActions::AddCollaborator.new(account: user.account, collaborator:, params: create_params) + end + + expose(:candidates) do + search_users.candidates + end + + def search + authorize user, :can_add_collaborators_to_account? + search_users.run if search_users.valid? + end + + def create + authorize user, :can_add_collaborators_to_account? + + add_collaborator_interactor.run.tap do |result| + if result.success? + redirect_to edit_admin_user_path(user), notice: "#{collaborator.email} successfully added to Collaborators!" + else + redirect_to edit_admin_user_path(user), notice: "#{collaborator.email} could not be added to Collaborators: #{result.error_messages}" + end + end + end + + private + + def create_params + params.require(:user).permit(:transfer_form_answers, :new_owner_id, :role).tap do |p| + p[:transfer_form_answers] = ActiveModel::Type::Boolean.new.cast(p[:transfer_form_answers]) + end + end + + def search_params + return if params[:search].blank? + + params.require(:search).permit(:query) + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 699a28d68..2ececa26b 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,6 +1,10 @@ class Admin::UsersController < Admin::BaseController before_action :find_resource, except: [:index, :new, :create] + expose(:collaborators) do + @resource.account.collaborators_without(@resource) + end + def index params[:search] ||= UserSearch::DEFAULT_SEARCH params[:search].permit! diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index 447b9822f..d95c723ff 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -1,5 +1,5 @@ class UserPolicy < ApplicationPolicy - %w[index? update? create? show? new?].each do |method| + %w[index? update? create? show? new? can_add_collaborators_to_account?].each do |method| define_method method do admin? end diff --git a/app/views/admin/users/_fields_collaborators.html.slim b/app/views/admin/users/_fields_collaborators.html.slim index 3816ed319..96d172afd 100644 --- a/app/views/admin/users/_fields_collaborators.html.slim +++ b/app/views/admin/users/_fields_collaborators.html.slim @@ -1,6 +1,4 @@ -/ TODO collaborators new/delete, also the collaborators variable - if action_name == "edit" - - collaborators = resource.account.collaborators_without(resource) - if collaborators.any? = render "admin/collaborators/list", collaborators: collaborators - else @@ -11,3 +9,6 @@ br p.p-empty This user has not added any collaborators. br + + - if policy(resource).can_add_collaborators_to_account? + = render "admin/users/collaborators/search_form", resource: resource diff --git a/app/views/admin/users/collaborators/_search_form.html.slim b/app/views/admin/users/collaborators/_search_form.html.slim new file mode 100644 index 000000000..61b926fae --- /dev/null +++ b/app/views/admin/users/collaborators/_search_form.html.slim @@ -0,0 +1,27 @@ += simple_form_for :search, + url: search_admin_user_collaborators_url(resource), + remote: true, + method: :get, + as: nil, + html: { class: "admin-search-collaborators-form" } do |f| + + .form-container + label.form-label for="admin-search-collaborators-query" Add collaborator + + .alert.alert-danger.hidden.js-admin-search-collaborators-error-box role="alert" + + .form-block + .row + .col-md-12 + = f.input :query, + as: :string, + label: false, + input_html: { class: "form-control", id: "admin-search-collaborators-query" }, + wrapper_html: { class: 'pull-left col-md-10 admin-search-collaborators-query' }, + placeholder: "Type part of email, first name or last name" + + .text-right + = f.submit "Search", class: "btn btn-primary pull-right" + .clear + +ul.list-unstyled.list-actions.hidden.js-admin-search-collaborators-results-box diff --git a/app/views/admin/users/collaborators/_search_results.html.slim b/app/views/admin/users/collaborators/_search_results.html.slim new file mode 100644 index 000000000..d052be58a --- /dev/null +++ b/app/views/admin/users/collaborators/_search_results.html.slim @@ -0,0 +1,9 @@ +- if candidates.present? + - candidates.each do |candidate| + li.list-group-item id="user_#{candidate.id}" + p.pull-right + = link_to "#{candidate.full_name} (#{candidate.email})", edit_admin_user_path(candidate) + + br + + p= render "admin/users/collaborators/transfer_form", collaborator: candidate diff --git a/app/views/admin/users/collaborators/_transfer_form.html.slim b/app/views/admin/users/collaborators/_transfer_form.html.slim new file mode 100644 index 000000000..a9f6382cf --- /dev/null +++ b/app/views/admin/users/collaborators/_transfer_form.html.slim @@ -0,0 +1,29 @@ += simple_form_for :user, url: admin_user_collaborators_url(collaborator_id: collaborator.id), method: :post do |f| + - if collaborator&.account.collaborators_without(collaborator).any? && collaborator.account.owner == collaborator + .question-group + h3 = f.label :new_owner_id, label: "Owner" + .row + .col-sm-6 + = f.select :new_owner_id, + collaborator.account.collaborators_without(collaborator).map { |u| [u.email, u.id] }, + { include_blank: 'Select New Owner…' }, + { class: "form-control" } + + .question-group + h3 = f.label :role, label: "Role" + .row + .col-sm-4 + = f.select :role, + User::POSSIBLE_ROLES.map { |r| [r.humanize, r] }, + { selected: collaborator.role }, + { class: "form-control" } + + - if collaborator.form_answers.any? + .question-group + h3 = f.label :transfer_form_answers, label: "Transfer existing Application/s" + .row + .col-sm-2 + = f.check_box :transfer_form_answers, value: true + + br + = f.submit "Transfer", class: "btn btn-secondary" diff --git a/app/views/admin/users/collaborators/search.js.slim b/app/views/admin/users/collaborators/search.js.slim new file mode 100644 index 000000000..267e91b09 --- /dev/null +++ b/app/views/admin/users/collaborators/search.js.slim @@ -0,0 +1,6 @@ +- if search_users.valid? + | $('.js-admin-search-collaborators-results-box').html("#{j render("search_results")}").removeClass("hidden"); + | $(".js-admin-search-collaborators-error-box").addClass("hidden"); +- else + | $('.js-admin-search-collaborators-results-box').addClass("hidden"); + | $(".js-admin-search-collaborators-error-box").text("#{search_users.error.html_safe}").removeClass("hidden"); diff --git a/app/views/admin/users/edit.html.slim b/app/views/admin/users/edit.html.slim index fb5c4e9ad..ff353e103 100644 --- a/app/views/admin/users/edit.html.slim +++ b/app/views/admin/users/edit.html.slim @@ -3,3 +3,13 @@ = "Edit #{controller_name == "users" ? "applicant" : controller_name.singularize}" = render 'form', resource: @resource + +- if collaborators + .panel.panel-default[data-controller="element-focus"] + .panel-heading id="section-collaborators-header" + h2.panel-title + a.collapsed data-toggle="collapse" data-parent="#user-form-panel" href="#section-collaborators" aria-expanded="false" aria-controls="section-collaborators" data-element-focus-target="reveal" + ' Collaborators + #section-collaborators.section-collaborators.panel-collapse.collapse[aria-labelledby="section-collaborators-header" data-element-scroll-target="accordion"] + .panel-body + = render "fields_collaborators", resource: @resource diff --git a/config/routes.rb b/config/routes.rb index 644fbbbf6..6e93fb78e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -263,6 +263,10 @@ patch :unlock post :scan_via_debounce_api end + + resources :collaborators, only: [:create], module: :users do + get :search, on: :collection + end end resources :collaborator_deletion, only: [:destroy] diff --git a/spec/features/admin/users/collaborators_spec.rb b/spec/features/admin/users/collaborators_spec.rb new file mode 100644 index 000000000..fc1067bb2 --- /dev/null +++ b/spec/features/admin/users/collaborators_spec.rb @@ -0,0 +1,155 @@ +require "rails_helper" + +describe "Collaborators", ' +As a an Admin +I want to be able to add collaborators to any account +So that they can collaborate applications +', js: true do + include ActiveJob::TestHelper + + let!(:admin) { create(:admin) } + let(:existing_user) { create(:user, :completed_profile) } + + before do + login_admin admin + visit edit_admin_user_path(existing_user) + end + + def search_for_user(email) + find("a[aria-controls='section-collaborators']").click + + within(".admin-search-collaborators-form") do + fill_in "search[query]", with: email.to_s[2..-2] + first("input[type='submit']").click + end + end + + describe "Add Collaborator" do + let(:email) { generate(:email) } + let!(:user) { create(:user, email: email) } + let(:form_answers) { [] } + + context "with the Account Admin role" do + let(:role) { "Account admin" } + + before do + search_for_user(email) + transfer_user(role) + end + + it "should transfer the user with the correct role" do + expect(page).to have_content("#{email} successfully added to Collaborators!") + expect(page).to have_content("#{email} successfully added to Collaborators!") + login_as existing_user + click_link "Collaborators" + expect_to_see_user(user, role: "Admin and collaborator") + end + end + + context "with the regular role" do + let(:role) { "Regular" } + + before do + search_for_user(email) + transfer_user(role) + end + + it "should transfer the user with the correct role" do + expect(page).to have_content("#{email} successfully added to Collaborators!") + expect(page).to have_content("#{email} successfully added to Collaborators!") + login_as existing_user + click_link "Collaborators" + expect_to_see_user(user, role: "Collaborator only") + end + end + + context "with form_answers" do + let!(:form_answers) { create_list(:form_answer, 3, user: user, account: user.account) } + let(:role) { "Account admin" } + context "when transferring form_answers" do + before do + search_for_user(email) + transfer_user(role, transfer_form_answers: true) + end + + it "should transfer the form_answers and user with the correct role" do + expect(page).to have_content("#{email} successfully added to Collaborators!") + login_as existing_user, scope: :user + click_link "Collaborators" + expect_to_see_user(user, role: "Admin and collaborator") + expect(existing_user.account.reload.form_answers).to eq(form_answers) + expect(user.reload.form_answers).to eq(form_answers) + end + end + + context "when not transferring form_answers" do + context "with no other users" do + before do + search_for_user(email) + transfer_user(role, transfer_form_answers: false) + end + + it "should transfer the user with the correct role" do + expect(page).to have_content("#{email} successfully added to Collaborators!") + login_as existing_user + click_link "Collaborators" + expect_to_see_user(user, role: "Admin and collaborator") + expect(existing_user.account.reload.form_answers).to eq([]) + expect(user.reload.form_answers).to eq([]) + end + end + + context "with other users" do + let!(:other_user) { create(:user, :completed_profile, account: user.account) } + + before do + search_for_user(email) + transfer_user(role, transfer_form_answers: false, new_owner: other_user) + end + + it "should transfer the user with the correct role" do + expect(page).to have_content("#{email} successfully added to Collaborators!") + login_as existing_user + click_link "Collaborators" + expect_to_see_user(user, role: "Admin and collaborator") + expect(existing_user.account.reload.form_answers).to eq([]) + expect(user.reload.form_answers).to eq([]) + expect(other_user.reload.form_answers).to eq(form_answers) + end + end + + context "when the form_answers are in progress" do + let!(:form_answers) { create_list(:form_answer, 3, :development, user: user, account: user.account) } + + before do + search_for_user(email) + transfer_user(role, transfer_form_answers: false) + end + + it "should display an error" do + expect(page).to have_content( + "#{user.email} could not be added to Collaborators: User has applications in progress, and there are no other users on the account to transfer them to", + ) + end + end + end + end + end + + def expect_to_see_user(user, role:) + within "#user_#{user.id}" do + expect(page).to have_content(user.full_name) + expect(page).to have_content(role) + end + end + + def transfer_user(role, transfer_form_answers: false, new_owner: nil) + within(".js-admin-search-collaborators-results-box") do + select role, from: "user[role]" + select new_owner.email, from: "user[new_owner_id]" if new_owner + check "user[transfer_form_answers]" if transfer_form_answers + + click_button "Transfer" + end + end +end