diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index 8749757d4..44fd6801f 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -44,6 +44,7 @@ //= require shared/remote_modal //= require shared/swap_remote_container //= require shared/autosave_form +//= require shared/replace_form //= require activestorage //= require global_activities/side_pane //= require protocols/header diff --git a/app/assets/javascripts/projects/index.js b/app/assets/javascripts/projects/index.js index f6bb51855..5bd3672fc 100644 --- a/app/assets/javascripts/projects/index.js +++ b/app/assets/javascripts/projects/index.js @@ -229,23 +229,6 @@ manageProjectUsersModal.find('.modal-footer').html(data.html_footer); } - // Initialize manage project users modal remote loading. - function initManageProjectUsersLink() { - $(projectsWrapper).on('ajax:success', '.manage-project-users-link', function(e, data) { - initManageProjectUsersModalBody(data); - manageProjectUsersModal.modal('show'); - }); - } - - // Initialize view project users modal remote loading. - function initViewProjectUsersLink() { - $('#cardsWrapper').on('ajax:success', '.view-project-users-link', function(e, data) { - let viewProjectUsersModal = $('#viewProjectUsersModal'); - viewProjectUsersModal.find('.modal-title').html(data.html_title); - viewProjectUsersModal.find('.modal-body').html(data.html_body); - viewProjectUsersModal.modal('show'); - }); - } // Initialize reloading manage user modal content after posting new // user. @@ -637,8 +620,6 @@ initExportProjectsModal(); initExportProjects(); initArchiveRestoreToolbarButtons(); - initViewProjectUsersLink(); - initManageProjectUsersLink(); initAddUserForm(); initRemoveUserLinks(); initUserRoleForms(); diff --git a/app/assets/javascripts/shared/autosave_form.js b/app/assets/javascripts/shared/autosave_form.js index cf7169919..591ab6238 100644 --- a/app/assets/javascripts/shared/autosave_form.js +++ b/app/assets/javascripts/shared/autosave_form.js @@ -7,5 +7,5 @@ }) } - $(document).on('turbolinks:load', initAutosaveListeners); + $(document).one('turbolinks:load', initAutosaveListeners); })(); diff --git a/app/assets/javascripts/shared/remote_modal.js b/app/assets/javascripts/shared/remote_modal.js index 996a8c4f0..829e3bb08 100644 --- a/app/assets/javascripts/shared/remote_modal.js +++ b/app/assets/javascripts/shared/remote_modal.js @@ -2,10 +2,11 @@ 'use strict'; function initRemoteModalListeners() { - $(document).on('click', 'a[data-action="remote-modal"]', function(el) { - el.stopPropagation(); - el.preventDefault(); - $.get(el.target.getAttribute('href')).then(function({modal}) { + $(document).on('click', 'a[data-action="remote-modal"]', function(ev) { + ev.stopImmediatePropagation(); + ev.stopPropagation(); + ev.preventDefault(); + $.get(ev.currentTarget.getAttribute('href')).then(function({modal}) { $(modal).modal('show') .on("shown.bs.modal", function() { $(this).find(".selectpicker").selectpicker(); @@ -14,5 +15,5 @@ }) } - $(document).on('turbolinks:load', initRemoteModalListeners); + $(document).one('turbolinks:load', initRemoteModalListeners); })(); diff --git a/app/assets/javascripts/shared/replace_form.js b/app/assets/javascripts/shared/replace_form.js new file mode 100644 index 000000000..87b6e5872 --- /dev/null +++ b/app/assets/javascripts/shared/replace_form.js @@ -0,0 +1,13 @@ +(function () { + 'use strict'; + + function initReplaceFormListeners() { + $(document).on('ajax:success', 'form[data-action="replace-form"]', function({ form }) { + let newForm = $(form) + $(this).replaceWith(newForm); + newForm.find('.selectpicker').selectpicker(); + }) + } + + $(document).one('turbolinks:load', initReplaceFormListeners); +})(); diff --git a/app/assets/javascripts/shared/swap_remote_container.js b/app/assets/javascripts/shared/swap_remote_container.js index b90ae9d0d..1f8b7fa5f 100644 --- a/app/assets/javascripts/shared/swap_remote_container.js +++ b/app/assets/javascripts/shared/swap_remote_container.js @@ -2,17 +2,21 @@ 'use strict'; function initSwapRemoteContainerListeners() { - $(document).on('click', 'a[data-action="swap-remote-container"]', function(el) { - let element = el.target; - el.stopPropagation(); - el.preventDefault(); + $(document).on('click', 'a[data-action="swap-remote-container"]', function(ev) { + let element = ev.currentTarget; + ev.stopImmediatePropagation(); + ev.stopPropagation(); + ev.preventDefault(); $.get(element.getAttribute('href')).then(function({html}) { - let target = element.getAttribute('data-target') - document.getElementById(target).insertAdjacentHTML(html) + let targetID = element.getAttribute('data-target') + let targetElement = $(element).closest(targetID) + let newContainer = $(html) + targetElement.replaceWith(newContainer) + newContainer.find('.selectpicker').selectpicker(); }) }) } - $(document).on('turbolinks:load', initSwapRemoteContainerListeners); + $(document).one('turbolinks:load', initSwapRemoteContainerListeners); })(); diff --git a/app/controllers/access_permissions/projects_controller.rb b/app/controllers/access_permissions/projects_controller.rb index 000a9758c..281adb2cd 100644 --- a/app/controllers/access_permissions/projects_controller.rb +++ b/app/controllers/access_permissions/projects_controller.rb @@ -6,6 +6,19 @@ module AccessPermissions before_action :check_read_permissions, only: %i[show] before_action :check_manage_permissions, only: %i[new create edit update destroy] + def new + available_users = current_team.users.where.not(id: @project.users.pluck(:id)) + @form = AccessPermissions::NewUserProjectForm.new( + current_user, + @project, + users: available_users + ) + + respond_to do |format| + format.json + end + end + def show respond_to do |format| format.json @@ -30,6 +43,17 @@ module AccessPermissions end end + def create + @form = AccessPermissions::NewUserProjectForm.new(current_user, @project) + @form.resource_members = permitted_create_params + + flash[:notice] = "Success" if @form.save + + respond_to do |format| + format.json :new + end + end + private def permitted_update_params @@ -37,6 +61,11 @@ module AccessPermissions .permit(user_assignments_attributes: %i[user_role_id _destroy id]) end + def permitted_create_params + params.require(:access_permissions_new_user_project_form) + .permit(resource_members: %i[assign user_id user_role_id]) + end + def set_project @project = Project.includes(user_assignments: [:user, :user_role]).find_by(id: params[:id]) diff --git a/app/forms/access_permissions/new_user_project_form.rb b/app/forms/access_permissions/new_user_project_form.rb new file mode 100644 index 000000000..87759f724 --- /dev/null +++ b/app/forms/access_permissions/new_user_project_form.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module AccessPermissions + class NewUserProjectForm + include ActiveModel::Model + + attr_accessor :project, :resource_members + attr_reader :current_user + + def initialize(current_user, project, attributes = {}) + @project = project + @users = attributes[:users] + @current_user = current_user + set_defaults if @users + @error = false + end + + def save + if @error + false + else + @resource_members.map(&:save) + true + end + end + + def resource_members=(attributes) + @resource_members ||= [] + attributes.fetch(:resource_members).each do |i, resource_member| + user = User.find(resource_member[:user_id]) + project_member = ProjectMember.new(user, @project, current_user) + project_member.assign = resource_member[:assign] + project_member.user_role_id = resource_member[:user_role_id] + byebug + @error = true unless project_member.valid? + + @resource_members << project_member + end + end + + private + + def set_defaults + @resource_members ||= @users.order(:full_name).map { |u| ProjectMember.new(u, @project, current_user) } + end + end +end + diff --git a/app/helpers/user_roles_helper.rb b/app/helpers/user_roles_helper.rb index 30cacea7a..275832504 100644 --- a/app/helpers/user_roles_helper.rb +++ b/app/helpers/user_roles_helper.rb @@ -3,7 +3,7 @@ module UserRolesHelper def user_roles_collection Rails.cache.fetch([current_user, 'available_user_roles']) do - @user_roles_collection ||= UserRole.all.pluck(:name, :id) + @user_roles_collection ||= [[t('user_assignment.select.default_option'), nil]] + UserRole.all.pluck(:name, :id) end end end diff --git a/app/models/project_member.rb b/app/models/project_member.rb new file mode 100644 index 000000000..b25e6fa1c --- /dev/null +++ b/app/models/project_member.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +class ProjectMember + include ActiveModel::Model + + attr_accessor :user, :project, :assign, :user_role_id, :user_id + attr_reader :current_user + + validates :user, :project, :user_role_id, presence: true + validate :role_presence + validate :validate_user_project_relation_presence + validate :validate_user_project_assignment_presence + + def initialize(user, project, current_user) + @user = user + @project = project + @current_user = current_user + end + + def save + return unless assign + + ActiveRecord::Base.transaction do + UserProject.create!(project: @project, user: @user) + UserAssignment.create!(assignable: @project, user: @user, user_role: set_user_role, assigned_by: current_user) + end + end + + def assign=(value) + @assign = ActiveModel::Type::Boolean.new.cast(value) + end + + private + + def set_user_role + UserRole.find!(user_role_id) + end + + def role_presence + errors.add(:user_role_id) if UserRole.find(user_role_id).nil? + end + + def validate_user_project_relation_presence + if UserProject.find_by(project: @project, user: @user).present? + errors.add(:user) + end + end + + def validate_user_project_assignment_presence + if UserAssignment.find_by(assignable: @project, user: @user).present? + errors.add(:user_role_id) + end + end +end diff --git a/app/views/access_permissions/modals/_edit_modal.html.erb b/app/views/access_permissions/modals/_edit_modal.html.erb index 2c696c57c..1a20835f9 100644 --- a/app/views/access_permissions/modals/_edit_modal.html.erb +++ b/app/views/access_permissions/modals/_edit_modal.html.erb @@ -1,7 +1,7 @@ <% # frozen_string_literal: true %>