extract members tab; invite members via email

pull/3480/head
Markus Kahl 9 years ago
parent 98ba7ce333
commit 2f5018c1d0
  1. 7
      app/assets/javascripts/members_select_boxes.js
  2. 38
      app/assets/javascripts/members_view.js
  3. 115
      app/controllers/members_controller.rb
  4. 7
      app/helpers/projects_helper.rb
  5. 1
      app/views/members/_member_form_impaired.html.erb
  6. 3
      app/views/members/_member_form_non_impaired.html.erb
  7. 26
      app/views/members/autocomplete_for_member.json.erb
  8. 4
      app/views/members/create.js.erb
  9. 138
      app/views/members/index.html.erb
  10. 42
      app/views/members/new.html.erb
  11. 147
      app/views/projects/settings/_members.html.erb
  12. 7
      app/views/users/new.html.erb
  13. 6
      config/initializers/menus.rb
  14. 2
      config/initializers/permissions.rb
  15. 2
      config/routes.rb

@ -103,10 +103,5 @@ jQuery(document).ready(function($) {
});
};
memberstab = $('#tab-members').first();
if ((memberstab !== null) && (memberstab.hasClass("selected"))) {
init_members_cb();
} else {
memberstab.click(init_members_cb);
}
init_members_cb();
});

@ -0,0 +1,38 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
jQuery(document).ready(function($) {
$("#add-member-button").click(function() {
var url = $(this).attr("href");
var helper = new ModalHelper();
helper.createModal({ url: url });
return false;
});
});

@ -35,6 +35,8 @@ class MembersController < ApplicationController
before_filter :authorize
include Pagination::Controller
include PaginationHelper
paginate_model User
search_for User, :search_in_project
search_options_for User, lambda { |_| { project: @project } }
@ -45,6 +47,22 @@ class MembersController < ApplicationController
@@scripts.unshift(script)
end
def index
@roles = Role.find_all_givable
# Check if there is at least one principal that can be added to the project
@principals_available = @project.possible_members("", 1)
@members = @project.member_principals.includes(:roles, :principal, :member_roles)
.order(User::USER_FORMATS_STRUCTURE[Setting.user_format].map{|attr| attr.to_s}.join(", "))
.page(params[:page])
.references(:users)
.per_page(per_page_param)
end
def new
set_roles_and_principles!
end
def create
if params[:member]
members = new_members_from_params
@ -52,28 +70,37 @@ class MembersController < ApplicationController
end
respond_to do |format|
if members.present? && members.all?(&:valid?)
flash.now.notice = l(:notice_successful_create)
format.html do redirect_to settings_project_path(@project, tab: 'members') end
flash.notice = l(:notice_successful_create)
format.js do
@pagination_url_options = { controller: 'projects', action: 'settings', id: @project }
render(:update) do |page|
page.replace_html 'tab-content-members', partial: 'projects/settings/members',
locals: { members: members }
page.insert_html :top, 'tab-content-members', render_flash_messages
format.html do
redirect_to project_members_path
end
page << MembersController.tab_scripts
format.js
else
format.html do
if params[:member]
@member = members.first
else
flash.error = l(:error_check_user_and_role)
end
set_roles_and_principles!
render 'new'
end
else
format.js do
@pagination_url_options = { controller: 'projects', action: 'settings', id: @project }
render(:update) do |page|
if params[:member]
page.insert_html :top, 'tab-content-members', partial: 'members/member_errors', locals: { member: members.first }
page.replace_html 'new-member-message',
partial: 'members/member_errors',
locals: { member: members.first }
else
page.insert_html :top, 'tab-content-members', partial: 'members/common_error', locals: { message: l(:error_check_user_and_role) }
page.replace_html 'new-member-message',
partial: 'members/common_error',
locals: { message: l(:error_check_user_and_role) }
end
end
end
@ -84,45 +111,21 @@ class MembersController < ApplicationController
def update
member = update_member_from_params
if member.save
flash.now.notice = l(:notice_successful_update)
flash.notice = l(:notice_successful_update)
end
respond_to do |format|
format.html do redirect_to controller: '/projects', action: 'settings', tab: 'members', id: @project, page: params[:page] end
format.js do
@pagination_url_options = { controller: 'projects', action: 'settings', id: @project }
render(:update) do |page|
if params[:membership]
@user = member.user
page.replace_html 'tab-content-memberships', partial: 'users/memberships'
else
page.replace_html 'tab-content-members', partial: 'projects/settings/members'
end
page.insert_html :top, 'tab-content-members', render_flash_messages
page << MembersController.tab_scripts
page.visual_effect(:highlight, "member-#{@member.id}") unless Member.find_by(id: @member.id).nil?
end
end
end
redirect_to project_members_path(project_id: @project.id,
page: params[:page],
per_page: params[:per_page])
end
def destroy
if @member.deletable?
@member.destroy
flash.now.notice = l(:notice_successful_delete)
end
respond_to do |format|
format.html do redirect_to controller: '/projects', action: 'settings', tab: 'members', id: @project end
format.js do
@pagination_url_options = { controller: 'projects', action: 'settings', id: @project }
render(:update) do |page|
page.replace_html 'tab-content-members', partial: 'projects/settings/members'
page.insert_html :top, 'tab-content-members', render_flash_messages
page << MembersController.tab_scripts
end
end
flash.notice = l(:notice_successful_delete)
end
redirect_to project_members_path(project_id: @project.id)
end
def autocomplete_for_member
@ -162,8 +165,14 @@ class MembersController < ApplicationController
@@scripts.join('(); ') + '();'
end
def set_roles_and_principles!
@roles = Role.find_all_givable
# Check if there is at least one principal that can be added to the project
@principals_available = @project.possible_members("", 1)
end
def new_members_from_params
user_ids = possibly_seperated_ids_for_entity(params[:member], :user)
user_ids = invite_new_users possibly_seperated_ids_for_entity(params[:member], :user)
roles = Role.where(id: possibly_seperated_ids_for_entity(params[:member], :role))
new_member = lambda { |user_id|
@ -182,6 +191,22 @@ class MembersController < ApplicationController
members
end
def invite_new_users(user_ids)
user_ids.map do |id|
if id.to_i == 0 # we've got an email - invite that user
# The invitation can pretty much only fail due to the user already
# having been invited. So look them up if it does.
user = UserInvitation.invite_new_user(email: id) ||
User.find_by_mail(id)
user.id if user
else
id
end
end.compact
end
def each_comma_seperated(array, &block)
array.map { |e|
if e.to_s.match /\d(,\d)*/

@ -52,12 +52,7 @@ module ProjectsHelper
name: 'modules',
action: :select_project_modules,
partial: 'projects/settings/modules',
label: :label_module_plural },
{
name: 'members',
action: :manage_members,
partial: 'projects/settings/members',
label: :label_member_plural
label: :label_module_plural
},
{
name: 'custom_fields',

@ -36,6 +36,7 @@ See doc/COPYRIGHT.rdoc for more details.
:html => {:id => "members_add_form"}) do |f| %>
<div class="form--section">
<h3 class="form--section-title"><%= l(:label_member_new) %></h3>
<div id="new-member-message"></div>
<div class="grid-block medium-up-2">
<div class="form--column">
<div class="form--field">

@ -30,11 +30,12 @@ See doc/COPYRIGHT.rdoc for more details.
<%= javascript_include_tag "members_select_boxes.js" %>
<%= labelled_tabular_form_for(:member, :url => {:controller => 'members', :action => 'create', :project_id => project},
:remote => true,
:method => :post,
:remote => true,
:html => {:id => "members_add_form"}) do |f| %>
<div class="form--section">
<h3 class="form--section-title"><%= l(:label_member_new) %></h3>
<div id="new-member-message"></div>
<div class="grid-block medium-up-2">
<div class="form--column">
<div class="form--field -vertical">

@ -27,16 +27,30 @@ See doc/COPYRIGHT.rdoc for more details.
++#%>
<%#
Lists found users (principals) to be used for the select box.
If the user enters an email address, a 'result' in the form of
'Invite: <email>' will be returned.
#%>
{
"results":
{
<% email = params[:q] =~ /.+@.+/ %>
"items":[
<% @principals.each_with_index do |principal, ix| %>
{
"id": <%= principal.id.to_json.html_safe %>,
"name": <%= principal.name.to_json.html_safe %>
} <%= "," unless ix == @principals.length - 1 %>
<% end %> ],
<% @principals.each_with_index do |principal, ix| %>
{
"id": <%= principal.id.to_json.html_safe %>,
"name": <%= principal.name.to_json.html_safe %>
} <%= "," unless !email && ix == @principals.length - 1 %>
<% end %>
<% if email %>
{
"id": "<%= params[:q] %>",
"name": "Invite <%= params[:q] %>"
}
<% end %>
],
"total": <%= @total ? @total : @principals.size %>,
"more": <%= @more ? @more : 0 %>
}

@ -0,0 +1,4 @@
// reload iframes parent (project members view)
// to show updated list of members and a flash message
window.parent.document.location.reload();

@ -0,0 +1,138 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= javascript_include_tag "members_view.js" %>
<% html_title 'Members' %>
<%= toolbar title: 'Members' do %>
<% if authorize_for(:members, :new) %>
<a href="<%= new_project_member_path %>" id="add-member-button" title="Add Member" class="button -alt-highlight">
<i class="button--icon icon-add"></i>
<span class="button--text">Add Member</span>
</a>
<% end %>
<% end %>
<div>
<% if @members.any? %>
<% authorized = authorize_for('members', 'update') %>
<div class="generic-table--container">
<div class="generic-table--results-container">
<table interactive-table role="grid" class="generic-table">
<colgroup>
<col highlight-col>
<col highlight-col>
<%= call_hook(:view_projects_settings_members_table_colgroup, :project => @project) %>
<col>
</colgroup>
<thead>
<tr>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= User.model_name.human %> / <%= Group.model_name.human %>
</span>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= l(:label_role_plural) %>
</span>
</div>
</div>
</th>
<%= call_hook(:view_projects_settings_members_table_header, :project => @project) %>
<th></th>
</tr>
</thead>
<tbody>
<% @members.each do |member| %>
<% next if member.new_record? %>
<tr id="member-<%= member.id %>" class=" member">
<td class="<%= member.principal.class.name.downcase %> <%= 'icon icon-group' if member.principal.class.name.downcase == 'group' %> <%= user_status_class member.principal%>" title="<%= user_status_i18n member.principal%>"><%= link_to_user member.principal %></td>
<td class="roles">
<span id="member-<%= member.id %>-roles"><%=h member.roles.sort.collect(&:to_s).join(', ') %></span>
<% if authorized %>
<%= form_for(member, :url => {:controller => '/members',
:action => 'update',
:id => member,
:page => params[:page]},
:method => :put,
:remote => true,
:html => { :id => "member-#{member.id}-roles-form",
:class => 'hol',
:style => 'display:none' }) do |f| %>
<p><% roles.each do |role| %>
<label><%= check_box_tag 'member[role_ids][]', role.id, member.roles.include?(role),
:disabled => member.member_roles.detect {|mr| mr.role_id == role.id && !mr.inherited_from.nil?} %> <%=h role %></label>
<% end %></p>
<%= hidden_field_tag 'member[role_ids][]', '' %>
<p><%= submit_tag l(:button_change), :class => 'button -highlight -small' %>
<%= link_to_function l(:button_cancel),
"$('member-#{member.id}-roles').show(); $('member-#{member.id}-roles-form').hide(); return false;",
class: 'button -small' %></p>
<% end %>
<%= call_hook(:view_projects_settings_members_table_row, { :project => @project, :member => member}) %>
<td class="buttons">
<%= link_to_function l(:button_edit), "$('member-#{member.id}-roles').hide(); $('member-#{member.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %>
<%= link_to(l(:button_delete), {:controller => '/members', :action => 'destroy', :id => member, :page => params[:page]},
:method => :delete,
:remote => true,
data: { confirm: ((!User.current.admin? && member.include?(User.current)) ? l(:text_own_membership_delete_confirmation) : nil) },
:title => l(:button_delete), :class => 'icon icon-delete') if member.deletable? %>
</td>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<div class="generic-table--header-background"></div>
</div>
</div>
<%= pagination_links_full @members, @pagination_url_options || {} %>
<% else %>
<div class="generic-table--container">
<div class="generic-table--no-results-container">
<h2 class="generic-table--no-results-title">
<i class="icon-info"></i>
<%= l(:label_nothing_display) %>
</h2>
<div class="generic-table--no-results-description">
<p class="nodata"><%= l(:label_no_data) %></p>
</div>
</div>
</div>
<% end %>
</div>

@ -0,0 +1,42 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= javascript_include_tag "members_form.js" %>
<%= error_messages_for 'member' %>
<div>
<% if @roles.any? && @principals_available.any? %>
<%= render :partial => "members/member_form",
:locals => { :project => @project,
:roles => @roles } %>
<% else %>
No roles defined.
<% end %>
</div>

@ -1,147 +0,0 @@
<%#-- copyright
OpenProject is a project management system.
Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License version 3.
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
Copyright (C) 2006-2013 Jean-Philippe Lang
Copyright (C) 2010-2013 the ChiliProject Team
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
See doc/COPYRIGHT.rdoc for more details.
++#%>
<%= javascript_include_tag "members_form.js" %>
<%= error_messages_for 'member' %>
<% roles = Role.find_all_givable
# Check if there is at least one principal that can be added to the project
principals_available = @project.possible_members("", 1)
member_per_page = 20
@members = @project.member_principals.includes(:roles, :principal, :member_roles)
.order(User::USER_FORMATS_STRUCTURE[Setting.user_format].map{|attr| attr.to_s}.join(", "))
.page(params[:page])
.references(:users)
.per_page(per_page_param)
%>
<div>
<% if roles.any? && principals_available.any? %>
<%= render :partial => "members/member_form",
:locals => { :project => @project,
:members => @members,
:roles => roles } %>
<% end %>
</div>
<div>
<% if @members.any? %>
<% authorized = authorize_for('members', 'update') %>
<div class="generic-table--container">
<div class="generic-table--results-container">
<table interactive-table role="grid" class="generic-table">
<colgroup>
<col highlight-col>
<col highlight-col>
<%= call_hook(:view_projects_settings_members_table_colgroup, :project => @project) %>
<col>
</colgroup>
<thead>
<tr>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= User.model_name.human %> / <%= Group.model_name.human %>
</span>
</div>
</div>
</th>
<th>
<div class="generic-table--sort-header-outer">
<div class="generic-table--sort-header">
<span>
<%= l(:label_role_plural) %>
</span>
</div>
</div>
</th>
<%= call_hook(:view_projects_settings_members_table_header, :project => @project) %>
<th></th>
</tr>
</thead>
<tbody>
<% @members.each do |member| %>
<% next if member.new_record? %>
<tr id="member-<%= member.id %>" class=" member">
<td class="<%= member.principal.class.name.downcase %> <%= 'icon icon-group' if member.principal.class.name.downcase == 'group' %> <%= user_status_class member.principal%>" title="<%= user_status_i18n member.principal%>"><%= link_to_user member.principal %></td>
<td class="roles">
<span id="member-<%= member.id %>-roles"><%=h member.roles.sort.collect(&:to_s).join(', ') %></span>
<% if authorized %>
<%= form_for(member, :url => {:controller => '/members',
:action => 'update',
:id => member,
:page => params[:page]},
:method => :put,
:remote => true,
:html => { :id => "member-#{member.id}-roles-form",
:class => 'hol',
:style => 'display:none' }) do |f| %>
<p><% roles.each do |role| %>
<label><%= check_box_tag 'member[role_ids][]', role.id, member.roles.include?(role),
:disabled => member.member_roles.detect {|mr| mr.role_id == role.id && !mr.inherited_from.nil?} %> <%=h role %></label>
<% end %></p>
<%= hidden_field_tag 'member[role_ids][]', '' %>
<p><%= submit_tag l(:button_change), :class => 'button -highlight -small' %>
<%= link_to_function l(:button_cancel),
"$('member-#{member.id}-roles').show(); $('member-#{member.id}-roles-form').hide(); return false;",
class: 'button -small' %></p>
<% end %>
<%= call_hook(:view_projects_settings_members_table_row, { :project => @project, :member => member}) %>
<td class="buttons">
<%= link_to_function l(:button_edit), "$('member-#{member.id}-roles').hide(); $('member-#{member.id}-roles-form').show(); return false;", :class => 'icon icon-edit' %>
<%= link_to(l(:button_delete), {:controller => '/members', :action => 'destroy', :id => member, :page => params[:page]},
:method => :delete,
:remote => true,
data: { confirm: ((!User.current.admin? && member.include?(User.current)) ? l(:text_own_membership_delete_confirmation) : nil) },
:title => l(:button_delete), :class => 'icon icon-delete') if member.deletable? %>
</td>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<div class="generic-table--header-background"></div>
</div>
</div>
<%= pagination_links_full @members, params: { tab: 'members' }.merge(@pagination_url_options || {}) %>
<% else %>
<div class="generic-table--container">
<div class="generic-table--no-results-container">
<h2 class="generic-table--no-results-title">
<i class="icon-info"></i>
<%= l(:label_nothing_display) %>
</h2>
<div class="generic-table--no-results-description">
<p class="nodata"><%= l(:label_no_data) %></p>
</div>
</div>
</div>
<% end %>
</div>

@ -33,11 +33,8 @@ See doc/COPYRIGHT.rdoc for more details.
:url => { :action => "create" },
:html => { :class => nil, :autocomplete => 'off' },
:as => :user do |f| %>
<%= render :partial => 'form', :locals => { :f => f } %>
<div class="form--field">
<label><%= styled_check_box_tag 'send_information', 1, true %>
<%= l(:label_send_information) %></label>
</div>
<%= render :partial => 'simple_form', :locals => { f: f, auth_sources: @auth_sources, user: @user } %>
<p>
<%= styled_button_tag l(:button_create), class: '-highlight -with-icon icon-yes' %>
<%= styled_button_tag l(:button_create_and_continue), :name => 'continue', class: '-highlight -with-icon icon-yes' %>

@ -267,6 +267,12 @@ Redmine::MenuManager.map :project_menu do |menu|
if: Proc.new { |p| p.project_type.try :allows_association },
html: { class: 'icon2 icon-dependency' }
menu.push :members,
{ controller: :members, action: :index },
param: :project_id,
caption: :label_member_plural,
html: { class: 'icon2 icon-group' }
menu.push :settings,
{ controller: '/projects', action: 'settings' },
caption: :label_project_settings,

@ -55,7 +55,7 @@ Redmine::AccessControl.map do |map|
map.permission :manage_members,
{ projects: :settings,
members: [:create, :update, :destroy, :autocomplete_for_member] },
members: [:index, :new, :create, :update, :destroy, :autocomplete_for_member] },
require: :member
map.permission :manage_versions,

@ -329,7 +329,7 @@ OpenProject::Application.routes.draw do
resources :categories, except: [:index, :show], shallow: true
resources :members, only: [:create, :update, :destroy], shallow: true do
resources :members, only: [:index, :new, :create, :update, :destroy], shallow: true do
match :autocomplete_for_member, on: :collection, via: [:get, :post]
end

Loading…
Cancel
Save