Commit aad3cbee authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'team-members' into 'master'

Use same layout and interactivity for project members as group members and make code more consistent.

It's probably easiest to review the commits one by one, but keep in mind that later commits sometimes touch the same place as earlier ones, so look at the complete diff to add comments.

The project members page and group members page now look and work the same, with an inline "Add members" form, member search and editing of access levels and deleting without page refresh.

Before:

![Screen_Shot_2015-03-13_at_16.50.39](https://dev.gitlab.org/gitlab/gitlabhq/uploads/2cd023c48de0a643ce04df965f39e1f5/Screen_Shot_2015-03-13_at_16.50.39.png)

After:

![Screen_Shot_2015-03-13_at_16.50.02](https://dev.gitlab.org/gitlab/gitlabhq/uploads/fdef480daf2859ed74f5641af1f337b3/Screen_Shot_2015-03-13_at_16.50.02.png)

See merge request !1695
parents 60df262c 584580e5
...@@ -68,6 +68,7 @@ v 7.9.0 (unreleased) ...@@ -68,6 +68,7 @@ v 7.9.0 (unreleased)
- Execute hooks and services when branch or tag is created or deleted through web interface. - Execute hooks and services when branch or tag is created or deleted through web interface.
- Block and unblock user if he/she was blocked/unblocked in Active Directory - Block and unblock user if he/she was blocked/unblocked in Active Directory
- Raise recommended number of unicorn workers from 2 to 3 - Raise recommended number of unicorn workers from 2 to 3
- Use same layout and interactivity for project members as group members.
v 7.8.4 v 7.8.4
- Fix issue_tracker_id substitution in custom issue trackers - Fix issue_tracker_id substitution in custom issue trackers
......
...@@ -73,9 +73,12 @@ class Dispatcher ...@@ -73,9 +73,12 @@ class Dispatcher
new Activities() new Activities()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ProjectsList() new ProjectsList()
when 'groups:members' when 'groups:group_members:index'
new GroupMembers() new GroupMembers()
new UsersSelect() new UsersSelect()
when 'projects:project_members:index'
new ProjectMembers()
new UsersSelect()
when 'groups:new', 'groups:edit', 'admin:groups:edit' when 'groups:new', 'groups:edit', 'admin:groups:edit'
new GroupAvatar() new GroupAvatar()
when 'projects:tree:show' when 'projects:tree:show'
...@@ -127,9 +130,8 @@ class Dispatcher ...@@ -127,9 +130,8 @@ class Dispatcher
new DropzoneInput($('.wiki-form')) new DropzoneInput($('.wiki-form'))
when 'snippets', 'labels', 'graphs' when 'snippets', 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new UsersSelect()
# If we haven't installed a custom shortcut handler, install the default one # If we haven't installed a custom shortcut handler, install the default one
......
class @ProjectMembers
constructor: ->
$('li.project_member').bind 'ajax:success', ->
$(this).fadeOut()
...@@ -167,7 +167,7 @@ li.note { ...@@ -167,7 +167,7 @@ li.note {
background-color: inherit; background-color: inherit;
} }
.team_member_show { .project_member_show {
td:first-child { td:first-child {
color: #aaa; color: #aaa;
} }
......
...@@ -156,7 +156,7 @@ ul.nav.nav-projects-tabs { ...@@ -156,7 +156,7 @@ ul.nav.nav-projects-tabs {
} }
} }
.team_member_row form { .project_member_row form {
margin: 0px; margin: 0px;
} }
......
class Admin::GroupsController < Admin::ApplicationController class Admin::GroupsController < Admin::ApplicationController
before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update] before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update]
def index def index
@groups = Group.all @groups = Group.all
...@@ -40,7 +40,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -40,7 +40,7 @@ class Admin::GroupsController < Admin::ApplicationController
end end
end end
def project_teams_update def members_update
@group.add_users(params[:user_ids].split(','), params[:access_level]) @group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to [:admin, @group], notice: 'Users were successfully added.' redirect_to [:admin, @group], notice: 'Users were successfully added.'
......
class Dashboard::GroupsController < ApplicationController class Dashboard::GroupsController < ApplicationController
def index def index
@user_groups = current_user.group_members.page(params[:page]).per(PER_PAGE) @group_members = current_user.group_members.page(params[:page]).per(PER_PAGE)
end
def leave
@users_group = group.group_members.where(user_id: current_user.id).first
if can?(current_user, :destroy, @users_group)
@users_group.destroy
redirect_to(dashboard_groups_path, info: "You left #{group.name} group.")
else
return render_403
end
end
private
def group
@group ||= Group.find_by(path: params[:id])
end end
end end
...@@ -2,9 +2,27 @@ class Groups::ApplicationController < ApplicationController ...@@ -2,9 +2,27 @@ class Groups::ApplicationController < ApplicationController
private private
def authorize_read_group!
unless @group and can?(current_user, :read_group, @group)
if current_user.nil?
return authenticate_user!
else
return render_404
end
end
end
def authorize_admin_group! def authorize_admin_group!
unless can?(current_user, :manage_group, group) unless can?(current_user, :manage_group, group)
return render_404 return render_404
end end
end end
def determine_layout
if current_user
'group'
else
'public_group'
end
end
end end
class Groups::GroupMembersController < Groups::ApplicationController class Groups::GroupMembersController < Groups::ApplicationController
skip_before_filter :authenticate_user!, only: [:index]
before_filter :group before_filter :group
# Authorize # Authorize
before_filter :authorize_admin_group! before_filter :authorize_read_group!
before_filter :authorize_admin_group!, except: [:index, :leave]
layout 'group' layout :determine_layout
def index
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = @group.group_members
if params[:search].present?
users = @group.users.search(params[:search]).to_a
@members = @members.where(user_id: users)
end
@members = @members.order('access_level DESC').page(params[:page]).per(50)
@group_member = GroupMember.new
end
def create def create
@group.add_users(params[:user_ids].split(','), params[:access_level]) @group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to members_group_path(@group), notice: 'Users were successfully added.' redirect_to group_group_members_path(@group), notice: 'Users were successfully added.'
end end
def update def update
...@@ -18,12 +33,12 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -18,12 +33,12 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
def destroy def destroy
@users_group = @group.group_members.find(params[:id]) @group_member = @group.group_members.find(params[:id])
if can?(current_user, :destroy, @users_group) # May fail if last owner. if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner.
@users_group.destroy @group_member.destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' } format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
format.js { render nothing: true } format.js { render nothing: true }
end end
else else
...@@ -31,6 +46,17 @@ class Groups::GroupMembersController < Groups::ApplicationController ...@@ -31,6 +46,17 @@ class Groups::GroupMembersController < Groups::ApplicationController
end end
end end
def leave
@group_member = @group.group_members.where(user_id: current_user.id).first
if can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy
redirect_to(dashboard_groups_path, info: "You left #{group.name} group.")
else
return render_403
end
end
protected protected
def group def group
......
class GroupsController < Groups::ApplicationController class GroupsController < Groups::ApplicationController
skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests] skip_before_filter :authenticate_user!, only: [:show, :issues, :merge_requests]
respond_to :html respond_to :html
before_filter :group, except: [:new, :create] before_filter :group, except: [:new, :create]
...@@ -67,19 +67,6 @@ class GroupsController < Groups::ApplicationController ...@@ -67,19 +67,6 @@ class GroupsController < Groups::ApplicationController
end end
end end
def members
@project = group.projects.find(params[:project_id]) if params[:project_id]
@members = group.group_members
if params[:search].present?
users = group.users.search(params[:search]).to_a
@members = @members.where(user_id: users)
end
@members = @members.order('access_level DESC').page(params[:page]).per(50)
@users_group = GroupMember.new
end
def edit def edit
end end
......
...@@ -14,9 +14,9 @@ class Profiles::NotificationsController < ApplicationController ...@@ -14,9 +14,9 @@ class Profiles::NotificationsController < ApplicationController
@saved = if type == 'global' @saved = if type == 'global'
current_user.update_attributes(user_params) current_user.update_attributes(user_params)
elsif type == 'group' elsif type == 'group'
users_group = current_user.group_members.find(params[:notification_id]) group_member = current_user.group_members.find(params[:notification_id])
users_group.notification_level = params[:notification_level] group_member.notification_level = params[:notification_level]
users_group.save group_member.save
else else
project_member = current_user.project_members.find(params[:notification_id]) project_member = current_user.project_members.find(params[:notification_id])
project_member.notification_level = params[:notification_level] project_member.notification_level = params[:notification_level]
......
class Projects::TeamMembersController < Projects::ApplicationController class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize # Authorize
before_filter :authorize_admin_project!, except: :leave before_filter :authorize_admin_project!, except: :leave
layout "project_settings" layout "project_settings"
def index def index
@project_members = @project.project_members
if params[:search].present?
users = @project.users.search(params[:search]).to_a
@project_members = @project_members.where(user_id: users)
end
@project_members = @project_members.order('access_level DESC')
@group = @project.group @group = @project.group
@project_members = @project.project_members.order('access_level DESC') if @group
@group_members = @group.group_members
if params[:search].present?
users = @group.users.search(params[:search]).to_a
@group_members = @group_members.where(user_id: users)
end
@group_members = @group_members.order('access_level DESC').limit(20)
end
@project_member = @project.project_members.new
end end
def new def new
@user_project_relation = @project.project_members.new @project_member = @project.project_members.new
end end
def create def create
users = User.where(id: params[:user_ids].split(',')) users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:access_level]] @project.team << [users, params[:access_level]]
redirect_to namespace_project_team_index_path(@project.namespace, @project) redirect_to namespace_project_project_members_path(@project.namespace, @project)
end end
def update def update
@user_project_relation = @project.project_members.find_by(user_id: member) @project_member = @project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params) @project_member.update_attributes(member_params)
unless @user_project_relation.valid?
flash[:alert] = "User should have at least one role"
end
redirect_to namespace_project_team_index_path(@project.namespace, @project)
end end
def destroy def destroy
@user_project_relation = @project.project_members.find_by(user_id: member) @project_member = @project.project_members.find_by(user_id: member)
@user_project_relation.destroy @project_member.destroy
respond_to do |format| respond_to do |format|
format.html do format.html do
redirect_to namespace_project_team_index_path(@project.namespace, redirect_to namespace_project_project_members_path(@project.namespace,
@project) @project)
end end
format.js { render nothing: true } format.js { render nothing: true }
...@@ -57,7 +72,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -57,7 +72,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
status = @project.team.import(giver) status = @project.team.import(giver)
notice = status ? "Successfully imported" : "Import failed" notice = status ? "Successfully imported" : "Import failed"
redirect_to(namespace_project_team_index_path(project.namespace, project), redirect_to(namespace_project_project_members_path(project.namespace, project),
notice: notice) notice: notice)
end end
......
...@@ -60,7 +60,7 @@ module SearchHelper ...@@ -60,7 +60,7 @@ module SearchHelper
{ label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, { label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) },
{ label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, { label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) },
{ label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, { label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) },
{ label: "#{prefix} - Team", url: namespace_project_team_index_path(@project.namespace, @project) }, { label: "#{prefix} - Members", url: namespace_project_project_members_path(@project.namespace, @project) },
{ label: "#{prefix} - Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }, { label: "#{prefix} - Wiki", url: namespace_project_wikis_path(@project.namespace, @project) },
] ]
else else
......
...@@ -89,7 +89,7 @@ module TabHelper ...@@ -89,7 +89,7 @@ module TabHelper
def project_tab_class def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project) return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
if ['services', 'hooks', 'deploy_keys', 'team_members', 'protected_branches'].include? controller.controller_name if ['services', 'hooks', 'deploy_keys', 'project_members', 'protected_branches'].include? controller.controller_name
"active" "active"
end end
end end
......
module Emails module Emails
module Groups module Groups
def group_access_granted_email(user_group_id) def group_access_granted_email(group_member_id)
@membership = GroupMember.find(user_group_id) @group_member = GroupMember.find(group_member_id)
@group = @membership.group @group = @group_member.group
@target_url = group_url(@group) @target_url = group_url(@group)
mail(to: @membership.user.email, mail(to: @group_member.user.email,
subject: subject("Access to group was granted")) subject: subject("Access to group was granted"))
end end
end end
......
...@@ -14,7 +14,7 @@ class Ability ...@@ -14,7 +14,7 @@ class Ability
when "MergeRequest" then merge_request_abilities(user, subject) when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject) when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject)
when "GroupMember" then users_group_abilities(user, subject) when "GroupMember" then group_member_abilities(user, subject)
else [] else []
end.concat(global_abilities(user)) end.concat(global_abilities(user))
end end
...@@ -37,7 +37,7 @@ class Ability ...@@ -37,7 +37,7 @@ class Ability
:read_issue, :read_issue,
:read_milestone, :read_milestone,
:read_project_snippet, :read_project_snippet,
:read_team_member, :read_project_member,
:read_merge_request, :read_merge_request,
:read_note, :read_note,
:download_code :download_code
...@@ -119,7 +119,7 @@ class Ability ...@@ -119,7 +119,7 @@ class Ability
:read_issue, :read_issue,
:read_milestone, :read_milestone,
:read_project_snippet, :read_project_snippet,
:read_team_member, :read_project_member,
:read_merge_request, :read_merge_request,
:read_note, :read_note,
:write_project, :write_project,
...@@ -166,7 +166,7 @@ class Ability ...@@ -166,7 +166,7 @@ class Ability
:admin_issue, :admin_issue,
:admin_milestone, :admin_milestone,
:admin_project_snippet, :admin_project_snippet,
:admin_team_member, :admin_project_member,
:admin_merge_request, :admin_merge_request,
:admin_note, :admin_note,
:admin_wiki, :admin_wiki,
...@@ -248,17 +248,17 @@ class Ability ...@@ -248,17 +248,17 @@ class Ability
end end
end end
def users_group_abilities(user, subject) def group_member_abilities(user, subject)
rules = [] rules = []
target_user = subject.user target_user = subject.user
group = subject.group group = subject.group
can_manage = group_abilities(user, group).include?(:manage_group) can_manage = group_abilities(user, group).include?(:manage_group)
if can_manage && (user != target_user) if can_manage && (user != target_user)
rules << :modify rules << :modify_group_member
rules << :destroy rules << :destroy_group_member
end end
if !group.last_owner?(user) && (can_manage || (user == target_user)) if !group.last_owner?(user) && (can_manage || (user == target_user))
rules << :destroy rules << :destroy_group_member
end end
rules rules
end end
......
...@@ -32,7 +32,6 @@ class Issue < ActiveRecord::Base ...@@ -32,7 +32,6 @@ class Issue < ActiveRecord::Base
validates :project, presence: true validates :project, presence: true
scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
scope :cared, ->(user) { where(assignee_id: user) } scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) } scope :open_for, ->(user) { opened.assigned_to(user) }
......
...@@ -116,14 +116,14 @@ class ProjectMember < Member ...@@ -116,14 +116,14 @@ class ProjectMember < Member
def post_create_hook def post_create_hook
unless owner? unless owner?
event_service.join_project(self.project, self.user) event_service.join_project(self.project, self.user)
notification_service.new_team_member(self) notification_service.new_project_member(self)
end end
system_hook_service.execute_hooks_for(self, :create) system_hook_service.execute_hooks_for(self, :create)
end end
def post_update_hook def post_update_hook
notification_service.update_team_member(self) if self.access_level_changed? notification_service.update_project_member(self) if self.access_level_changed?
end end
def post_destroy_hook def post_destroy_hook
......
...@@ -115,7 +115,6 @@ class MergeRequest < ActiveRecord::Base ...@@ -115,7 +115,6 @@ class MergeRequest < ActiveRecord::Base
validate :validate_fork validate :validate_fork
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) } scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
scope :merged, -> { with_state(:merged) } scope :merged, -> { with_state(:merged) }
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
......
...@@ -157,7 +157,6 @@ class Project < ActiveRecord::Base ...@@ -157,7 +157,6 @@ class Project < ActiveRecord::Base
scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) } scope :without_user, ->(user) { where('projects.id NOT IN (:ids)', ids: user.authorized_projects.map(&:id) ) }
scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped } scope :without_team, ->(team) { team.projects.present? ? where('projects.id NOT IN (:ids)', ids: team.projects.map(&:id)) : scoped }
scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) } scope :not_in_group, ->(group) { where('projects.id NOT IN (:ids)', ids: group.project_ids ) }
scope :in_team, ->(team) { where('projects.id IN (:ids)', ids: team.projects.map(&:id)) }
scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) } scope :in_namespace, ->(namespace) { where(namespace_id: namespace.id) }
scope :in_group_namespace, -> { joins(:group) } scope :in_group_namespace, -> { joins(:group) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
...@@ -445,13 +444,13 @@ class Project < ActiveRecord::Base ...@@ -445,13 +444,13 @@ class Project < ActiveRecord::Base
end end
end end
def team_member_by_name_or_email(name = nil, email = nil) def project_member_by_name_or_email(name = nil, email = nil)
user = users.where('name like ? or email like ?', name, email).first user = users.where('name like ? or email like ?', name, email).first
project_members.where(user: user) if user project_members.where(user: user) if user
end end
# Get Team Member record by user id # Get Team Member record by user id
def team_member_by_id(user_id) def project_member_by_id(user_id)
project_members.find_by(user_id: user_id) project_members.find_by(user_id: user_id)
end end
......
...@@ -31,16 +31,16 @@ class ProjectTeam ...@@ -31,16 +31,16 @@ class ProjectTeam
user user
end end
def find_tm(user_id) def find_member(user_id)
tm = project.project_members.find_by(user_id: user_id) member = project.project_members.find_by(user_id: user_id)
# If user is not in project members # If user is not in project members
# we should check for group membership # we should check for group membership
if group && !tm if group && !member
tm = group.group_members.find_by(user_id: user_id) member = group.group_members.find_by(user_id: user_id)
end end
tm member
end end
def add_user(user, access) def add_user(user, access)
...@@ -91,24 +91,24 @@ class ProjectTeam ...@@ -91,24 +91,24 @@ class ProjectTeam
def import(source_project) def import(source_project)
target_project = project target_project = project
source_team = source_project.project_members.to_a source_members = source_project.project_members.to_a
target_user_ids = target_project.project_members.pluck(:user_id) target_user_ids = target_project.project_members.pluck(:user_id)
source_team.reject! do |tm| source_members.reject! do |member|
# Skip if user already present in team # Skip if user already present in team
target_user_ids.include?(tm.user_id) target_user_ids.include?(member.user_id)
end end
source_team.map! do |tm| source_members.map! do |member|
new_tm = tm.dup new_member = member.dup
new_tm.id = nil new_member.id = nil
new_tm.source = target_project new_member.source = target_project
new_tm new_member
end end
ProjectMember.transaction do ProjectMember.transaction do
source_team.each do |tm| source_members.each do |member|
tm.save member.save
end end
end end
...@@ -118,26 +118,26 @@ class ProjectTeam ...@@ -118,26 +118,26 @@ class ProjectTeam
end end
def guest?(user) def guest?(user)
max_tm_access(user.id) == Gitlab::Access::GUEST max_member_access(user.id) == Gitlab::Access::GUEST
end end
def reporter?(user) def reporter?(user)
max_tm_access(user.id) == Gitlab::Access::REPORTER max_member_access(user.id) == Gitlab::Access::REPORTER
end end
def developer?(user) def developer?(user)
max_tm_access(user.id) == Gitlab::Access::DEVELOPER max_member_access(user.id) == Gitlab::Access::DEVELOPER
end end
def master?(user) def master?(user)
max_tm_access(user.id) == Gitlab::Access::MASTER max_member_access(user.id) == Gitlab::Access::MASTER
end end
def member?(user_id) def member?(user_id)
!!find_tm(user_id) !!find_member(user_id)
end end
def max_tm_access(user_id) def max_member_access(user_id)
access = [] access = []
access << project.project_members.find_by(user_id: user_id).try(:access_field) access << project.project_members.find_by(user_id: user_id).try(:access_field)
......
...@@ -169,11 +169,8 @@ class User < ActiveRecord::Base ...@@ -169,11 +169,8 @@ class User < ActiveRecord::Base
scope :admins, -> { where(admin: true) } scope :admins, -> { where(admin: true) }
scope :blocked, -> { with_state(:blocked) } scope :blocked, -> { with_state(:blocked) }
scope :active, -> { with_state(:active) } scope :active, -> { with_state(:active) }
scope :in_team, ->(team){ where(id: team.member_ids) }
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
# #
# Class methods # Class methods
...@@ -407,7 +404,7 @@ class User < ActiveRecord::Base ...@@ -407,7 +404,7 @@ class User < ActiveRecord::Base
end end
def tm_of(project) def tm_of(project)
project.team_member_by_id(self.id) project.project_member_by_id(self.id)
end end
def already_forked?(project) def already_forked?(project)
......
...@@ -162,20 +162,20 @@ class NotificationService ...@@ -162,20 +162,20 @@ class NotificationService
end end
end end
def new_team_member(project_member) def new_project_member(project_member)
mailer.project_access_granted_email(project_member.id) mailer.project_access_granted_email(project_member.id)
end end
def update_team_member(project_member) def update_project_member(project_member)
mailer.project_access_granted_email(project_member.id) mailer.project_access_granted_email(project_member.id)
end end
def new_group_member(users_group) def new_group_member(group_member)
mailer.group_access_granted_email(users_group.id) mailer.group_access_granted_email(group_member.id)
end end
def update_group_member(users_group) def update_group_member(group_member)
mailer.group_access_granted_email(users_group.id) mailer.group_access_granted_email(group_member.id)
end end
def project_was_moved(project) def project_was_moved(project)
...@@ -194,11 +194,11 @@ class NotificationService ...@@ -194,11 +194,11 @@ class NotificationService
project_members = project_member_notification(project) project_members = project_member_notification(project)
users_with_project_level_global = project_member_notification(project, Notification::N_GLOBAL) users_with_project_level_global = project_member_notification(project, Notification::N_GLOBAL)
users_with_group_level_global = users_group_notification(project, Notification::N_GLOBAL) users_with_group_level_global = group_member_notification(project, Notification::N_GLOBAL)
users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq) users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq)
users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users) users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users)
users_with_group_setting = select_users_group_setting(project, project_members, users_with_group_level_global, users) users_with_group_setting = select_group_member_setting(project, project_members, users_with_group_level_global, users)
User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a
end end
...@@ -213,7 +213,7 @@ class NotificationService ...@@ -213,7 +213,7 @@ class NotificationService
end end
end end
def users_group_notification(project, notification_level) def group_member_notification(project, notification_level)
if project.group if project.group
project.group.group_members.where(notification_level: notification_level).pluck(:user_id) project.group.group_members.where(notification_level: notification_level).pluck(:user_id)
else else
...@@ -243,8 +243,8 @@ class NotificationService ...@@ -243,8 +243,8 @@ class NotificationService
end end
# Build a list of users based on group notification settings # Build a list of users based on group notification settings
def select_users_group_setting(project, project_members, global_setting, users_global_level_watch) def select_group_member_setting(project, project_members, global_setting, users_global_level_watch)
uids = users_group_notification(project, Notification::N_WATCH) uids = group_member_notification(project, Notification::N_WATCH)
# Group setting is watch, add to users list if user is not project member # Group setting is watch, add to users list if user is not project member
users = [] users = []
...@@ -273,20 +273,20 @@ class NotificationService ...@@ -273,20 +273,20 @@ class NotificationService
users.reject do |user| users.reject do |user|
next user.notification.disabled? unless project next user.notification.disabled? unless project
tm = project.project_members.find_by(user_id: user.id) member = project.project_members.find_by(user_id: user.id)
if !tm && project.group if !member && project.group
tm = project.group.group_members.find_by(user_id: user.id) member = project.group.group_members.find_by(user_id: user.id)
end end
# reject users who globally disabled notification and has no membership # reject users who globally disabled notification and has no membership
next user.notification.disabled? unless tm next user.notification.disabled? unless member
# reject users who disabled notification in project # reject users who disabled notification in project
next true if tm.notification.disabled? next true if member.notification.disabled?
# reject users who have N_GLOBAL in project and disabled in global settings # reject users who have N_GLOBAL in project and disabled in global settings
tm.notification.global? && user.notification.disabled? member.notification.global? && user.notification.disabled?
end end
end end
...@@ -297,20 +297,20 @@ class NotificationService ...@@ -297,20 +297,20 @@ class NotificationService
users.reject do |user| users.reject do |user|
next user.notification.mention? unless project next user.notification.mention? unless project
tm = project.project_members.find_by(user_id: user.id) member = project.project_members.find_by(user_id: user.id)
if !tm && project.group if !member && project.group
tm = project.group.group_members.find_by(user_id: user.id) member = project.group.group_members.find_by(user_id: user.id)
end end
# reject users who globally set mention notification and has no membership # reject users who globally set mention notification and has no membership
next user.notification.mention? unless tm next user.notification.mention? unless member
# reject users who set mention notification in project # reject users who set mention notification in project
next true if tm.notification.mention? next true if member.notification.mention?
# reject users who have N_MENTION in project and disabled in global settings # reject users who have N_MENTION in project and disabled in global settings
tm.notification.global? && user.notification.mention? member.notification.global? && user.notification.mention?
end end
end end
......
...@@ -12,8 +12,8 @@ module Projects ...@@ -12,8 +12,8 @@ module Projects
else else
[] []
end end
team_members = sorted(@project.team.members) project_members = sorted(@project.team.members)
participants = all_members + groups + team_members + participating participants = all_members + groups + project_members + participating
participants.uniq participants.uniq
end end
......
...@@ -58,7 +58,7 @@ ...@@ -58,7 +58,7 @@
Read more about project permissions Read more about project permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
= form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div %div
= users_select_tag(:user_ids, multiple: true) = users_select_tag(:user_ids, multiple: true)
%div.prepend-top-10 %div.prepend-top-10
......
...@@ -111,10 +111,10 @@ ...@@ -111,10 +111,10 @@
%small %small
(#{@project.users.count}) (#{@project.users.count})
.pull-right .pull-right
= link_to namespace_project_team_index_path(@project.namespace, @project), class: "btn btn-xs" do = link_to namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-xs" do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
Manage Access Manage Access
%ul.well-list.team_members %ul.well-list.project_members
- @project_members.each do |project_member| - @project_members.each do |project_member|
- user = project_member.user - user = project_member.user
%li.project_member %li.project_member
...@@ -126,7 +126,7 @@ ...@@ -126,7 +126,7 @@
%span.light Owner %span.light Owner
- else - else
%span.light= project_member.human_access %span.light= project_member.human_access
= link_to namespace_project_team_member_path(@project.namespace, @project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do = link_to namespace_project_project_member_path(@project.namespace, @project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do
%i.fa.fa-times %i.fa.fa-times
.panel-footer .panel-footer
= paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab'
...@@ -174,15 +174,15 @@ ...@@ -174,15 +174,15 @@
.panel.panel-default .panel.panel-default
.panel-heading Groups: .panel-heading Groups:
%ul.well-list %ul.well-list
- @user.group_members.each do |user_group| - @user.group_members.each do |group_member|
- group = user_group.group - group = group_member.group
%li.group_member %li.group_member
%span{class: ("list-item-name" unless user_group.owner?)} %span{class: ("list-item-name" unless group_member.owner?)}
%strong= link_to group.name, admin_group_path(group) %strong= link_to group.name, admin_group_path(group)
.pull-right .pull-right
%span.light= user_group.human_access %span.light= group_member.human_access
- unless user_group.owner? - unless group_member.owner?
= link_to group_group_member_path(group, user_group), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do = link_to group_group_member_path(group, group_member), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-times.fa-inverse %i.fa.fa-times.fa-inverse
- else - else
.nothing-here-block This user has no groups. .nothing-here-block This user has no groups.
...@@ -207,21 +207,21 @@ ...@@ -207,21 +207,21 @@
.panel-heading Joined projects (#{@joined_projects.count}) .panel-heading Joined projects (#{@joined_projects.count})
%ul.well-list %ul.well-list
- @joined_projects.sort_by(&:name_with_namespace).each do |project| - @joined_projects.sort_by(&:name_with_namespace).each do |project|
- tm = project.team.find_tm(@user.id) - member = project.team.find_member(@user.id)
%li.project_member %li.project_member
.list-item-name .list-item-name
= link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do
= project.name_with_namespace = project.name_with_namespace
- if tm - if member
.pull-right .pull-right
- if tm.owner? - if member.owner?
%span.light Owner %span.light Owner
- else - else
%span.light= tm.human_access %span.light= member.human_access
- if tm.respond_to? :project - if member.respond_to? :project
= link_to namespace_project_team_member_path(project.namespace, project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do = link_to namespace_project_project_member_path(project.namespace, project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do
%i.fa.fa-times %i.fa.fa-times
#ssh-keys.tab-pane #ssh-keys.tab-pane
= render 'profiles/keys/key_table', admin: true = render 'profiles/keys/key_table', admin: true
...@@ -11,10 +11,10 @@ ...@@ -11,10 +11,10 @@
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%strong Groups %strong Groups
(#{@user_groups.count}) (#{@group_members.count})
%ul.well-list %ul.well-list
- @user_groups.each do |user_group| - @group_members.each do |group_member|
- group = user_group.group - group = group_member.group
%li %li
.pull-right .pull-right
- if can?(current_user, :manage_group, group) - if can?(current_user, :manage_group, group)
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
%i.fa.fa-cogs %i.fa.fa-cogs
Settings Settings
- if can?(current_user, :destroy, user_group) - if can?(current_user, :destroy_group_member, group_member)
= link_to leave_dashboard_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Remove user from group' do = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Remove user from group' do
%i.fa.fa-sign-out %i.fa.fa-sign-out
Leave Leave
...@@ -32,9 +32,9 @@ ...@@ -32,9 +32,9 @@
%strong= group.name %strong= group.name
as as
%strong #{user_group.human_access} %strong #{group_member.human_access}
%div.light %div.light
#{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")} #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")}
= paginate @user_groups = paginate @group_members
- user = member.user - user = member.user
- return unless user - return unless user
- show_roles = true if show_roles.nil? - show_roles = true if show_roles.nil?
%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)}
%span{class: ("list-item-name" if show_controls)} %span{class: ("list-item-name" if show_controls)}
= image_tag avatar_icon(user.email, 16), class: "avatar s16" = image_tag avatar_icon(user.email, 16), class: "avatar s16"
...@@ -16,13 +17,13 @@ ...@@ -16,13 +17,13 @@
%span.pull-right %span.pull-right
%strong= member.human_access %strong= member.human_access
- if show_controls - if show_controls
- if can?(current_user, :modify, member) - if can?(current_user, :modify_group_member, member)
= button_tag class: "btn-xs btn js-toggle-button", = button_tag class: "btn-xs btn js-toggle-button",
title: 'Edit access level', type: 'button' do title: 'Edit access level', type: 'button' do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
- if can?(current_user, :destroy, member) - if can?(current_user, :destroy_group_member, member)
- if current_user == member.user - if current_user == user
= link_to leave_dashboard_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from group' do = link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse %i.fa.fa-minus.fa-inverse
- else - else
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
......
= form_for @users_group, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f| = form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f|
.form-group .form-group
= f.label :user_ids, "People", class: 'control-label' = f.label :user_ids, "People", class: 'control-label'
.col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large') .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
.form-group .form-group
= f.label :access_level, "Group Access", class: 'control-label' = f.label :access_level, "Group Access", class: 'control-label'
.col-sm-10 .col-sm-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles, @users_group.access_level), class: "project-access-select select2" = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2"
.help-block .help-block
Read more about role permissions Read more about role permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
......
- show_roles = should_user_see_group_roles?(current_user, @group) - show_roles = should_user_see_group_roles?(current_user, @group)
%h3.page-title %h3.page-title
Group members Group members
- if show_roles - if show_roles
...@@ -10,7 +11,7 @@ ...@@ -10,7 +11,7 @@
%hr %hr
.clearfix.js-toggle-container .clearfix.js-toggle-container
= form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group .form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' } = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
= button_tag 'Search', class: 'btn' = button_tag 'Search', class: 'btn'
...@@ -33,6 +34,7 @@ ...@@ -33,6 +34,7 @@
%ul.well-list %ul.well-list
- @members.each do |member| - @members.each do |member|
= render 'groups/group_members/group_member', member: member, show_roles: show_roles, show_controls: true = render 'groups/group_members/group_member', member: member, show_roles: show_roles, show_controls: true
= paginate @members, theme: 'gitlab' = paginate @members, theme: 'gitlab'
:coffeescript :coffeescript
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
%span.label.label-gray %span.label.label-gray
= repository_size(project) = repository_size(project)
.pull-right .pull-right
= link_to 'Members', namespace_project_team_index_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" = link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm"
= link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove" = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove"
- if @projects.blank? - if @projects.blank?
......
...@@ -24,8 +24,8 @@ ...@@ -24,8 +24,8 @@
Merge Requests Merge Requests
- if current_user - if current_user
%span.count= MergeRequest.opened.of_group(@group).count %span.count= MergeRequest.opened.of_group(@group).count
= nav_link(path: 'groups#members') do = nav_link(controller: [:group_members]) do
= link_to members_group_path(@group), title: 'Members' do = link_to group_group_members_path(@group), title: 'Members' do
%i.fa.fa-users %i.fa.fa-users
%span %span
Members Members
......
%p %p
= "You have been granted #{@membership.human_access} access to group" = "You have been granted #{@group_member.human_access} access to group"
= link_to group_url(@group) do = link_to group_url(@group) do
= @group.name = @group.name
You have been granted <%= @membership.human_access %> access to group <%= @group.name %> You have been granted <%= @group_member.human_access %> access to group <%= @group.name %>
<%= url_for(group_url(@group)) %> <%= url_for(group_url(@group)) %>
...@@ -62,9 +62,9 @@ ...@@ -62,9 +62,9 @@
By default, all projects and groups will use the notification level set above. By default, all projects and groups will use the notification level set above.
%h4 Groups: %h4 Groups:
%ul.bordered-list %ul.bordered-list
- @group_members.each do |users_group| - @group_members.each do |group_member|
- notification = Notification.new(users_group) - notification = Notification.new(group_member)
= render 'settings', type: 'group', membership: users_group, notification: notification = render 'settings', type: 'group', membership: group_member, notification: notification
.col-md-6 .col-md-6
%p %p
......
...@@ -15,9 +15,9 @@ ...@@ -15,9 +15,9 @@
%li %li
= link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
New snippet New snippet
- if can?(current_user, :admin_team_member, @project) - if can?(current_user, :admin_project_member, @project)
%li %li
= link_to new_namespace_project_team_member_path(@project.namespace, @project), title: "New project member" do = link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do
New project member New project member
- if can? current_user, :push_code, @project - if can? current_user, :push_code, @project
%li.divider %li.divider
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
%span %span
Project Project
= nav_link(controller: [:team_members, :teams]) do = nav_link(controller: [:project_members, :teams]) do
= link_to namespace_project_team_index_path(@project.namespace, @project), title: 'Members', class: "team-tab tab" do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: "team-tab tab" do
%i.fa.fa-users %i.fa.fa-users
%span %span
Members Members
......
- group_users_count = @group.group_members.count
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%strong #{@group.name} %strong #{@group.name}
group members (#{group_users_count}) group members
%small
(#{members.count})
.pull-right .pull-right
= link_to members_group_path(@group), class: 'btn btn-sm' do = link_to group_group_members_path(@group), class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
%ul.well-list %ul.well-list
- @group.group_members.order('access_level DESC').limit(20).each do |member| - members.each do |member|
= render 'groups/group_members/group_member', member: member, show_controls: false = render 'groups/group_members/group_member', member: member, show_controls: false
- if group_users_count > 20 - if members.count > 20
%li %li
and #{group_users_count - 20} more. For full list visit #{link_to 'group members page', members_group_path(@group)} and #{members.count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(@group)}
= form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f|
.form-group
= f.label :user_ids, "People", class: 'control-label'
.col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large')
.form-group
= f.label :access_level, "Project Access", class: 'control-label'
.col-sm-10
= select_tag :access_level, options_for_select(ProjectMember.access_roles, @project_member.access_level), class: "project-access-select select2"
.help-block
Read more about role permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
.form-actions
= f.submit 'Add users to project', class: "btn btn-create"
- user = member.user
- return unless user
%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)}
%span.list-item-name
= image_tag avatar_icon(user.email, 16), class: "avatar s16"
%strong= user.name
%span.cgray= user.username
- if user == current_user
%span.label.label-success It's you
- if user.blocked?
%label.label.label-danger
%strong Blocked
- if current_user_can_admin_project
- unless @project.personal? && user == current_user
.pull-right
%strong= member.human_access
= button_tag class: "btn-xs btn js-toggle-button",
title: 'Edit access level', type: 'button' do
%i.fa.fa-pencil-square-o
- if current_user == user
= link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: "Leave project?"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do
%i.fa.fa-minus.fa-inverse
- else
= link_to namespace_project_project_member_path(@project.namespace, @project, user), data: { confirm: remove_from_project_team_message(@project, user) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from team' do
%i.fa.fa-minus.fa-inverse
.edit-member.hide.js-toggle-content
= form_for member, as: :project_member, url: namespace_project_project_member_path(@project.namespace, @project, member.user), remote: true do |f|
.alert.prepend-top-20
= f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level)
= f.submit 'Save', class: 'btn btn-save btn-sm'
- can_admin_project = can?(current_user, :admin_project, @project)
.panel.panel-default.prepend-top-20
.panel-heading
%strong #{@project.name}
project members
%small
(#{members.count})
%ul.well-list
- members.each do |project_member|
= render 'project_member', member: project_member, current_user_can_admin_project: can_admin_project
...@@ -3,12 +3,12 @@ ...@@ -3,12 +3,12 @@
%p.light %p.light
Only project members will be imported. Group members will be skipped. Only project members will be imported. Group members will be skipped.
%hr %hr
= form_tag apply_import_namespace_project_team_members_path(@project.namespace, @project), method: 'post', class: 'form-horizontal' do = form_tag apply_import_namespace_project_project_members_path(@project.namespace, @project), method: 'post', class: 'form-horizontal' do
.form-group .form-group
= label_tag :source_project_id, "Project", class: 'control-label' = label_tag :source_project_id, "Project", class: 'control-label'
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true) .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions .form-actions
= button_tag 'Import project members', class: "btn btn-create" = button_tag 'Import project members', class: "btn btn-create"
= link_to "Cancel", namespace_project_team_index_path(@project.namespace, @project), class: "btn btn-cancel" = link_to "Cancel", namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-cancel"
%h3.page-title
Users with access to this project
%p.light
Read more about project permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
%hr
.clearfix.js-toggle-container
= form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
= button_tag 'Search', class: 'btn'
- if can?(current_user, :admin_project_member, @project)
%span.pull-right
= button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do
Add members
%i.fa.fa-chevron-down
= link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
Import members
.js-toggle-content.hide.new-group-member-holder
= render "new_project_member"
= render "team", members: @project_members
- if @group
= render "group_members", members: @group_members
:coffeescript
$('form.member-search-form').on 'submit', (event) ->
event.preventDefault()
Turbolinks.visit @.action + '?' + $(@).serialize()
- can_admin_project = can?(current_user, :admin_project, @project)
:plain
$("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member, current_user_can_admin_project: can_admin_project))}');
%h3.page-title
New project member(s)
= form_for @user_project_relation, as: :project_member, url: namespace_project_team_members_path(@project.namespace, @project), html: { class: "form-horizontal users-project-form" } do |f|
-if @user_project_relation.errors.any?
.alert.alert-danger
%ul
- @user_project_relation.errors.full_messages.each do |msg|
%li= msg
%p 1. Choose people you want in the project
.form-group
= f.label :user_ids, "People", class: 'control-label'
.col-sm-10
= users_select_tag(:user_ids, multiple: true)
%p 2. Set access level for them
.form-group
= f.label :access_level, "Project Access", class: 'control-label'
.col-sm-10
= select_tag :access_level, options_for_select(Gitlab::Access.options, @user_project_relation.access_level), class: "project-access-select select2"
.help-block
Read more about role permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
.form-actions
= f.submit 'Add users', class: "btn btn-create"
= link_to "Cancel", namespace_project_team_index_path(@project.namespace, @project), class: "btn btn-cancel"
.team-table
- can_admin_project = (can? current_user, :admin_project, @project)
.panel.panel-default
.panel-heading
%strong #{@project.name}
project members (#{members.count})
%ul.well-list
- members.each do |team_member|
= render 'team_member', member: team_member, current_user_can_admin_project: can_admin_project
- user = member.user
%li{id: dom_id(user), class: "team_member_row access-#{member.human_access.downcase}"}
.pull-right
- if current_user_can_admin_project
- unless @project.personal? && user == current_user
.pull-left
= form_for(member, as: :project_member, url: namespace_project_team_member_path(@project.namespace, @project, member.user)) do |f|
= f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "trigger-submit"
&nbsp;
= link_to namespace_project_team_member_path(@project.namespace, @project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from team' do
%i.fa.fa-minus.fa-inverse
= image_tag avatar_icon(user.email, 32), class: "avatar s32"
%p
%strong= user.name
- if user.blocked?
%label.label.label-danger
%strong Blocked
%span.cgray= user.username
%h3.page-title
Users with access to this project
- if can? current_user, :admin_team_member, @project
%span.pull-right
= link_to new_namespace_project_team_member_path(@project.namespace, @project), class: "btn btn-new btn-grouped", title: "New project member" do
New project member
= link_to import_namespace_project_team_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do
Import members
%p.light
Read more about project permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
= render "team", members: @project_members
- if @group
= render "group_members"
- if @user_project_relation.valid?
:plain
$("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#529214"}, 1000);;
- else
:plain
$("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#D12F19"}, 1000);;
...@@ -136,7 +136,7 @@ Gitlab::Application.routes.draw do ...@@ -136,7 +136,7 @@ Gitlab::Application.routes.draw do
resources :groups, constraints: { id: /[^\/]+/ } do resources :groups, constraints: { id: /[^\/]+/ } do
member do member do
put :project_teams_update put :members_update
end end
end end
...@@ -215,11 +215,7 @@ Gitlab::Application.routes.draw do ...@@ -215,11 +215,7 @@ Gitlab::Application.routes.draw do
scope module: :dashboard do scope module: :dashboard do
resources :milestones, only: [:index, :show] resources :milestones, only: [:index, :show]
resources :groups, only: [:index] do resources :groups, only: [:index]
member do
delete :leave
end
end
resources :projects, only: [] do resources :projects, only: [] do
collection do collection do
...@@ -236,12 +232,14 @@ Gitlab::Application.routes.draw do ...@@ -236,12 +232,14 @@ Gitlab::Application.routes.draw do
member do member do
get :issues get :issues
get :merge_requests get :merge_requests
get :members
get :projects get :projects
end end
scope module: :groups do scope module: :groups do
resources :group_members, only: [:create, :update, :destroy] resources :group_members, only: [:index, :create, :update, :destroy] do
delete :leave, on: :collection
end
resource :avatar, only: [:destroy] resource :avatar, only: [:destroy]
resources :milestones, only: [:index, :show, :update] resources :milestones, only: [:index, :show, :update]
end end
...@@ -425,7 +423,6 @@ Gitlab::Application.routes.draw do ...@@ -425,7 +423,6 @@ Gitlab::Application.routes.draw do
end end
end end
resources :team, controller: 'team_members', only: [:index]
resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do
member do member do
put :sort_issues put :sort_issues
...@@ -445,7 +442,7 @@ Gitlab::Application.routes.draw do ...@@ -445,7 +442,7 @@ Gitlab::Application.routes.draw do
end end
end end
resources :team_members, except: [:index, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do
collection do collection do
delete :leave delete :leave
......
...@@ -13,7 +13,7 @@ Feature: Project Team Management ...@@ -13,7 +13,7 @@ Feature: Project Team Management
@javascript @javascript
Scenario: Add user to project Scenario: Add user to project
Given I click link "New Team Member" Given I click link "Add members"
And I select "Mike" as "Reporter" And I select "Mike" as "Reporter"
Then I should see "Mike" in team list as "Reporter" Then I should see "Mike" in team list as "Reporter"
......
...@@ -38,7 +38,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps ...@@ -38,7 +38,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
When 'I select user "John Doe" from user list as "Reporter"' do When 'I select user "John Doe" from user list as "Reporter"' do
select2(user_john.id, from: "#user_ids", multiple: true) select2(user_john.id, from: "#user_ids", multiple: true)
within "#new_team_member" do within "#new_project_member" do
select "Reporter", from: "access_level" select "Reporter", from: "access_level"
end end
click_button "Add users to group" click_button "Add users to group"
......
...@@ -35,7 +35,7 @@ class Spinach::Features::ExploreGroups < Spinach::FeatureSteps ...@@ -35,7 +35,7 @@ class Spinach::Features::ExploreGroups < Spinach::FeatureSteps
end end
step 'I visit group "TestGroup" members page' do step 'I visit group "TestGroup" members page' do
visit members_group_path(Group.find_by(name: "TestGroup")) visit group_group_members_path(Group.find_by(name: "TestGroup"))
end end
step 'I should not see project "Enterprise" items' do step 'I should not see project "Enterprise" items' do
......
...@@ -15,18 +15,18 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps ...@@ -15,18 +15,18 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
page.should have_content(user.username) page.should have_content(user.username)
end end
step 'I click link "New Team Member"' do step 'I click link "Add members"' do
click_link "New project member" find(:css, 'button.btn-new').click
end end
step 'I select "Mike" as "Reporter"' do step 'I select "Mike" as "Reporter"' do
user = User.find_by(name: "Mike") user = User.find_by(name: "Mike")
within ".users-project-form" do
select2(user.id, from: "#user_ids", multiple: true) select2(user.id, from: "#user_ids", multiple: true)
within "#new_project_member" do
select "Reporter", from: "access_level" select "Reporter", from: "access_level"
end end
click_button "Add users" click_button "Add users to project"
end end
step 'I should see "Mike" in team list as "Reporter"' do step 'I should see "Mike" in team list as "Reporter"' do
...@@ -42,9 +42,13 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps ...@@ -42,9 +42,13 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
end end
step 'I change "Sam" role to "Reporter"' do step 'I change "Sam" role to "Reporter"' do
user = User.find_by(name: "Sam") project = Project.find_by(name: "Shop")
within "#user_#{user.id}" do user = User.find_by(name: 'Sam')
project_member = project.project_members.find_by(user_id: user.id)
within "#project_member_#{project_member.id}" do
click_button "Edit access level"
select "Reporter", from: "project_member_access_level" select "Reporter", from: "project_member_access_level"
click_button "Save"
end end
end end
...@@ -100,7 +104,10 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps ...@@ -100,7 +104,10 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps
end end
step 'I click cancel link for "Sam"' do step 'I click cancel link for "Sam"' do
within "#user_#{User.find_by(name: 'Sam').id}" do project = Project.find_by(name: "Shop")
user = User.find_by(name: 'Sam')
project_member = project.project_members.find_by(user_id: user.id)
within "#project_member_#{project_member.id}" do
click_link('Remove user from team') click_link('Remove user from team')
end end
end end
......
...@@ -32,7 +32,7 @@ module SharedPaths ...@@ -32,7 +32,7 @@ module SharedPaths
end end
step 'I visit group "Owned" members page' do step 'I visit group "Owned" members page' do
visit members_group_path(Group.find_by(name:"Owned")) visit group_group_members_path(Group.find_by(name:"Owned"))
end end
step 'I visit group "Owned" settings page' do step 'I visit group "Owned" settings page' do
...@@ -52,7 +52,7 @@ module SharedPaths ...@@ -52,7 +52,7 @@ module SharedPaths
end end
step 'I visit group "Guest" members page' do step 'I visit group "Guest" members page' do
visit members_group_path(Group.find_by(name:"Guest")) visit group_group_members_path(Group.find_by(name:"Guest"))
end end
step 'I visit group "Guest" settings page' do step 'I visit group "Guest" settings page' do
...@@ -386,7 +386,7 @@ module SharedPaths ...@@ -386,7 +386,7 @@ module SharedPaths
end end
step 'I visit project "Shop" team page' do step 'I visit project "Shop" team page' do
visit namespace_project_team_index_path(project.namespace, project) visit namespace_project_project_members_path(project.namespace, project)
end end
step 'I visit project wiki page' do step 'I visit project wiki page' do
......
...@@ -53,14 +53,14 @@ module API ...@@ -53,14 +53,14 @@ module API
authorize! :manage_group, group authorize! :manage_group, group
required_attributes! [:access_level] required_attributes! [:access_level]
team_member = group.group_members.find_by(user_id: params[:user_id]) group_member = group.group_members.find_by(user_id: params[:user_id])
not_found!('User can not be found') if team_member.nil? not_found!('User can not be found') if group_member.nil?
if team_member.update_attributes(access_level: params[:access_level]) if group_member.update_attributes(access_level: params[:access_level])
@member = team_member.user @member = group_member.user
present @member, with: Entities::GroupMember, group: group present @member, with: Entities::GroupMember, group: group
else else
handle_member_errors team_member.errors handle_member_errors group_member.errors
end end
end end
......
...@@ -46,19 +46,19 @@ module API ...@@ -46,19 +46,19 @@ module API
required_attributes! [:user_id, :access_level] required_attributes! [:user_id, :access_level]
# either the user is already a team member or a new one # either the user is already a team member or a new one
team_member = user_project.team_member_by_id(params[:user_id]) project_member = user_project.project_member_by_id(params[:user_id])
if team_member.nil? if project_member.nil?
team_member = user_project.project_members.new( project_member = user_project.project_members.new(
user_id: params[:user_id], user_id: params[:user_id],
access_level: params[:access_level] access_level: params[:access_level]
) )
end end
if team_member.save if project_member.save
@member = team_member.user @member = project_member.user
present @member, with: Entities::ProjectMember, project: user_project present @member, with: Entities::ProjectMember, project: user_project
else else
handle_member_errors team_member.errors handle_member_errors project_member.errors
end end
end end
...@@ -74,14 +74,14 @@ module API ...@@ -74,14 +74,14 @@ module API
authorize! :admin_project, user_project authorize! :admin_project, user_project
required_attributes! [:access_level] required_attributes! [:access_level]
team_member = user_project.project_members.find_by(user_id: params[:user_id]) project_member = user_project.project_members.find_by(user_id: params[:user_id])
not_found!("User can not be found") if team_member.nil? not_found!("User can not be found") if project_member.nil?
if team_member.update_attributes(access_level: params[:access_level]) if project_member.update_attributes(access_level: params[:access_level])
@member = team_member.user @member = project_member.user
present @member, with: Entities::ProjectMember, project: user_project present @member, with: Entities::ProjectMember, project: user_project
else else
handle_member_errors team_member.errors handle_member_errors project_member.errors
end end
end end
...@@ -94,9 +94,9 @@ module API ...@@ -94,9 +94,9 @@ module API
# DELETE /projects/:id/members/:user_id # DELETE /projects/:id/members/:user_id
delete ":id/members/:user_id" do delete ":id/members/:user_id" do
authorize! :admin_project, user_project authorize! :admin_project, user_project
team_member = user_project.project_members.find_by(user_id: params[:user_id]) project_member = user_project.project_members.find_by(user_id: params[:user_id])
unless team_member.nil? unless project_member.nil?
team_member.destroy project_member.destroy
else else
{ message: "Access revoked", id: params[:user_id].to_i } { message: "Access revoked", id: params[:user_id].to_i }
end end
......
...@@ -200,7 +200,7 @@ module Gitlab ...@@ -200,7 +200,7 @@ module Gitlab
def reference_user(identifier, project = @project, _ = nil) def reference_user(identifier, project = @project, _ = nil)
options = html_options.merge( options = html_options.merge(
class: "gfm gfm-team_member #{html_options[:class]}" class: "gfm gfm-project_member #{html_options[:class]}"
) )
if identifier == "all" if identifier == "all"
......
...@@ -59,8 +59,8 @@ describe "Group access", feature: true do ...@@ -59,8 +59,8 @@ describe "Group access", feature: true do
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
end end
describe "GET /groups/:path/members" do describe "GET /groups/:path/group_members" do
subject { members_group_path(group) } subject { group_group_members_path(group) }
it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
......
...@@ -55,8 +55,8 @@ describe "Group with internal project access", feature: true do ...@@ -55,8 +55,8 @@ describe "Group with internal project access", feature: true do
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
end end
describe "GET /groups/:path/members" do describe "GET /groups/:path/group_members" do
subject { members_group_path(group) } subject { group_group_members_path(group) }
it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
......
...@@ -56,8 +56,8 @@ describe "Group access", feature: true do ...@@ -56,8 +56,8 @@ describe "Group access", feature: true do
it { is_expected.to be_allowed_for :visitor } it { is_expected.to be_allowed_for :visitor }
end end
describe "GET /groups/:path/members" do describe "GET /groups/:path/group_members" do
subject { members_group_path(group) } subject { group_group_members_path(group) }
it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
......
...@@ -55,8 +55,8 @@ describe "Group with public project access", feature: true do ...@@ -55,8 +55,8 @@ describe "Group with public project access", feature: true do
it { is_expected.to be_allowed_for :visitor } it { is_expected.to be_allowed_for :visitor }
end end
describe "GET /groups/:path/members" do describe "GET /groups/:path/group_members" do
subject { members_group_path(group) } subject { group_group_members_path(group) }
it { is_expected.to be_allowed_for owner } it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
......
...@@ -79,8 +79,8 @@ describe "Internal Project Access", feature: true do ...@@ -79,8 +79,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
end end
describe "GET /:project_path/team" do describe "GET /:project_path/project_members" do
subject { namespace_project_team_index_path(project.namespace, project) } subject { namespace_project_project_members_path(project.namespace, project) }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
it { is_expected.to be_denied_for reporter } it { is_expected.to be_denied_for reporter }
......
...@@ -79,8 +79,8 @@ describe "Private Project Access", feature: true do ...@@ -79,8 +79,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
end end
describe "GET /:project_path/team" do describe "GET /:project_path/project_members" do
subject { namespace_project_team_index_path(project.namespace, project) } subject { namespace_project_project_members_path(project.namespace, project) }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
it { is_expected.to be_denied_for reporter } it { is_expected.to be_denied_for reporter }
......
...@@ -84,8 +84,8 @@ describe "Public Project Access", feature: true do ...@@ -84,8 +84,8 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :visitor } it { is_expected.to be_allowed_for :visitor }
end end
describe "GET /:project_path/team" do describe "GET /:project_path/project_members" do
subject { namespace_project_team_index_path(project.namespace, project) } subject { namespace_project_project_members_path(project.namespace, project) }
it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for master }
it { is_expected.to be_denied_for reporter } it { is_expected.to be_denied_for reporter }
......
...@@ -180,7 +180,7 @@ describe GitlabMarkdownHelper do ...@@ -180,7 +180,7 @@ describe GitlabMarkdownHelper do
end end
it "should include standard gfm classes" do it "should include standard gfm classes" do
expect(gfm(actual)).to match(/class="\s?gfm gfm-team_member\s?"/) expect(gfm(actual)).to match(/class="\s?gfm gfm-project_member\s?"/)
end end
end end
......
...@@ -28,18 +28,18 @@ describe GroupMember do ...@@ -28,18 +28,18 @@ describe GroupMember do
describe "#after_update" do describe "#after_update" do
before do before do
@membership = create :group_member @group_member = create :group_member
@membership.stub(notification_service: double('NotificationService').as_null_object) @group_member.stub(notification_service: double('NotificationService').as_null_object)
end end
it "should send email to user" do it "should send email to user" do
expect(@membership).to receive(:notification_service) expect(@group_member).to receive(:notification_service)
@membership.update_attribute(:access_level, GroupMember::MASTER) @group_member.update_attribute(:access_level, GroupMember::MASTER)
end end
it "does not send an email when the access level has not changed" do it "does not send an email when the access level has not changed" do
expect(@membership).not_to receive(:notification_service) expect(@group_member).not_to receive(:notification_service)
@membership.update_attribute(:access_level, GroupMember::OWNER) @group_member.update_attribute(:access_level, GroupMember::OWNER)
end end
end end
end end
......
...@@ -338,17 +338,14 @@ describe Projects::CommitsController, 'routing' do ...@@ -338,17 +338,14 @@ describe Projects::CommitsController, 'routing' do
end end
end end
# project_team_members GET /:project_id/team_members(.:format) team_members#index # project_project_members GET /:project_id/project_members(.:format) project_members#index
# POST /:project_id/team_members(.:format) team_members#create # POST /:project_id/project_members(.:format) project_members#create
# new_project_team_member GET /:project_id/team_members/new(.:format) team_members#new # PUT /:project_id/project_members/:id(.:format) project_members#update
# edit_project_team_member GET /:project_id/team_members/:id/edit(.:format) team_members#edit # DELETE /:project_id/project_members/:id(.:format) project_members#destroy
# project_team_member GET /:project_id/team_members/:id(.:format) team_members#show describe Projects::ProjectMembersController, 'routing' do
# PUT /:project_id/team_members/:id(.:format) team_members#update
# DELETE /:project_id/team_members/:id(.:format) team_members#destroy
describe Projects::TeamMembersController, 'routing' do
it_behaves_like 'RESTful project resources' do it_behaves_like 'RESTful project resources' do
let(:actions) { [:new, :create, :update, :destroy] } let(:actions) { [:index, :create, :update, :destroy] }
let(:controller) { 'team_members' } let(:controller) { 'project_members' }
end end
end end
......
...@@ -69,9 +69,9 @@ describe NotificationService do ...@@ -69,9 +69,9 @@ describe NotificationService do
user_project = note.project.project_members.find_by_user_id(@u_watcher.id) user_project = note.project.project_members.find_by_user_id(@u_watcher.id)
user_project.notification_level = Notification::N_PARTICIPATING user_project.notification_level = Notification::N_PARTICIPATING
user_project.save user_project.save
user_group = note.project.group.group_members.find_by_user_id(@u_watcher.id) group_member = note.project.group.group_members.find_by_user_id(@u_watcher.id)
user_group.notification_level = Notification::N_GLOBAL group_member.notification_level = Notification::N_GLOBAL
user_group.save group_member.save
end end
it do it do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment