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 %>
+
+
+
+ <%= form_with(url: create_path, method: :post, remote: true, html: { id: 'new-user-assignment-to-protocol-form', data: { action: 'replace-form', target: '#user_assignments_modal', object_type: resource.class.to_s.downcase} }) do |f| %>
+
+
+ <%= text_field_tag :search_users, '', placeholder: t('.find_people_html'), class: 'sci-input-field', data: { action: 'filter-list', target: 'new-user-assignment-to-project-form' } %>
+
+
+ <% users.each do |user| %>
+ <%= f.fields_for :users, UserAssignment.new(user: user) do |user_form| %>
+ <%= render 'access_permissions/partials/protocol_user_assignment_field.html.erb', user_form: user_form %>
+ <% end %>
+ <% end %>
+
+
+ <% end %>
+
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) %>
+
+
+
+
+ <%= user_form.select :user_role_id,
+ options_for_select(user_roles_collection),
+ {},
+ name: "access_permissions_new_user_protocol_form[resource_members][#{user.id}][user_role_id]",
+ class: 'form-control selectpicker pull-right',
+ title: t('user_assignment.select_role') %>
+
+
+
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 %>
+
+
+
+
+
+
+ <% protocol.assigned_users.order(full_name: :asc).each do |user| %>
+ <%= render('access_permissions/partials/protocol_member_field', user: user, protocol: protocol, update_path: update_path) %>
+ <% end %>
+
+
+
+
+
+
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 %>
+
+
+
+
+
+
+ <% users.order(full_name: :asc).each do |user| %>
+ <% user_assignment = protocol.user_assignments.find_by(user: user) %>
+ <%= render partial: 'access_permissions/partials/user_assignment', locals: { user_assignment: user_assignment, user: user, resource: protocol } %>
+ <% end %>
+
+
+
+
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