fix the logic for addin/editing new project members

This commit is contained in:
zmagoD 2021-04-28 18:02:18 +02:00
parent c136d953d0
commit 8a74546cdf
20 changed files with 202 additions and 92 deletions

View file

@ -45,6 +45,7 @@
//= require shared/swap_remote_container
//= require shared/autosave_form
//= require shared/replace_form
//= require shared/remote_destroy
//= require activestorage
//= require global_activities/side_pane
//= require protocols/header

View file

@ -2,8 +2,8 @@
'use strict';
function initAutosaveListeners() {
$(document).on('change', 'form[data-action="autosave-form"]', function({ currentTarget }) {
currentTarget.submit()
$(document).on('change', 'form[data-action*="autosave-form"]', function({ currentTarget }) {
$.rails.fire($(currentTarget), 'submit')
})
}

View file

@ -0,0 +1,16 @@
(function () {
'use strict';
function initRemoteDestroyListeners() {
$(document).on('ajax:success', 'a[data-action*="remote-destroy"]', function({ currentTarget }) {
let target = currentTarget.getAttribute('data-target')
document.querySelector(target).remove()
})
$(document).on('ajax:error', 'a[data-action*="remote-destroy"]', function(_, data) {
HelperModule.flashAlertMsg(data.responseJSON.flash, 'danger');
})
}
$(document).one('turbolinks:load', initRemoteDestroyListeners);
})();

View file

@ -2,9 +2,15 @@
'use strict';
function initReplaceFormListeners() {
$(document).on('ajax:success', 'form[data-action="replace-form"]', function({ form }) {
$(document).on('ajax:success', 'form[data-action*="replace-form"]', function(_, {form}) {
let newForm = $(form)
$(this).replaceWith(newForm);
let target = this.getAttribute('data-target');
if (target) {
$(target).replaceWith(newForm);
} else {
$(this).replaceWith(newForm);
}
newForm.find('.selectpicker').selectpicker();
})
}

View file

@ -32,13 +32,12 @@ module AccessPermissions
end
def update
@form = AccessPermissions::EditUserProjectForm.new(current_user, @project)
@form.update(permitted_update_params)
respond_to do |format|
format.json do
if @project.update(permitted_update_params)
head :no_content
else
render :edit
end
render :project_member
end
end
end
@ -47,18 +46,32 @@ module AccessPermissions
@form = AccessPermissions::NewUserProjectForm.new(current_user, @project)
@form.resource_members = permitted_create_params
flash[:notice] = "Success" if @form.save
respond_to do |format|
if @form.save
format.json { render :edit }
else
format.json { render :new }
end
end
end
def destroy
user = @project.users.find(params[:user_id])
project_member = ProjectMember.new(user, @project)
project_member.destroy
respond_to do |format|
format.json :new
format.json do
render json: { status: :ok }
end
end
end
private
def permitted_update_params
params.require(:project)
.permit(user_assignments_attributes: %i[user_role_id _destroy id])
params.require(:project_member)
.permit(%i[user_role_id user_id])
end
def permitted_create_params
@ -67,7 +80,7 @@ module AccessPermissions
end
def set_project
@project = Project.includes(user_assignments: [:user, :user_role]).find_by(id: params[:id])
@project = current_team.projects.includes(user_assignments: [:user, :user_role]).find_by(id: params[:id])
render_404 unless @project
end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
module AccessPermissions
class EditUserProjectForm
include ActiveModel::Model
attr_accessor :user, :project
def initialize(current_user, project)
@project = project
@current_user = current_user
end
def update(params)
@user = project.users.find(params[:user_id])
project_member = ProjectMember.new(user, project)
project_member.user_role_id = params[:user_role_id]
project_member.update
end
end
end

View file

@ -19,7 +19,7 @@ module AccessPermissions
if @error
false
else
@resource_members.map(&:save)
@resource_members.map(&:create)
true
end
end
@ -31,7 +31,6 @@ module AccessPermissions
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

View file

@ -3,7 +3,7 @@
module UserRolesHelper
def user_roles_collection
Rails.cache.fetch([current_user, 'available_user_roles']) do
@user_roles_collection ||= [[t('user_assignment.select.default_option'), nil]] + UserRole.all.pluck(:name, :id)
@user_roles_collection ||= UserRole.all.pluck(:name, :id)
end
end
end

View file

@ -371,7 +371,7 @@ class Project < ApplicationRecord
UserAssignment.create(
user: created_by,
assignable: self,
user_role: UserRole.owner_role
user_role: UserRole.find_by(name: 'Owner')
)
end
end

View file

@ -4,20 +4,24 @@ class ProjectMember
include ActiveModel::Model
attr_accessor :user, :project, :assign, :user_role_id, :user_id
attr_reader :current_user
attr_reader :current_user, :user_assignment, :user_role
validates :user, :project, :user_role_id, presence: true
validate :role_presence
validate :validate_user_project_relation_presence
validate :validate_user_project_assignment_presence
delegate :user_role, to: :user_assignment, allow_nil: true
def initialize(user, project, current_user)
validates :user, :project, presence: true, if: -> { assign }
validates :user_role_id, presence: true, if: -> { assign }
validate :validate_role_presence, if: -> { assign }
validate :validate_user_project_relation_presence, if: -> { assign }
validate :validate_user_project_assignment_presence, if: -> { assign }
def initialize(user, project, current_user = nil)
@user = user
@project = project
@current_user = current_user
@user_assignment = UserAssignment.find_by(assignable: @project, user: @user)
end
def save
def create
return unless assign
ActiveRecord::Base.transaction do
@ -26,6 +30,23 @@ class ProjectMember
end
end
def update
validate_role_presence
return false unless valid?
user_assignment = UserAssignment.find_by!(assignable: @project, user: @user)
user_assignment.update(user_role: set_user_role)
end
def destroy
user_assignment = UserAssignment.find_by!(assignable: @project, user: @user)
user_project = UserProject.find_by!(project: @project, user: @user)
ActiveRecord::Base.transaction do
user_assignment.destroy!
user_project.destroy!
end
end
def assign=(value)
@assign = ActiveModel::Type::Boolean.new.cast(value)
end
@ -33,11 +54,11 @@ class ProjectMember
private
def set_user_role
UserRole.find!(user_role_id)
UserRole.find(user_role_id)
end
def role_presence
errors.add(:user_role_id) if UserRole.find(user_role_id).nil?
def validate_role_presence
errors.add(:user_role_id, :not_found) if UserRole.find_by(id: user_role_id).nil?
end
def validate_user_project_relation_presence
@ -48,7 +69,7 @@ class ProjectMember
def validate_user_project_assignment_presence
if UserAssignment.find_by(assignable: @project, user: @user).present?
errors.add(:user_role_id)
errors.add(:user_role_id, :already_present)
end
end
end

View file

@ -1,9 +1,9 @@
# frozen_string_literal: true
class UserProject < ApplicationRecord
# TODO: Remove this from DB (Ask Alex)
enum role: { owner: 0, normal_user: 1, technician: 2, viewer: 3 }
validates :role, presence: true
validates :user, presence: true, uniqueness: { scope: :project }
validates :project, presence: true

View file

@ -1,25 +0,0 @@
<% # frozen_string_literal: true %>
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="user_assignments_modal">
<%= form_with(model: resource, url: update_path, remote: true, html: { data: { action: 'autosave-form' } }) do |f| %>
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"><%= t '.title', resource_name: resource.name %></h4>
</div>
<div class="modal-body">
<%= f.nested_fields_for :user_assignments, resource.user_assignments, wrapper_options: { class: 'row' } do |user_assignment_form| %>
<%= render('access_permissions/partials/user_permission_field.html.erb', f: user_assignment_form) if user_assignment_form.object.persisted? %>
<% end %>
</div>
<div class="modal-footer">
<%= link_to new_resource_path, class: 'btn btn-default pull-left', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
<i class="fas fa-plus"></i>
<%= t '.new_resource_assignments', resource: resource.model_name.human.downcase %>
<% end %>
</div>
<% end %>
</div>
</div>
</div>

View file

@ -0,0 +1,19 @@
<% # frozen_string_literal: true %>
<div class="modal-content" id="user_assignments_modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"><%= t '.title', resource_name: project.name %></h4>
</div>
<div class="modal-body">
<% project.users.each do |user| %>
<%= render('access_permissions/partials/project_member_field.html.erb', user: user, project: project, update_path: update_path) %>
<% end %>
</div>
<div class="modal-footer">
<%= link_to new_resource_path, class: 'btn btn-default pull-left', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
<i class="fas fa-plus"></i>
<%= t '.new_resource_assignments', resource: project.model_name.human.downcase %>
<% end %>
</div>
</div>

View file

@ -4,13 +4,13 @@
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">
<%= link_to resource_path, class: 'pull-left', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
<%= link_to resource_path, remote: true, class: 'pull-left', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
<i class="fas fa-arrow-left"></i>
<% end %>
<%= t '.title', resource_name: resource.name %>
</h4>
</div>
<%= form_with(model: form_object, url: create_path, method: :post, remote: true, html: { data: { action: 'replace-form' } }) do |f| %>
<%= form_with(model: form_object, url: create_path, method: :post, remote: true, html: { data: { action: 'replace-form', target: '#user_assignments_modal' } }) do |f| %>
<div class="modal-body">
<% f.object.resource_members.each do |member| %>
<%= f.fields_for :resource_members, member do |member_form| %>

View file

@ -0,0 +1,34 @@
<% # frozen_string_literal: true %>
<% project_member = ProjectMember.new(user, project) %>
<% item_id = "project-member-#{user.id}" %>
<%= form_with(model: project_member, url: update_path, method: :put, remote: true, html: { id: item_id, data: { action: 'replace-form autosave-form' } }) do |f| %>
<%= f.hidden_field :user_id, value: f.object.user.id %>
<div class="row">
<div class="col-xs-2">
<span class="global-avatar-container">
<%= image_tag avatar_path(user, :icon_small), title: current_assignee_name(user), class: 'img-circle pull-left' %>
</span>
</div>
<div class="col-xs-3">
<span><%= current_assignee_name(user) %></span>
<br>
<small class="text-muted"><%= project_member.user_role.name %></small>
</div>
<div class="col-xs-7">
<div class="row">
<div class="col-xs-7">
<% unless user == current_user %>
<%= f.select :user_role_id, options_for_select(user_roles_collection, selected: f.object.user_role.id), {}, class: 'form-control selectpicker' %>
<% end %>
</div>
<div class="col-xs-5">
<%= link_to access_permissions_project_path(project, user_id: user, format: :json), remote: true, method: :delete, class: 'btn btn-link', data: { action: 'remote-destroy', target: "##{item_id}" } do %>
<span class="fas fa-times"></span>
<%= t 'general.remove' %>
<% end %>
</div>
</div>
</div>
</div>
<% end %>

View file

@ -1,28 +0,0 @@
<% # frozen_string_literal: true %>
<% user_assignment = f.object %>
<div class="col-xs-2">
<span class="global-avatar-container">
<%= image_tag avatar_path(user_assignment.user, :icon_small), title: current_assignee_name(user_assignment.user), class: 'img-circle pull-left' %>
</span>
</div>
<div class="col-xs-3">
<span><%= current_assignee_name(user_assignment.user) %></span>
<br>
<small class="text-muted"><%= user_assignment.user_role.name %></small>
</div>
<div class="col-xs-7">
<div class="row">
<div class="col-xs-7">
<% unless user_assignment.user == current_user %>
<%= f.select :user_role_id, options_for_select(user_roles_collection), {}, class: 'form-control selectpicker' %>
<% end %>
</div>
<div class="col-xs-5">
<%= f.remove_nested_fields_link class: 'btn btn-link' do %>
<span class="fas fa-times"></span>
<%= t 'general.remove' %>
<% end %>
</div>
</div>
</div>

View file

@ -1,12 +1,26 @@
# frozen_string_literal: true
json.modal controller.render_to_string(
partial: 'access_permissions/modals/edit_modal',
partial: 'access_permissions/projects/modals/edit_modal',
formats: [:html],
locals: {
resource: @project,
project: @project,
update_path: access_permissions_project_path(@project, format: :json),
new_resource_path: new_access_permissions_project_path(id: @project, format: :json)
},
layout: false
)
modal_container = controller.render_to_string(
partial: 'access_permissions/partials/edit_assignments_content',
formats: [:html],
locals: {
project: @project,
update_path: access_permissions_project_path(@project, format: :json),
new_resource_path: new_access_permissions_project_path(id: @project, format: :json)
},
layout: false
)
json.html modal_container
json.form modal_container

View file

@ -0,0 +1,7 @@
<% # frozen_string_literal: true %>
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<%= render 'access_permissions/partials/edit_assignments_content', project: project, new_resource_path: new_resource_path, update_path: update_path %>
</div>
</div>

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
json.form controller.render_to_string(
partial: 'access_permissions/partials/project_member_field',
formats: [:html],
locals: {
user: @form.user,
project: @project,
update_path: access_permissions_project_path(@project, format: :json)
},
layout: false
)

View file

@ -2316,18 +2316,16 @@ en:
default_task_name: "New Task"
user_assignment:
select:
default_option: "Select User role"
current_assignee: "(you)"
access_permissions:
partials:
edit_assignments_content:
title: "Manage access for to %{resource_name}"
new_resource_assignments: "Grant new access to %{resource}"
new_assignments_form:
title: "Select members"
submit: "Grand access"
modals:
edit_modal:
title: "Manage access for to %{resource_name}"
new_resource_assignments: "Grant new access to %{resource}"
show_modal:
title: "Access to %{resource_name}"
new_resource_assignments: "Grant new access to %{resource}"