Commit 30d2d2381f608b7c926747d61a9231aa417ea5da

Authored by Dmitriy Zaporozhets
2 parents cc5440e8 60bb3516

Merge branch 'feature/users_groups' into 6-0-dev

Showing 41 changed files with 396 additions and 252 deletions   Show diff stats
app/assets/javascripts/dispatcher.js.coffee
@@ -32,6 +32,8 @@ class Dispatcher @@ -32,6 +32,8 @@ class Dispatcher
32 new Wall(project_id) 32 new Wall(project_id)
33 when 'teams:members:index' 33 when 'teams:members:index'
34 new TeamMembers() 34 new TeamMembers()
  35 + when 'groups:people'
  36 + new GroupMembers()
35 37
36 switch path.first() 38 switch path.first()
37 when 'admin' then new Admin() 39 when 'admin' then new Admin()
app/assets/javascripts/groups.js.coffee 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +class GroupMembers
  2 + constructor: ->
  3 + $('li.users_group').bind 'ajax:success', ->
  4 + $(this).fadeOut()
  5 +
  6 +@GroupMembers = GroupMembers
app/assets/stylesheets/gitlab_bootstrap/lists.scss
@@ -16,6 +16,12 @@ @@ -16,6 +16,12 @@
16 color: #888; 16 color: #888;
17 } 17 }
18 18
  19 + &.unstyled {
  20 + &:hover {
  21 + background: none;
  22 + }
  23 + }
  24 +
19 &.smoke { background-color: #f5f5f5; } 25 &.smoke { background-color: #f5f5f5; }
20 26
21 &:hover { 27 &:hover {
app/controllers/admin/groups_controller.rb
@@ -66,14 +66,12 @@ class Admin::GroupsController < Admin::ApplicationController @@ -66,14 +66,12 @@ class Admin::GroupsController < Admin::ApplicationController
66 end 66 end
67 67
68 def project_teams_update 68 def project_teams_update
69 - @group.add_users_to_project_teams(params[:user_ids].split(','), params[:project_access]) 69 + @group.add_users(params[:user_ids].split(','), params[:group_access])
70 70
71 redirect_to [:admin, @group], notice: 'Users were successfully added.' 71 redirect_to [:admin, @group], notice: 'Users were successfully added.'
72 end 72 end
73 73
74 def destroy 74 def destroy
75 - @group.truncate_teams  
76 -  
77 @group.destroy 75 @group.destroy
78 76
79 redirect_to admin_groups_path, notice: 'Group was successfully deleted.' 77 redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
app/controllers/dashboard_controller.rb
@@ -34,11 +34,12 @@ class DashboardController < ApplicationController @@ -34,11 +34,12 @@ class DashboardController < ApplicationController
34 @projects 34 @projects
35 end 35 end
36 36
37 - @projects = @projects.tagged_with(params[:label]) if params[:label].present?  
38 @projects = @projects.search(params[:search]) if params[:search].present? 37 @projects = @projects.search(params[:search]) if params[:search].present?
39 - @projects = @projects.page(params[:page]).per(30)  
40 38
41 @labels = Project.where(id: @projects.map(&:id)).tags_on(:labels) 39 @labels = Project.where(id: @projects.map(&:id)).tags_on(:labels)
  40 +
  41 + @projects = @projects.tagged_with(params[:label]) if params[:label].present?
  42 + @projects = @projects.page(params[:page]).per(30)
42 end 43 end
43 44
44 # Get authored or assigned open merge requests 45 # Get authored or assigned open merge requests
app/controllers/graphs_controller.rb
@@ -10,7 +10,7 @@ class GraphsController < ProjectResourceController @@ -10,7 +10,7 @@ class GraphsController < ProjectResourceController
10 format.js do 10 format.js do
11 @repo = @project.repository 11 @repo = @project.repository
12 @stats = Gitlab::Git::GitStats.new(@repo.raw, @repo.root_ref) 12 @stats = Gitlab::Git::GitStats.new(@repo.raw, @repo.root_ref)
13 - @log = @stats.parsed_log.to_json 13 + @log = @stats.parsed_log.to_json rescue []
14 end 14 end
15 end 15 end
16 end 16 end
app/controllers/groups_controller.rb
1 class GroupsController < ApplicationController 1 class GroupsController < ApplicationController
2 respond_to :html 2 respond_to :html
3 - before_filter :group, except: [:new, :create] 3 + before_filter :group, except: [:new, :create, :people]
4 4
5 # Authorize 5 # Authorize
6 before_filter :authorize_read_group!, except: [:new, :create] 6 before_filter :authorize_read_group!, except: [:new, :create]
@@ -63,19 +63,8 @@ class GroupsController &lt; ApplicationController @@ -63,19 +63,8 @@ class GroupsController &lt; ApplicationController
63 63
64 def people 64 def people
65 @project = group.projects.find(params[:project_id]) if params[:project_id] 65 @project = group.projects.find(params[:project_id]) if params[:project_id]
66 - @users = @project ? @project.users : group.users  
67 - @users.sort_by!(&:name)  
68 -  
69 - if @project  
70 - @team_member = @project.users_projects.new  
71 - else  
72 - @team_member = UsersProject.new  
73 - end  
74 - end  
75 -  
76 - def team_members  
77 - @group.add_users_to_project_teams(params[:user_ids].split(','), params[:project_access])  
78 - redirect_to people_group_path(@group), notice: 'Users were successfully added.' 66 + @members = group.users_groups.order('group_access DESC')
  67 + @users_group = UsersGroup.new
79 end 68 end
80 69
81 def edit 70 def edit
@@ -83,7 +72,7 @@ class GroupsController &lt; ApplicationController @@ -83,7 +72,7 @@ class GroupsController &lt; ApplicationController
83 72
84 def update 73 def update
85 group_params = params[:group].dup 74 group_params = params[:group].dup
86 - owner_id =group_params.delete(:owner_id) 75 + owner_id = group_params.delete(:owner_id)
87 76
88 if owner_id 77 if owner_id
89 @group.owner = User.find(owner_id) 78 @group.owner = User.find(owner_id)
app/controllers/issues_controller.rb
@@ -23,7 +23,7 @@ class IssuesController &lt; ProjectResourceController @@ -23,7 +23,7 @@ class IssuesController &lt; ProjectResourceController
23 23
24 assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] 24 assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
25 25
26 - @assignee = @project.users.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero? 26 + @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
27 @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? 27 @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
28 28
29 respond_to do |format| 29 respond_to do |format|
app/controllers/team_members_controller.rb
@@ -4,10 +4,8 @@ class TeamMembersController &lt; ProjectResourceController @@ -4,10 +4,8 @@ class TeamMembersController &lt; ProjectResourceController
4 before_filter :authorize_admin_project!, except: [:index, :show] 4 before_filter :authorize_admin_project!, except: [:index, :show]
5 5
6 def index 6 def index
7 - @team = @project.users_projects.scoped  
8 - @team = @team.send(params[:type]) if %w(masters developers reporters guests).include?(params[:type])  
9 - @team = @team.sort_by(&:project_access).reverse.group_by(&:project_access)  
10 - 7 + @group = @project.group
  8 + @users_projects = @project.users_projects.order('project_access DESC')
11 @assigned_teams = @project.user_team_project_relationships 9 @assigned_teams = @project.user_team_project_relationships
12 end 10 end
13 11
app/controllers/users_controller.rb
@@ -3,7 +3,7 @@ class UsersController &lt; ApplicationController @@ -3,7 +3,7 @@ class UsersController &lt; ApplicationController
3 3
4 def show 4 def show
5 @user = User.find_by_username!(params[:username]) 5 @user = User.find_by_username!(params[:username])
6 - @projects = @user.authorized_projects.where('projects.id in (?)', current_user.authorized_projects.map(&:id)) 6 + @projects = @user.authorized_projects.where('projects.id in (?)', current_user.authorized_projects.map(&:id)).order('namespace_id DESC')
7 @events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20) 7 @events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20)
8 8
9 @title = @user.name 9 @title = @user.name
app/controllers/users_groups_controller.rb 0 → 100644
@@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
  1 +class UsersGroupsController < ApplicationController
  2 + before_filter :group
  3 +
  4 + # Authorize
  5 + before_filter :authorize_admin_group!
  6 +
  7 + layout 'group'
  8 +
  9 + def create
  10 + @group.add_users(params[:user_ids].split(','), params[:group_access])
  11 +
  12 + redirect_to people_group_path(@group), notice: 'Users were successfully added.'
  13 + end
  14 +
  15 + def update
  16 + # TODO: implement
  17 + end
  18 +
  19 + def destroy
  20 + @users_group = @group.users_groups.find(params[:id])
  21 + @users_group.destroy
  22 +
  23 + respond_to do |format|
  24 + format.html { redirect_to people_group_path(@group), notice: 'User was successfully removed from group.' }
  25 + format.js { render nothing: true }
  26 + end
  27 + end
  28 +
  29 + protected
  30 +
  31 + def group
  32 + @group ||= Group.find_by_path(params[:group_id])
  33 + end
  34 +
  35 + def authorize_admin_group!
  36 + unless can?(current_user, :manage_group, group)
  37 + return render_404
  38 + end
  39 + end
  40 +end
app/helpers/groups_helper.rb
@@ -14,4 +14,8 @@ module GroupsHelper @@ -14,4 +14,8 @@ module GroupsHelper
14 merge_requests_group_path(@group, options) 14 merge_requests_group_path(@group, options)
15 end 15 end
16 end 16 end
  17 +
  18 + def remove_user_from_group_message(group, user)
  19 + "You are going to remove #{user.name} from #{group.name} Group. Are you sure?"
  20 + end
17 end 21 end
app/models/ability.rb
@@ -50,6 +50,10 @@ class Ability @@ -50,6 +50,10 @@ class Ability
50 rules << project_admin_rules 50 rules << project_admin_rules
51 end 51 end
52 52
  53 + if project.group && project.group.owners.include?(user)
  54 + rules << project_admin_rules
  55 + end
  56 +
53 rules.flatten 57 rules.flatten
54 end 58 end
55 59
@@ -132,7 +136,7 @@ class Ability @@ -132,7 +136,7 @@ class Ability
132 rules = [] 136 rules = []
133 137
134 # Only group owner and administrators can manage group 138 # Only group owner and administrators can manage group
135 - if group.owner == user || user.admin? 139 + if group.owners.include?(user) || user.admin?
136 rules << [ 140 rules << [
137 :manage_group, 141 :manage_group,
138 :manage_namespace 142 :manage_namespace
app/models/group.rb
@@ -13,26 +13,28 @@ @@ -13,26 +13,28 @@
13 # 13 #
14 14
15 class Group < Namespace 15 class Group < Namespace
  16 + has_many :users_groups, dependent: :destroy
  17 + has_many :users, through: :users_groups
16 18
17 - def add_users_to_project_teams(user_ids, project_access)  
18 - UsersProject.add_users_into_projects(  
19 - projects.map(&:id),  
20 - user_ids,  
21 - project_access  
22 - ) 19 + after_create :add_owner
  20 +
  21 + def human_name
  22 + name
23 end 23 end
24 24
25 - def users  
26 - users = User.joins(:users_projects).where(users_projects: {project_id: project_ids})  
27 - users = users << owner  
28 - users.uniq 25 + def owners
  26 + @owners ||= (users_groups.owners.map(&:user) << owner)
29 end 27 end
30 28
31 - def human_name  
32 - name 29 + def add_users(user_ids, group_access)
  30 + user_ids.compact.each do |user_id|
  31 + self.users_groups.create(user_id: user_id, group_access: group_access)
  32 + end
33 end 33 end
34 34
35 - def truncate_teams  
36 - UsersProject.truncate_teams(project_ids) 35 + private
  36 +
  37 + def add_owner
  38 + self.add_users([owner.id], UsersGroup::OWNER)
37 end 39 end
38 end 40 end
app/models/project_team.rb
@@ -21,6 +21,16 @@ class ProjectTeam @@ -21,6 +21,16 @@ class ProjectTeam
21 end 21 end
22 end 22 end
23 23
  24 + def find user_id
  25 + user = project.users.find_by_id(user_id)
  26 +
  27 + if group
  28 + user ||= group.users.find_by_id(user_id)
  29 + end
  30 +
  31 + user
  32 + end
  33 +
24 def get_tm user_id 34 def get_tm user_id
25 project.users_projects.find_by_user_id(user_id) 35 project.users_projects.find_by_user_id(user_id)
26 end 36 end
@@ -47,23 +57,23 @@ class ProjectTeam @@ -47,23 +57,23 @@ class ProjectTeam
47 end 57 end
48 58
49 def members 59 def members
50 - project.users_projects 60 + fetch_members
51 end 61 end
52 62
53 def guests 63 def guests
54 - members.guests.map(&:user) 64 + @guests ||= fetch_members(:guests)
55 end 65 end
56 66
57 def reporters 67 def reporters
58 - members.reporters.map(&:user) 68 + @reporters ||= fetch_members(:reporters)
59 end 69 end
60 70
61 def developers 71 def developers
62 - members.developers.map(&:user) 72 + @developers ||= fetch_members(:developers)
63 end 73 end
64 74
65 def masters 75 def masters
66 - members.masters.map(&:user) 76 + @masters ||= fetch_members(:masters)
67 end 77 end
68 78
69 def import(source_project) 79 def import(source_project)
@@ -96,4 +106,22 @@ class ProjectTeam @@ -96,4 +106,22 @@ class ProjectTeam
96 rescue 106 rescue
97 false 107 false
98 end 108 end
  109 +
  110 + private
  111 +
  112 + def fetch_members(level = nil)
  113 + project_members = project.users_projects
  114 + group_members = group ? group.users_groups : []
  115 +
  116 + if level
  117 + project_members = project_members.send(level)
  118 + group_members = group_members.send(level) if group
  119 + end
  120 +
  121 + (project_members + group_members).map(&:user).uniq
  122 + end
  123 +
  124 + def group
  125 + project.group
  126 + end
99 end 127 end
app/models/user.rb
@@ -71,7 +71,9 @@ class User &lt; ActiveRecord::Base @@ -71,7 +71,9 @@ class User &lt; ActiveRecord::Base
71 has_many :keys, dependent: :destroy 71 has_many :keys, dependent: :destroy
72 72
73 # Groups 73 # Groups
74 - has_many :groups, class_name: "Group", foreign_key: :owner_id 74 + has_many :own_groups, class_name: "Group", foreign_key: :owner_id
  75 + has_many :users_groups, dependent: :destroy
  76 + has_many :groups, through: :users_groups
75 77
76 # Teams 78 # Teams
77 has_many :own_teams, dependent: :destroy, class_name: "UserTeam", foreign_key: :owner_id 79 has_many :own_teams, dependent: :destroy, class_name: "UserTeam", foreign_key: :owner_id
@@ -230,7 +232,7 @@ class User &lt; ActiveRecord::Base @@ -230,7 +232,7 @@ class User &lt; ActiveRecord::Base
230 232
231 # Groups where user is an owner 233 # Groups where user is an owner
232 def owned_groups 234 def owned_groups
233 - groups 235 + own_groups
234 end 236 end
235 237
236 def owned_teams 238 def owned_teams
@@ -239,14 +241,14 @@ class User &lt; ActiveRecord::Base @@ -239,14 +241,14 @@ class User &lt; ActiveRecord::Base
239 241
240 # Groups user has access to 242 # Groups user has access to
241 def authorized_groups 243 def authorized_groups
242 - @group_ids ||= (groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) 244 + @group_ids ||= (groups.pluck(:id) + own_groups.pluck(:id) + authorized_projects.pluck(:namespace_id))
243 Group.where(id: @group_ids) 245 Group.where(id: @group_ids)
244 end 246 end
245 247
246 248
247 # Projects user has access to 249 # Projects user has access to
248 def authorized_projects 250 def authorized_projects
249 - @project_ids ||= (owned_projects.pluck(:id) + projects.pluck(:id)).uniq 251 + @project_ids ||= (owned_projects.pluck(:id) + groups.map(&:projects).flatten.map(&:id) + projects.pluck(:id)).uniq
250 Project.where(id: @project_ids) 252 Project.where(id: @project_ids)
251 end 253 end
252 254
app/models/users_group.rb 0 → 100644
@@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
  1 +class UsersGroup < ActiveRecord::Base
  2 + GUEST = 10
  3 + REPORTER = 20
  4 + DEVELOPER = 30
  5 + MASTER = 40
  6 + OWNER = 50
  7 +
  8 + def self.group_access_roles
  9 + {
  10 + "Guest" => GUEST,
  11 + "Reporter" => REPORTER,
  12 + "Developer" => DEVELOPER,
  13 + "Master" => MASTER,
  14 + "Owner" => OWNER
  15 + }
  16 + end
  17 +
  18 + attr_accessible :group_access, :user_id
  19 +
  20 + belongs_to :user
  21 + belongs_to :group
  22 +
  23 + scope :guests, -> { where(group_access: GUEST) }
  24 + scope :reporters, -> { where(group_access: REPORTER) }
  25 + scope :developers, -> { where(group_access: DEVELOPER) }
  26 + scope :masters, -> { where(group_access: MASTER) }
  27 + scope :owners, -> { where(group_access: OWNER) }
  28 +
  29 + scope :with_group, ->(group) { where(group_id: group.id) }
  30 + scope :with_user, ->(user) { where(user_id: user.id) }
  31 +
  32 + validates :group_access, inclusion: { in: UsersGroup.group_access_roles.values }, presence: true
  33 + validates :user_id, presence: true
  34 + validates :group_id, presence: true
  35 + validates :user_id, uniqueness: { scope: [:group_id], message: "already exists in group" }
  36 +
  37 + delegate :name, :username, :email, to: :user, prefix: true
  38 +
  39 + def human_access
  40 + UsersGroup.group_access_roles.key(self.group_access)
  41 + end
  42 +end
app/models/users_project.rb
@@ -129,9 +129,7 @@ class UsersProject &lt; ActiveRecord::Base @@ -129,9 +129,7 @@ class UsersProject &lt; ActiveRecord::Base
129 Project.access_options.key(self.project_access) 129 Project.access_options.key(self.project_access)
130 end 130 end
131 131
132 - def repo_access_human  
133 - self.class.access_roles.invert[self.project_access]  
134 - end 132 + alias_method :human_access, :project_access_human
135 133
136 def skip_git? 134 def skip_git?
137 !!@skip_git 135 !!@skip_git
app/services/system_hooks_service.rb
@@ -43,7 +43,7 @@ class SystemHooksService @@ -43,7 +43,7 @@ class SystemHooksService
43 project_id: model.project_id, 43 project_id: model.project_id,
44 user_name: model.user.name, 44 user_name: model.user.name,
45 user_email: model.user.email, 45 user_email: model.user.email,
46 - project_access: model.repo_access_human 46 + project_access: model.project_access_human
47 }) 47 })
48 end 48 end
49 end 49 end
app/views/admin/groups/show.html.haml
@@ -49,10 +49,23 @@ @@ -49,10 +49,23 @@
49 %strong 49 %strong
50 = @group.created_at.stamp("March 1, 1999") 50 = @group.created_at.stamp("March 1, 1999")
51 51
  52 + .ui-box
  53 + %h5.title
  54 + Projects
  55 + %small
  56 + (#{@group.projects.count})
  57 + %ul.well-list
  58 + - @group.projects.sort_by(&:name).each do |project|
  59 + %li
  60 + %strong
  61 + = link_to project.name_with_namespace, [:admin, project]
  62 + %span.pull-right.light
  63 + %span.monospace= project.path_with_namespace + ".git"
52 64
  65 + .span6
53 .ui-box 66 .ui-box
54 %h5.title 67 %h5.title
55 - Add user to Group projects: 68 + Add user(s):
56 .ui-box-body.form-holder 69 .ui-box-body.form-holder
57 %p.light 70 %p.light
58 Read more about project permissions 71 Read more about project permissions
@@ -64,30 +77,18 @@ @@ -64,30 +77,18 @@
64 %div.prepend-top-10 77 %div.prepend-top-10
65 = select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span2"} 78 = select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span2"}
66 %hr 79 %hr
67 - = submit_tag 'Add user to projects in group', class: "btn btn-create" 80 + = submit_tag 'Add users into group', class: "btn btn-create"
68 .ui-box 81 .ui-box
69 %h5.title 82 %h5.title
70 - Users from Group projects 83 + Users from #{@group.name} Group
71 %small 84 %small
72 - (#{@group.users.count}) 85 + (#{@group.users_groups.count})
73 %ul.well-list 86 %ul.well-list
74 - - @group.users.sort_by(&:name).each do |user| 87 + - @group.users_groups.order('group_access DESC').each do |member|
  88 + - user = member.user
75 %li{class: dom_class(user)} 89 %li{class: dom_class(user)}
76 %strong 90 %strong
77 = link_to user.name, admin_user_path(user) 91 = link_to user.name, admin_user_path(user)
78 %span.pull-right.light 92 %span.pull-right.light
79 - = pluralize user.authorized_projects.in_namespace(@group).count, 'project' 93 + = member.human_access
80 94
81 - .span6  
82 - .ui-box  
83 - %h5.title  
84 - Projects  
85 - %small  
86 - (#{@group.projects.count})  
87 - %ul.well-list  
88 - - @group.projects.sort_by(&:name).each do |project|  
89 - %li  
90 - %strong  
91 - = link_to project.name_with_namespace, [:admin, project]  
92 - %span.pull-right.light  
93 - %span.monospace= project.path_with_namespace + ".git"  
app/views/groups/_new_group_member.html.haml
1 -= form_for @team_member, as: :team_member, url: team_members_group_path(@group) do |f| 1 += form_for @users_group, url: group_users_groups_path(@group) do |f|
2 %fieldset 2 %fieldset
3 - %legend= "New Team member(s) for projects in #{@group.name}" 3 + %legend= "New Group member(s) for #{@group.name}"
4 4
5 - %h6 1. Choose people you want in the team 5 + %h6 1. Choose people you want in the group
6 .clearfix 6 .clearfix
7 = f.label :user_ids, "People" 7 = f.label :user_ids, "People"
8 - .input= users_select_tag(:user_ids, multiple: true) 8 + .input= users_select_tag(:user_ids, multiple: true, class: 'input-large')
9 9
10 %h6 2. Set access level for them 10 %h6 2. Set access level for them
11 .clearfix 11 .clearfix
12 - = f.label :project_access, "Project Access"  
13 - .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen" 12 + = f.label :group_access, "Group Access"
  13 + .input= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select chosen"
14 14
15 .form-actions 15 .form-actions
16 = hidden_field_tag :redirect_to, people_group_path(@group) 16 = hidden_field_tag :redirect_to, people_group_path(@group)
17 - = f.submit 'Add', class: "btn btn-save" 17 + = f.submit 'Add users into group', class: "btn btn-create"
18 18
app/views/groups/_new_member.html.haml
@@ -1,18 +0,0 @@ @@ -1,18 +0,0 @@
1 -= form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f|  
2 - %fieldset  
3 - %legend= "New Team member(s) for #{@project.name}"  
4 -  
5 - %h6 1. Choose people you want in the team  
6 - .clearfix  
7 - = f.label :user_ids, "People"  
8 - .input= users_select_tag(:user_ids, multiple: true)  
9 -  
10 - %h6 2. Set access level for them  
11 - .clearfix  
12 - = f.label :project_access, "Project Access"  
13 - .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen"  
14 -  
15 - .form-actions  
16 - = hidden_field_tag :redirect_to, people_group_path(@group, project_id: @project.id)  
17 - = f.submit 'Add', class: "btn btn-save"  
18 -  
app/views/groups/_people_filter.html.haml
@@ -1,16 +0,0 @@ @@ -1,16 +0,0 @@
1 -= form_tag people_group_path(@group), method: 'get' do  
2 - %fieldset  
3 - %legend Projects:  
4 - %ul.nav.nav-pills.nav-stacked  
5 - - @projects.each do |project|  
6 - %li{class: ("active" if params[:project_id] == project.id.to_s)}  
7 - = link_to people_group_path(@group, project_id: project.id) do  
8 - = project.name_with_namespace  
9 - %small.pull-right= project.users.count  
10 - - if @projects.blank?  
11 - %p.nothing_here_message This group has no projects yet  
12 -  
13 - %fieldset  
14 - %hr  
15 - = link_to "Reset", people_group_path(@group), class: 'btn pull-right'  
16 -  
app/views/groups/people.html.haml
  1 +- can_manage_group = current_user.can? :manage_group, @group
1 .row 2 .row
2 - .span3  
3 - = render 'people_filter'  
4 - .span9  
5 - - if can?(current_user, :manage_group, @group)  
6 - = render (@project ? "new_member" : "new_group_member") 3 + .span6
  4 + - if can_manage_group
  5 + = render "new_group_member"
  6 + - else
  7 + .light-well
  8 + %h4.nothing_here_message
  9 + Only group owners can manage group members
  10 + .span6
7 .ui-box 11 .ui-box
8 %h5.title 12 %h5.title
9 - Team 13 + #{@group.name} Group Members
10 %small 14 %small
11 - (#{@users.size}) 15 + (#{@members.count})
12 %ul.well-list 16 %ul.well-list
13 - - @users.each do |user|  
14 - %li  
15 - = image_tag gravatar_icon(user.email, 16), class: "avatar s16"  
16 - %strong= user.name  
17 - %span.cgray= user.email  
18 - - if @group.owner == user  
19 - %span.btn.btn-small.disabled.pull-right Group Owner  
20 - 17 + - @members.each do |member|
  18 + = render 'users_groups/users_group', member: member, show_controls: can_manage_group
  19 + %p.light
  20 + Group members get access to all projects in this group
app/views/issues/_form.html.haml
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 Assign to 21 Assign to
22 .input 22 .input
23 .pull-left 23 .pull-left
24 - = f.select(:assignee_id, @project.users.alphabetically.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'}) 24 + = f.select(:assignee_id, @project.team.members.sort_by(&:name).map {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'})
25 .pull-right 25 .pull-right
26 &nbsp; 26 &nbsp;
27 = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link' 27 = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link'
app/views/issues/_issues.html.haml
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
7 %span.update_issues_text Update selected issues with &nbsp; 7 %span.update_issues_text Update selected issues with &nbsp;
8 .left 8 .left
9 = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status") 9 = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status")
10 - = select_tag('update[assignee_id]', options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee") 10 + = select_tag('update[assignee_id]', options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id]), prompt: "Assignee")
11 = select_tag('update[milestone_id]', options_from_collection_for_select(issues_active_milestones, "id", "title", params[:milestone_id]), prompt: "Milestone") 11 = select_tag('update[milestone_id]', options_from_collection_for_select(issues_active_milestones, "id", "title", params[:milestone_id]), prompt: "Milestone")
12 = hidden_field_tag 'update[issues_ids]', [] 12 = hidden_field_tag 'update[issues_ids]', []
13 = hidden_field_tag :status, params[:status] 13 = hidden_field_tag :status, params[:status]
@@ -50,7 +50,7 @@ @@ -50,7 +50,7 @@
50 Any 50 Any
51 = link_to project_issues_with_filter_path(@project, assignee_id: 0) do 51 = link_to project_issues_with_filter_path(@project, assignee_id: 0) do
52 Unassigned 52 Unassigned
53 - - @project.users.sort_by(&:name).each do |user| 53 + - @project.team.members.sort_by(&:name).each do |user|
54 %li 54 %li
55 = link_to project_issues_with_filter_path(@project, assignee_id: user.id) do 55 = link_to project_issues_with_filter_path(@project, assignee_id: user.id) do
56 = image_tag gravatar_icon(user.email), class: "avatar s16" 56 = image_tag gravatar_icon(user.email), class: "avatar s16"
app/views/projects/_settings_nav.html.haml
@@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
6 = nav_link(controller: [:team_members, :teams]) do 6 = nav_link(controller: [:team_members, :teams]) do
7 = link_to project_team_index_path(@project), class: "team-tab tab" do 7 = link_to project_team_index_path(@project), class: "team-tab tab" do
8 %i.icon-group 8 %i.icon-group
9 - Project Members 9 + Members
10 = nav_link(controller: :deploy_keys) do 10 = nav_link(controller: :deploy_keys) do
11 = link_to project_deploy_keys_path(@project) do 11 = link_to project_deploy_keys_path(@project) do
12 %span 12 %span
app/views/team_members/_group_members.html.haml 0 → 100644
@@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
  1 +.ui-box
  2 + %h5.title
  3 + %strong #{@group.name} Group
  4 + members (#{@group.users_groups.count})
  5 + .pull-right
  6 + = link_to people_group_path(@group), class: 'btn btn-small' do
  7 + %i.icon-edit
  8 + %ul.well-list
  9 + - @group.users_groups.order('group_access DESC').each do |member|
  10 + = render 'users_groups/users_group', member: member, show_controls: false
app/views/team_members/_team.html.haml
1 -- can_admin_project = (can? current_user, :admin_project, @project)  
2 -- team.each do |access, members|  
3 - - role = Project.access_options.key(access).pluralize  
4 - .ui-box{class: role.downcase} 1 +.team-table
  2 + - can_admin_project = (can? current_user, :admin_project, @project)
  3 + .ui-box
5 %h5.title 4 %h5.title
6 - = role  
7 - %span.light (#{members.size}) 5 + %strong #{@project.name} Project
  6 + members (#{members.count})
8 %ul.well-list 7 %ul.well-list
9 - - members.sort_by(&:user_name).each do |team_member| 8 + - members.each do |team_member|
10 = render 'team_members/team_member', member: team_member, current_user_can_admin_project: can_admin_project 9 = render 'team_members/team_member', member: team_member, current_user_can_admin_project: can_admin_project
app/views/team_members/_team_member.html.haml
1 - user = member.user 1 - user = member.user
2 - allow_admin = current_user_can_admin_project 2 - allow_admin = current_user_can_admin_project
3 %li{id: dom_id(user), class: "team_member_row user_#{user.id}"} 3 %li{id: dom_id(user), class: "team_member_row user_#{user.id}"}
4 - .row  
5 - .span4  
6 - = link_to user, title: user.name, class: "dark" do  
7 - = image_tag gravatar_icon(user.email, 32), class: "avatar s32"  
8 - %strong= truncate(user.name, lenght: 40)  
9 - %br  
10 - %small.cgray= user.username 4 + .pull-right
  5 + - if allow_admin
  6 + .pull-left
  7 + = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|
  8 + = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit"
  9 + &nbsp;
  10 + = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
  11 + %i.icon-minus.icon-white
  12 + = image_tag gravatar_icon(user.email, 32), class: "avatar s32"
  13 + %p
  14 + %strong= user.name
  15 + %span.cgray= user.username
11 16
12 - .span4.pull-right  
13 - - if allow_admin  
14 - .left  
15 - = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f|  
16 - = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit"  
17 - .pull-right  
18 - - if current_user == user  
19 - %span.label.label-success This is you!  
20 - - if @project.namespace_owner == user  
21 - %span.label.label-info Owner  
22 - - elsif user.blocked?  
23 - %span.label.label-error Blocked  
24 - - elsif allow_admin  
25 - = link_to project_team_member_path(@project, user), confirm: remove_from_project_team_message(@project, user), method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do  
26 - %i.icon-minus.icon-white  
27 17
app/views/team_members/index.html.haml
1 = render "projects/settings_nav" 1 = render "projects/settings_nav"
2 %h3.page_title 2 %h3.page_title
3 - Project Members  
4 - (#{@project.users.count})  
5 - %small  
6 - Read more about project permissions  
7 - %strong= link_to "here", help_permissions_path, class: "vlink" 3 + Users with access to this project
8 4
9 - if can? current_user, :admin_team_member, @project 5 - if can? current_user, :admin_team_member, @project
10 %span.pull-right 6 %span.pull-right
@@ -15,42 +11,25 @@ @@ -15,42 +11,25 @@
15 = link_to new_project_team_member_path(@project), class: "btn btn-primary small grouped", title: "New Team Member" do 11 = link_to new_project_team_member_path(@project), class: "btn btn-primary small grouped", title: "New Team Member" do
16 New Team Member 12 New Team Member
17 13
18 -%hr 14 +%p.light
  15 + Read more about project permissions
  16 + %strong= link_to "here", help_permissions_path, class: "vlink"
19 17
20 .clearfix 18 .clearfix
21 -.row  
22 - .span3  
23 - %ul.nav.nav-pills.nav-stacked  
24 - %li{class: ("active" if !params[:type])}  
25 - = link_to project_team_index_path(type: nil) do  
26 - All  
27 - %li{class: ("active" if params[:type] == 'masters')}  
28 - = link_to project_team_index_path(type: 'masters') do  
29 - Masters  
30 - %span.pull-right= @project.users_projects.masters.count  
31 - %li{class: ("active" if params[:type] == 'developers')}  
32 - = link_to project_team_index_path(type: 'developers') do  
33 - Developers  
34 - %span.pull-right= @project.users_projects.developers.count  
35 - %li{class: ("active" if params[:type] == 'reporters')}  
36 - = link_to project_team_index_path(type: 'reporters') do  
37 - Reporters  
38 - %span.pull-right= @project.users_projects.reporters.count  
39 - %li{class: ("active" if params[:type] == 'guests')}  
40 - = link_to project_team_index_path(type: 'guests') do  
41 - Guests  
42 - %span.pull-right= @project.users_projects.guests.count  
43 -  
44 - - if @assigned_teams.present?  
45 - %h5  
46 - Assigned teams  
47 - (#{@project.user_teams.count})  
48 - %div  
49 - = render "team_members/assigned_teams", assigned_teams: @assigned_teams  
50 -  
51 - .span9  
52 - %div.team-table  
53 - = render "team_members/team", team: @team  
54 -  
55 -  
56 19
  20 +- if @group
  21 + .row
  22 + .span6
  23 + = render "team_members/group_members"
  24 + .span6
  25 + = render "team_members/team", members: @users_projects
  26 +
  27 +- else
  28 + = render "team_members/team", members: @users_projects
  29 +
  30 +- if @assigned_teams.present?
  31 + %h5
  32 + Assigned teams
  33 + (#{@project.user_teams.count})
  34 + %div
  35 + = render "team_members/assigned_teams", assigned_teams: @assigned_teams
app/views/users_groups/_users_group.html.haml 0 → 100644
@@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
  1 +- user = member.user
  2 +- return unless user
  3 +%li{class: dom_class(member)}
  4 + = image_tag gravatar_icon(user.email, 16), class: "avatar s16"
  5 + %strong= user.name
  6 + %span.cgray= user.username
  7 +
  8 + %span.pull-right
  9 + - if @group.owners.include?(user)
  10 + %span.label.label-info Group Owner
  11 + - else
  12 + = member.human_access
  13 +
  14 + - if show_controls && user != current_user
  15 + = link_to group_users_group_path(@group, member), confirm: remove_user_from_group_message(@group, user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
  16 + %i.icon-minus.icon-white
config/routes.rb
@@ -149,10 +149,10 @@ Gitlab::Application.routes.draw do @@ -149,10 +149,10 @@ Gitlab::Application.routes.draw do
149 member do 149 member do
150 get :issues 150 get :issues
151 get :merge_requests 151 get :merge_requests
152 - get :search  
153 get :people 152 get :people
154 - post :team_members  
155 end 153 end
  154 +
  155 + resources :users_groups, only: [:create, :update, :destroy]
156 end 156 end
157 157
158 # 158 #
db/migrate/20130617095603_create_users_groups.rb 0 → 100644
@@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
  1 +class CreateUsersGroups < ActiveRecord::Migration
  2 + def change
  3 + create_table :users_groups do |t|
  4 + t.integer :group_access, null: false
  5 + t.integer :group_id, null: false
  6 + t.integer :user_id, null: false
  7 +
  8 + t.timestamps
  9 + end
  10 + end
  11 +end
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 # 11 #
12 # It's strongly recommended to check this file into your version control system. 12 # It's strongly recommended to check this file into your version control system.
13 13
14 -ActiveRecord::Schema.define(:version => 20130614132337) do 14 +ActiveRecord::Schema.define(:version => 20130617095603) do
15 15
16 create_table "deploy_keys_projects", :force => true do |t| 16 create_table "deploy_keys_projects", :force => true do |t|
17 t.integer "deploy_key_id", :null => false 17 t.integer "deploy_key_id", :null => false
@@ -53,8 +53,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do @@ -53,8 +53,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do
53 t.integer "assignee_id" 53 t.integer "assignee_id"
54 t.integer "author_id" 54 t.integer "author_id"
55 t.integer "project_id" 55 t.integer "project_id"
56 - t.datetime "created_at", :null => false  
57 - t.datetime "updated_at", :null => false 56 + t.datetime "created_at"
  57 + t.datetime "updated_at"
58 t.integer "position", :default => 0 58 t.integer "position", :default => 0
59 t.string "branch_name" 59 t.string "branch_name"
60 t.text "description" 60 t.text "description"
@@ -71,8 +71,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do @@ -71,8 +71,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do
71 71
72 create_table "keys", :force => true do |t| 72 create_table "keys", :force => true do |t|
73 t.integer "user_id" 73 t.integer "user_id"
74 - t.datetime "created_at", :null => false  
75 - t.datetime "updated_at", :null => false 74 + t.datetime "created_at"
  75 + t.datetime "updated_at"
76 t.text "key" 76 t.text "key"
77 t.string "title" 77 t.string "title"
78 t.string "identifier" 78 t.string "identifier"
@@ -89,8 +89,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do @@ -89,8 +89,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do
89 t.integer "author_id" 89 t.integer "author_id"
90 t.integer "assignee_id" 90 t.integer "assignee_id"
91 t.string "title" 91 t.string "title"
92 - t.datetime "created_at", :null => false  
93 - t.datetime "updated_at", :null => false 92 + t.datetime "created_at"
  93 + t.datetime "updated_at"
94 t.text "st_commits", :limit => 2147483647 94 t.text "st_commits", :limit => 2147483647
95 t.text "st_diffs", :limit => 2147483647 95 t.text "st_diffs", :limit => 2147483647
96 t.integer "milestone_id" 96 t.integer "milestone_id"
@@ -139,8 +139,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do @@ -139,8 +139,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do
139 t.text "note" 139 t.text "note"
140 t.string "noteable_type" 140 t.string "noteable_type"
141 t.integer "author_id" 141 t.integer "author_id"
142 - t.datetime "created_at", :null => false  
143 - t.datetime "updated_at", :null => false 142 + t.datetime "created_at"
  143 + t.datetime "updated_at"
144 t.integer "project_id" 144 t.integer "project_id"
145 t.string "attachment" 145 t.string "attachment"
146 t.string "line_code" 146 t.string "line_code"
@@ -158,8 +158,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do @@ -158,8 +158,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do
158 t.string "name" 158 t.string "name"
159 t.string "path" 159 t.string "path"
160 t.text "description" 160 t.text "description"
161 - t.datetime "created_at", :null => false  
162 - t.datetime "updated_at", :null => false 161 + t.datetime "created_at"
  162 + t.datetime "updated_at"
163 t.integer "creator_id" 163 t.integer "creator_id"
164 t.string "default_branch" 164 t.string "default_branch"
165 t.boolean "issues_enabled", :default => true, :null => false 165 t.boolean "issues_enabled", :default => true, :null => false
@@ -206,8 +206,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do @@ -206,8 +206,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do
206 t.text "content" 206 t.text "content"
207 t.integer "author_id", :null => false 207 t.integer "author_id", :null => false
208 t.integer "project_id" 208 t.integer "project_id"
209 - t.datetime "created_at", :null => false  
210 - t.datetime "updated_at", :null => false 209 + t.datetime "created_at"
  210 + t.datetime "updated_at"
211 t.string "file_name" 211 t.string "file_name"
212 t.datetime "expires_at" 212 t.datetime "expires_at"
213 t.boolean "private", :default => true, :null => false 213 t.boolean "private", :default => true, :null => false
@@ -228,9 +228,6 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do @@ -228,9 +228,6 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do
228 t.datetime "created_at" 228 t.datetime "created_at"
229 end 229 end
230 230
231 - add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id"  
232 - add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context"  
233 -  
234 create_table "tags", :force => true do |t| 231 create_table "tags", :force => true do |t|
235 t.string "name" 232 t.string "name"
236 end 233 end
@@ -262,53 +259,60 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do @@ -262,53 +259,60 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do
262 end 259 end
263 260
264 create_table "users", :force => true do |t| 261 create_table "users", :force => true do |t|
265 - t.string "email", :default => "", :null => false  
266 - t.string "encrypted_password", :default => "", :null => false 262 + t.string "email", :default => "", :null => false
  263 + t.string "encrypted_password", :limit => 128, :default => "", :null => false
267 t.string "reset_password_token" 264 t.string "reset_password_token"
268 t.datetime "reset_password_sent_at" 265 t.datetime "reset_password_sent_at"
269 t.datetime "remember_created_at" 266 t.datetime "remember_created_at"
270 - t.integer "sign_in_count", :default => 0 267 + t.integer "sign_in_count", :default => 0
271 t.datetime "current_sign_in_at" 268 t.datetime "current_sign_in_at"
272 t.datetime "last_sign_in_at" 269 t.datetime "last_sign_in_at"
273 t.string "current_sign_in_ip" 270 t.string "current_sign_in_ip"
274 t.string "last_sign_in_ip" 271 t.string "last_sign_in_ip"
275 - t.datetime "created_at", :null => false  
276 - t.datetime "updated_at", :null => false 272 + t.datetime "created_at"
  273 + t.datetime "updated_at"
277 t.string "name" 274 t.string "name"
278 - t.boolean "admin", :default => false, :null => false  
279 - t.integer "projects_limit", :default => 10  
280 - t.string "skype", :default => "", :null => false  
281 - t.string "linkedin", :default => "", :null => false  
282 - t.string "twitter", :default => "", :null => false 275 + t.boolean "admin", :default => false, :null => false
  276 + t.integer "projects_limit", :default => 10
  277 + t.string "skype", :default => "", :null => false
  278 + t.string "linkedin", :default => "", :null => false
  279 + t.string "twitter", :default => "", :null => false
283 t.string "authentication_token" 280 t.string "authentication_token"
284 - t.integer "theme_id", :default => 1, :null => false 281 + t.integer "theme_id", :default => 1, :null => false
285 t.string "bio" 282 t.string "bio"
286 - t.integer "failed_attempts", :default => 0 283 + t.integer "failed_attempts", :default => 0
287 t.datetime "locked_at" 284 t.datetime "locked_at"
288 t.string "extern_uid" 285 t.string "extern_uid"
289 t.string "provider" 286 t.string "provider"
290 t.string "username" 287 t.string "username"
291 - t.boolean "can_create_group", :default => true, :null => false  
292 - t.boolean "can_create_team", :default => true, :null => false 288 + t.boolean "can_create_group", :default => true, :null => false
  289 + t.boolean "can_create_team", :default => true, :null => false
293 t.string "state" 290 t.string "state"
294 - t.integer "color_scheme_id", :default => 1, :null => false  
295 - t.integer "notification_level", :default => 1, :null => false 291 + t.integer "color_scheme_id", :default => 1, :null => false
  292 + t.integer "notification_level", :default => 1, :null => false
296 t.datetime "password_expires_at" 293 t.datetime "password_expires_at"
297 t.integer "created_by_id" 294 t.integer "created_by_id"
298 end 295 end
299 296
300 add_index "users", ["admin"], :name => "index_users_on_admin" 297 add_index "users", ["admin"], :name => "index_users_on_admin"
301 add_index "users", ["email"], :name => "index_users_on_email", :unique => true 298 add_index "users", ["email"], :name => "index_users_on_email", :unique => true
302 - add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true  
303 add_index "users", ["name"], :name => "index_users_on_name" 299 add_index "users", ["name"], :name => "index_users_on_name"
304 add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true 300 add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true
305 add_index "users", ["username"], :name => "index_users_on_username" 301 add_index "users", ["username"], :name => "index_users_on_username"
306 302
  303 + create_table "users_groups", :force => true do |t|
  304 + t.integer "group_access", :null => false
  305 + t.integer "group_id", :null => false
  306 + t.integer "user_id", :null => false
  307 + t.datetime "created_at", :null => false
  308 + t.datetime "updated_at", :null => false
  309 + end
  310 +
307 create_table "users_projects", :force => true do |t| 311 create_table "users_projects", :force => true do |t|
308 t.integer "user_id", :null => false 312 t.integer "user_id", :null => false
309 t.integer "project_id", :null => false 313 t.integer "project_id", :null => false
310 - t.datetime "created_at", :null => false  
311 - t.datetime "updated_at", :null => false 314 + t.datetime "created_at"
  315 + t.datetime "updated_at"
312 t.integer "project_access", :default => 0, :null => false 316 t.integer "project_access", :default => 0, :null => false
313 t.integer "notification_level", :default => 3, :null => false 317 t.integer "notification_level", :default => 3, :null => false
314 end 318 end
@@ -320,8 +324,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do @@ -320,8 +324,8 @@ ActiveRecord::Schema.define(:version =&gt; 20130614132337) do
320 create_table "web_hooks", :force => true do |t| 324 create_table "web_hooks", :force => true do |t|
321 t.string "url" 325 t.string "url"
322 t.integer "project_id" 326 t.integer "project_id"
323 - t.datetime "created_at", :null => false  
324 - t.datetime "updated_at", :null => false 327 + t.datetime "created_at"
  328 + t.datetime "updated_at"
325 t.string "type", :default => "ProjectHook" 329 t.string "type", :default => "ProjectHook"
326 t.integer "service_id" 330 t.integer "service_id"
327 end 331 end
features/steps/admin/admin_groups.rb
@@ -45,7 +45,7 @@ class AdminGroups &lt; Spinach::FeatureSteps @@ -45,7 +45,7 @@ class AdminGroups &lt; Spinach::FeatureSteps
45 within "#new_team_member" do 45 within "#new_team_member" do
46 select "Reporter", from: "project_access" 46 select "Reporter", from: "project_access"
47 end 47 end
48 - click_button "Add user to projects in group" 48 + click_button "Add users into group"
49 end 49 end
50 50
51 Then 'I should see "John" in team list in every project as "Reporter"' do 51 Then 'I should see "John" in team list in every project as "Reporter"' do
features/steps/group/group.rb
@@ -39,11 +39,11 @@ class Groups &lt; Spinach::FeatureSteps @@ -39,11 +39,11 @@ class Groups &lt; Spinach::FeatureSteps
39 39
40 And 'I select user "John" from list with role "Reporter"' do 40 And 'I select user "John" from list with role "Reporter"' do
41 user = User.find_by_name("John") 41 user = User.find_by_name("John")
42 - within "#new_team_member" do 42 + within ".new_users_group" do
43 select2(user.id, from: "#user_ids", multiple: true) 43 select2(user.id, from: "#user_ids", multiple: true)
44 - select "Reporter", from: "project_access" 44 + select "Reporter", from: "group_access"
45 end 45 end
46 - click_button "Add" 46 + click_button "Add users into group"
47 end 47 end
48 48
49 Then 'I should see user "John" in team list' do 49 Then 'I should see user "John" in team list' do
features/steps/project/project_active_tab.rb
@@ -45,7 +45,7 @@ class ProjectActiveTab &lt; Spinach::FeatureSteps @@ -45,7 +45,7 @@ class ProjectActiveTab &lt; Spinach::FeatureSteps
45 # Sub Tabs: Home 45 # Sub Tabs: Home
46 46
47 Given 'I click the "Team" tab' do 47 Given 'I click the "Team" tab' do
48 - click_link('Project Members') 48 + click_link('Members')
49 end 49 end
50 50
51 Given 'I click the "Attachments" tab' do 51 Given 'I click the "Attachments" tab' do
@@ -73,7 +73,7 @@ class ProjectActiveTab &lt; Spinach::FeatureSteps @@ -73,7 +73,7 @@ class ProjectActiveTab &lt; Spinach::FeatureSteps
73 end 73 end
74 74
75 Then 'the active sub tab should be Team' do 75 Then 'the active sub tab should be Team' do
76 - ensure_active_sub_tab('Project Members') 76 + ensure_active_sub_tab('Members')
77 end 77 end
78 78
79 Then 'the active sub tab should be Attachments' do 79 Then 'the active sub tab should be Attachments' do
features/steps/project/project_team_management.rb
@@ -30,14 +30,20 @@ class ProjectTeamManagement &lt; Spinach::FeatureSteps @@ -30,14 +30,20 @@ class ProjectTeamManagement &lt; Spinach::FeatureSteps
30 end 30 end
31 31
32 Then 'I should see "Mike" in team list as "Reporter"' do 32 Then 'I should see "Mike" in team list as "Reporter"' do
33 - within '.reporters' do 33 + user = User.find_by_name("Mike")
  34 +
  35 + within "#user_#{user.id}" do
34 page.should have_content('Mike') 36 page.should have_content('Mike')
  37 + page.find('#team_member_project_access').value.should == access_value(:reporter)
35 end 38 end
36 end 39 end
37 40
38 Given 'I should see "Sam" in team list as "Developer"' do 41 Given 'I should see "Sam" in team list as "Developer"' do
39 - within '.developers' do 42 + user = User.find_by_name("Sam")
  43 +
  44 + within "#user_#{user.id}" do
40 page.should have_content('Sam') 45 page.should have_content('Sam')
  46 + page.find('#team_member_project_access').value.should == access_value(:developer)
41 end 47 end
42 end 48 end
43 49
@@ -49,8 +55,10 @@ class ProjectTeamManagement &lt; Spinach::FeatureSteps @@ -49,8 +55,10 @@ class ProjectTeamManagement &lt; Spinach::FeatureSteps
49 end 55 end
50 56
51 And 'I should see "Sam" in team list as "Reporter"' do 57 And 'I should see "Sam" in team list as "Reporter"' do
52 - within '.reporters' do 58 + user = User.find_by_name("Sam")
  59 + within ".user_#{user.id}" do
53 page.should have_content('Sam') 60 page.should have_content('Sam')
  61 + page.find('#team_member_project_access').value.should == access_value(:reporter)
54 end 62 end
55 end 63 end
56 64
@@ -103,4 +111,10 @@ class ProjectTeamManagement &lt; Spinach::FeatureSteps @@ -103,4 +111,10 @@ class ProjectTeamManagement &lt; Spinach::FeatureSteps
103 click_link('Remove user from team') 111 click_link('Remove user from team')
104 end 112 end
105 end 113 end
  114 +
  115 + private
  116 +
  117 + def access_value(key)
  118 + UsersProject.roles_hash[key].to_s
  119 + end
106 end 120 end
spec/factories/users_groups.rb 0 → 100644
@@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
  1 +FactoryGirl.define do
  2 + factory :users_group do
  3 + group_access { UsersGroup::OWNER }
  4 + group
  5 + user
  6 + end
  7 +end
spec/models/users_group_spec.rb 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +require 'spec_helper'
  2 +
  3 +describe UsersGroup do
  4 + describe "Associations" do
  5 + it { should belong_to(:group) }
  6 + it { should belong_to(:user) }
  7 + end
  8 +
  9 + describe "Mass assignment" do
  10 + it { should_not allow_mass_assignment_of(:group_id) }
  11 + end
  12 +
  13 + describe "Validation" do
  14 + let!(:users_group) { create(:users_group) }
  15 +
  16 + it { should validate_presence_of(:user_id) }
  17 + it { should validate_uniqueness_of(:user_id).scoped_to(:group_id).with_message(/already exists/) }
  18 +
  19 + it { should validate_presence_of(:group_id) }
  20 + it { should ensure_inclusion_of(:group_access).in_array(UsersGroup.group_access_roles.values) }
  21 + end
  22 +
  23 + describe "Delegate methods" do
  24 + it { should respond_to(:user_name) }
  25 + it { should respond_to(:user_email) }
  26 + end
  27 +end