diff --git a/app/assets/javascripts/access_permissions/projects.js b/app/assets/javascripts/access_permissions/user_assignments.js similarity index 95% rename from app/assets/javascripts/access_permissions/projects.js rename to app/assets/javascripts/access_permissions/user_assignments.js index 8e1a7354d..c2eecd7ae 100644 --- a/app/assets/javascripts/access_permissions/projects.js +++ b/app/assets/javascripts/access_permissions/user_assignments.js @@ -2,7 +2,7 @@ 'use strict'; function initNewUserAssignmentFormListener() { - $(document).on('change', 'form#new-user-assignment-to-project-form', function() { + $(document).on('change', 'form#new-user-assignment-to-project-form, form#new-user-assignment-to-protocol-form', function() { let values = []; let count = 0; let submitBtn = $(this).find('input[type="submit"]'); diff --git a/app/controllers/access_permissions/protocols_controller.rb b/app/controllers/access_permissions/protocols_controller.rb new file mode 100644 index 000000000..d8a2f4344 --- /dev/null +++ b/app/controllers/access_permissions/protocols_controller.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +module AccessPermissions + class ProtocolsController < ApplicationController + before_action :set_protocol + before_action :check_read_permissions, only: %i(show) + before_action :check_manage_permissions, except: %i(show) + + def new + @user_assignment = UserAssignment.new(assignable: @project, assigned_by: current_user) + + respond_to do |format| + format.json + end + end + + def show + respond_to do |format| + format.json + end + end + + def edit + respond_to do |format| + format.json + end + end + + def update + @user_assignment = UserAssignment.find_by(user_id: permitted_update_params[:user_id], assignable: @protocol) + @user_assignment.update(permitted_update_params) + respond_to do |format| + format.json do + render :protocol_member + end + end + end + + def create + ActiveRecord::Base.transaction do + permitted_create_params[:resource_members].each do |_k, user_assignment_params| + next unless user_assignment_params[:assign] == '1' + + user_assignment = UserAssignment.new(user_assignment_params) + user_assignment.assignable = @protocol + user_assignment.assigned_by = current_user + user_assignment.save! + end + + respond_to do |format| + @message = t('access_permissions.create.success', count: @protocol.user_assignments.count) + format.json { render :edit } + end + rescue ActiveRecord::RecordInvalid + respond_to do |format| + @message = t('access_permissions.create.failure') + format.json { render :new } + end + end + end + + def destroy + user = @protocol.assigned_users.find(params[:user_id]) + user_assignment = @protocol.user_assignments.find_by(user_id: params[:user_id]) + respond_to do |format| + if user_assignment.destroy + format.json do + render json: { flash: t('access_permissions.destroy.success', member_name: user.full_name) }, + status: :ok + end + else + format.json do + render json: { flash: t('access_permissions.destroy.failure') }, + status: :unprocessable_entity + end + end + end + end + + private + + def permitted_update_params + params.require(:user_assignment) + .permit(%i(user_role_id user_id)) + end + + def permitted_create_params + params.require(:access_permissions_new_user_protocol_form) + .permit(resource_members: %i(assign user_id user_role_id)) + end + + def set_protocol + @protocol = current_team.protocols.includes(user_assignments: %i(user user_role)).find_by(id: params[:id]) + + render_404 unless @protocol + end + + def check_manage_permissions + render_403 unless can_manage_protocol_users?(@protocol) + end + + def check_read_permissions + render_403 unless can_read_protocol_in_repository?(@protocol) + end + end +end diff --git a/app/helpers/user_assignments_helper.rb b/app/helpers/user_assignments_helper.rb index be0731d3d..c5167bd5a 100644 --- a/app/helpers/user_assignments_helper.rb +++ b/app/helpers/user_assignments_helper.rb @@ -12,7 +12,7 @@ module UserAssignmentsHelper def user_assignment_resource_role_name(user, resource, inherit = '') user_assignment = resource.user_assignments.find_by(user: user) - if resource.class != Project && user_assignment.automatically_assigned? + if ![Project, Protocol].include?(resource.class) && user_assignment.automatically_assigned? parent = resource.permission_parent return user_assignment_resource_role_name(user, parent, '_inherit') end diff --git a/app/models/concerns/assignable.rb b/app/models/concerns/assignable.rb index 59960e30a..00b3f228f 100644 --- a/app/models/concerns/assignable.rb +++ b/app/models/concerns/assignable.rb @@ -42,6 +42,10 @@ module Assignable User.joins(:user_assignments).where(user_assignments: { assigned: :manually, assignable: self }) end + def assigned_users + User.joins(:user_assignments).where(user_assignments: { assignable: self }) + end + private def create_users_assignments diff --git a/app/models/user_assignment.rb b/app/models/user_assignment.rb index 210dd3e0e..fa09bd1a7 100644 --- a/app/models/user_assignment.rb +++ b/app/models/user_assignment.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class UserAssignment < ApplicationRecord + attr_accessor :assign + before_validation -> { self.team ||= (assignable.is_a?(Team) ? assignable : assignable.team) } after_create :assign_team_child_objects, if: -> { assignable.is_a?(Team) } after_update :update_team_children_assignments, if: -> { assignable.is_a?(Team) && saved_change_to_user_role_id? } @@ -16,6 +18,10 @@ class UserAssignment < ApplicationRecord validates :user, uniqueness: { scope: %i(assignable team_id) } + def last_assignable_owner? + assignable_owners.count == 1 && user_role.owner? + end + private def assign_team_child_objects @@ -29,4 +35,10 @@ class UserAssignment < ApplicationRecord def unassign_team_child_objects UserAssignments::RemoveTeamUserAssignmentsService.new(self).call end + + def assignable_owners + @assignable_owners ||= assignable.user_assignments + .includes(:user_role) + .where(user_roles: { name: I18n.t('user_roles.predefined.owner') }) + end end diff --git a/app/views/access_permissions/partials/_default_public_user_role_form.html.erb b/app/views/access_permissions/partials/_default_public_user_role_form.html.erb index 1e13d77b8..96f9a9d8e 100644 --- a/app/views/access_permissions/partials/_default_public_user_role_form.html.erb +++ b/app/views/access_permissions/partials/_default_public_user_role_form.html.erb @@ -5,7 +5,7 @@
<%= t('access_permissions.everyone_else', team_name: project.team.name) %> - <%= render 'access_permissions/partials/public_members_dropdown', team: project.team, project: project %> + <%= render 'access_permissions/partials/public_members_dropdown', team: project.team, assignable: project %>
<%= project.default_public_user_role.name %> diff --git a/app/views/access_permissions/partials/_new_protocol_assignments_form.html.erb b/app/views/access_permissions/partials/_new_protocol_assignments_form.html.erb new file mode 100644 index 000000000..a049f22f6 --- /dev/null +++ b/app/views/access_permissions/partials/_new_protocol_assignments_form.html.erb @@ -0,0 +1,29 @@ +<% # frozen_string_literal: true %> + + diff --git a/app/views/access_permissions/partials/_protocol_member_field.html.erb b/app/views/access_permissions/partials/_protocol_member_field.html.erb new file mode 100644 index 000000000..ae047d377 --- /dev/null +++ b/app/views/access_permissions/partials/_protocol_member_field.html.erb @@ -0,0 +1,33 @@ +<% # frozen_string_literal: true %> + +<% + protocol_assignment = UserAssignment.find_by(user_id: user.id, assignable: protocol) + item_id = dom_id(user, :protocol_member) +%> + +<%= form_with(model: protocol_assignment, url: update_path, method: :put, remote: true, html: { class: 'row member-item', id: item_id, data: { action: 'replace-form autosave-form', object_type: :protocol } }) do |f| %> + <%= f.hidden_field :user_id, value: f.object.user.id %> +
+
+ <%= image_tag avatar_path(user, :icon_small), title: current_assignee_name(user), class: 'img-circle pull-left' %> +
+
+ <%= current_assignee_name(user) %> +
+ <%= user_assignment_resource_role_name(user, protocol) %> +
+
+
+
+ <%= f.select :user_role_id, options_for_select(user_roles_collection, selected: f.object.user_role.id), {}, class: 'form-control selectpicker', title: t('user_assignment.change_protocol_role'), data: { 'selected-text-format' => 'static' } %> +
+
+ <% unless protocol_assignment.last_assignable_owner? %> + <%= link_to access_permissions_protocol_path(protocol, user_id: user), remote: true, method: :delete, class: 'btn btn-secondary', data: { action: 'remote-destroy', target: "##{item_id}" } do %> + + <%= t 'general.remove' %> + <% end %> + <% end %> +
+
+<% end %> diff --git a/app/views/access_permissions/partials/_protocol_user_assignment_field.html.erb b/app/views/access_permissions/partials/_protocol_user_assignment_field.html.erb new file mode 100644 index 000000000..6ff5e9ac0 --- /dev/null +++ b/app/views/access_permissions/partials/_protocol_user_assignment_field.html.erb @@ -0,0 +1,36 @@ +<% # frozen_string_literal: true %> + +<% + user = user_form.object.user + id = dom_id(user, :new_protocol_member) +%> + +
+ <%= user_form.hidden_field :user_id, value: user.id, name:"access_permissions_new_user_protocol_form[resource_members][#{user.id}][user_id]" %> +
+
+ <%= user_form.check_box :assign, + name: "access_permissions_new_user_protocol_form[resource_members][#{user.id}][assign]", + data: { action: 'toggle-visibility', target: id }, + class: "sci-checkbox" + %> + +
+
+ <%= image_tag avatar_path(user, :icon_small), title: current_assignee_name(user), class: 'img-circle pull-left' %> +
+
+ <%= current_assignee_name(user) %> +
+
+
+ +
+
diff --git a/app/views/access_permissions/partials/_public_members_dropdown.html.erb b/app/views/access_permissions/partials/_public_members_dropdown.html.erb index dd7c7a362..16c32b32b 100644 --- a/app/views/access_permissions/partials/_public_members_dropdown.html.erb +++ b/app/views/access_permissions/partials/_public_members_dropdown.html.erb @@ -5,7 +5,8 @@
<%= t('.title', team: team.name) %>
- <% team.users.order(full_name: :asc).where.not(id: project.manually_assigned_users.select(:id)).each do |user| %> + <% users_excluded_id = assignable.manually_assigned_users.select(:id) %> + <% team.users.order(full_name: :asc).where.not(id: users_excluded_id).each do |user| %>
<%= user.full_name %>
diff --git a/app/views/access_permissions/protocols/edit.json.jbuilder b/app/views/access_permissions/protocols/edit.json.jbuilder new file mode 100644 index 000000000..3cd9a7ec5 --- /dev/null +++ b/app/views/access_permissions/protocols/edit.json.jbuilder @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +json.modal controller.render_to_string( + partial: 'access_permissions/protocols/modals/edit_modal', + formats: [:html], + locals: { + protocol: @protocol, + update_path: access_permissions_protocol_path(@protocol), + new_resource_path: new_access_permissions_protocol_path(id: @protocol) + }, + layout: false +) + +json.flash @message diff --git a/app/views/access_permissions/protocols/modals/_edit_modal.html.erb b/app/views/access_permissions/protocols/modals/_edit_modal.html.erb new file mode 100644 index 000000000..c081e8fdf --- /dev/null +++ b/app/views/access_permissions/protocols/modals/_edit_modal.html.erb @@ -0,0 +1,24 @@ +<% # frozen_string_literal: true %> + + diff --git a/app/views/access_permissions/protocols/modals/_show_modal.html.erb b/app/views/access_permissions/protocols/modals/_show_modal.html.erb new file mode 100644 index 000000000..371f4e0ae --- /dev/null +++ b/app/views/access_permissions/protocols/modals/_show_modal.html.erb @@ -0,0 +1,18 @@ +<% # frozen_string_literal: true %> + + diff --git a/app/views/access_permissions/protocols/new.json.jbuilder b/app/views/access_permissions/protocols/new.json.jbuilder new file mode 100644 index 000000000..f0796a3de --- /dev/null +++ b/app/views/access_permissions/protocols/new.json.jbuilder @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +json.html controller.render_to_string( + partial: 'access_permissions/partials/new_protocol_assignments_form', + formats: [:html], + locals: { + resource: @protocol, + form_object: @user_assignment, + users: current_team.users.where.not(id: @protocol.assigned_users.select(:id)), + create_path: access_permissions_protocols_path(id: @protocol.id), + resource_path: edit_access_permissions_protocol_path(@protocol) + }, + layout: false +) diff --git a/app/views/access_permissions/protocols/protocol_member.json.jbuilder b/app/views/access_permissions/protocols/protocol_member.json.jbuilder new file mode 100644 index 000000000..7024964e9 --- /dev/null +++ b/app/views/access_permissions/protocols/protocol_member.json.jbuilder @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +json.form controller.render_to_string( + partial: 'access_permissions/partials/protocol_member_field', + formats: [:html], + locals: { + user: @user_assignment.user, + protocol: @protocol, + update_path: access_permissions_protocol_path(@protocol) + }, + layout: false +) diff --git a/app/views/access_permissions/protocols/show.json.jbuilder b/app/views/access_permissions/protocols/show.json.jbuilder new file mode 100644 index 000000000..faedde483 --- /dev/null +++ b/app/views/access_permissions/protocols/show.json.jbuilder @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +json.modal controller.render_to_string( + partial: 'access_permissions/protocols/modals/show_modal', + formats: [:html], + locals: { + protocol: @protocol, + users: @protocol.assigned_users, + can_manage_resource: can_manage_protocol_users?(@protocol) + }, + layout: false +) diff --git a/config/locales/en.yml b/config/locales/en.yml index c40e6c62e..f6ddb8db4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3036,6 +3036,7 @@ en: user_assignment: current_assignee: "(you)" change_project_role: "Change project role" + change_protocol_role: "Change protocol role" select_default_user_role: "Select default user role" change_experiment_role: "Change experiment role" change_my_module_role: "Change task role" @@ -3069,6 +3070,12 @@ en: submit_singular: "Grant access to 1 user" submit_plural: "Grant access to {num} users" find_people_html: "Find people" + new_protocol_assignments_form: + title: "Select members" + submit: "Grant access" + submit_singular: "Grant access to 1 user" + submit_plural: "Grant access to {num} users" + find_people_html: "Find people" experiment_member_field: reset: "Inherit role" reset_description: "The inherited role from project will be applied" @@ -3078,6 +3085,9 @@ en: project: "Project" project_tooltip: "This role was set on this project." project_tooltip_inherit: "This role was inherited from the project." + protocol: "Protocol" + protocol_tooltip: "This role was set on this protocol." + protocol_tooltip_inherit: "This role was inherited from the protocol." experiment: "Experiment" experiment_tooltip: "This role was set on this experiment." experiment_tooltip_inherit: "This role was inherited from the experiment." @@ -3091,6 +3101,14 @@ en: show_modal: title: "Access to %{resource_name}" new_resource_assignments: "Grant new access to %{resource}" + protocols: + modals: + show_modal: + title: "Access to %{resource_name}" + new_resource_assignments: "Grant new access to %{resource}" + edit_modal: + title: "Manage access for %{resource_name}" + new_resource_assignments: "Grant new access to %{resource}" experiments: modals: show_modal: diff --git a/config/routes.rb b/config/routes.rb index 8c4daea22..ad1996c64 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -280,6 +280,7 @@ Rails.application.routes.draw do end namespace :access_permissions do + resources :protocols, defaults: { format: 'json' } resources :projects, defaults: { format: 'json' } do put :update_default_public_user_role, on: :member resources :experiments, only: %i(show update edit) do