diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2013-06-18 19:34:32 +0300 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2013-06-18 19:34:32 +0300 |
commit | 30d2d2381f608b7c926747d61a9231aa417ea5da (patch) | |
tree | 16b5f2d3452210b2be232b0bae0ddfbb968ec460 | |
parent | cc5440e82a396fe4967a0b31322d9bb67ee70057 (diff) | |
parent | 60bb35161741338e585feedf09538f5935bba271 (diff) | |
download | gitlab-ce-30d2d2381f608b7c926747d61a9231aa417ea5da.tar.gz |
Merge branch 'feature/users_groups' into 6-0-dev
41 files changed, 396 insertions, 252 deletions
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index fb149b7f677..ce53d14d671 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -32,6 +32,8 @@ class Dispatcher new Wall(project_id) when 'teams:members:index' new TeamMembers() + when 'groups:people' + new GroupMembers() switch path.first() when 'admin' then new Admin() diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee new file mode 100644 index 00000000000..c0ffccd8f70 --- /dev/null +++ b/app/assets/javascripts/groups.js.coffee @@ -0,0 +1,6 @@ +class GroupMembers + constructor: -> + $('li.users_group').bind 'ajax:success', -> + $(this).fadeOut() + +@GroupMembers = GroupMembers diff --git a/app/assets/stylesheets/gitlab_bootstrap/lists.scss b/app/assets/stylesheets/gitlab_bootstrap/lists.scss index e661e02623e..0fa4da44de5 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/lists.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/lists.scss @@ -16,6 +16,12 @@ color: #888; } + &.unstyled { + &:hover { + background: none; + } + } + &.smoke { background-color: #f5f5f5; } &:hover { diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index c38461c89db..6e6c8d54e29 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -66,14 +66,12 @@ class Admin::GroupsController < Admin::ApplicationController end def project_teams_update - @group.add_users_to_project_teams(params[:user_ids].split(','), params[:project_access]) + @group.add_users(params[:user_ids].split(','), params[:group_access]) redirect_to [:admin, @group], notice: 'Users were successfully added.' end def destroy - @group.truncate_teams - @group.destroy redirect_to admin_groups_path, notice: 'Group was successfully deleted.' diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index b74c22b1547..9b3f59600c6 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -34,11 +34,12 @@ class DashboardController < ApplicationController @projects end - @projects = @projects.tagged_with(params[:label]) if params[:label].present? @projects = @projects.search(params[:search]) if params[:search].present? - @projects = @projects.page(params[:page]).per(30) @labels = Project.where(id: @projects.map(&:id)).tags_on(:labels) + + @projects = @projects.tagged_with(params[:label]) if params[:label].present? + @projects = @projects.page(params[:page]).per(30) end # Get authored or assigned open merge requests diff --git a/app/controllers/graphs_controller.rb b/app/controllers/graphs_controller.rb index 5ae9c15c0f7..6c2ac5fcbf4 100644 --- a/app/controllers/graphs_controller.rb +++ b/app/controllers/graphs_controller.rb @@ -10,7 +10,7 @@ class GraphsController < ProjectResourceController format.js do @repo = @project.repository @stats = Gitlab::Git::GitStats.new(@repo.raw, @repo.root_ref) - @log = @stats.parsed_log.to_json + @log = @stats.parsed_log.to_json rescue [] end end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index e6559b8d8fe..f44bc10cf39 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,6 +1,6 @@ class GroupsController < ApplicationController respond_to :html - before_filter :group, except: [:new, :create] + before_filter :group, except: [:new, :create, :people] # Authorize before_filter :authorize_read_group!, except: [:new, :create] @@ -63,19 +63,8 @@ class GroupsController < ApplicationController def people @project = group.projects.find(params[:project_id]) if params[:project_id] - @users = @project ? @project.users : group.users - @users.sort_by!(&:name) - - if @project - @team_member = @project.users_projects.new - else - @team_member = UsersProject.new - end - end - - def team_members - @group.add_users_to_project_teams(params[:user_ids].split(','), params[:project_access]) - redirect_to people_group_path(@group), notice: 'Users were successfully added.' + @members = group.users_groups.order('group_access DESC') + @users_group = UsersGroup.new end def edit @@ -83,7 +72,7 @@ class GroupsController < ApplicationController def update group_params = params[:group].dup - owner_id =group_params.delete(:owner_id) + owner_id = group_params.delete(:owner_id) if owner_id @group.owner = User.find(owner_id) diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index ba92ba2bdae..65e72792924 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -23,7 +23,7 @@ class IssuesController < ProjectResourceController assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] - @assignee = @project.users.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero? + @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero? @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? respond_to do |format| diff --git a/app/controllers/team_members_controller.rb b/app/controllers/team_members_controller.rb index 35aa315dac1..095d7a3af55 100644 --- a/app/controllers/team_members_controller.rb +++ b/app/controllers/team_members_controller.rb @@ -4,10 +4,8 @@ class TeamMembersController < ProjectResourceController before_filter :authorize_admin_project!, except: [:index, :show] def index - @team = @project.users_projects.scoped - @team = @team.send(params[:type]) if %w(masters developers reporters guests).include?(params[:type]) - @team = @team.sort_by(&:project_access).reverse.group_by(&:project_access) - + @group = @project.group + @users_projects = @project.users_projects.order('project_access DESC') @assigned_teams = @project.user_team_project_relationships end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 4947c33f959..028af8ff59f 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,7 +3,7 @@ class UsersController < ApplicationController def show @user = User.find_by_username!(params[:username]) - @projects = @user.authorized_projects.where('projects.id in (?)', current_user.authorized_projects.map(&:id)) + @projects = @user.authorized_projects.where('projects.id in (?)', current_user.authorized_projects.map(&:id)).order('namespace_id DESC') @events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20) @title = @user.name diff --git a/app/controllers/users_groups_controller.rb b/app/controllers/users_groups_controller.rb new file mode 100644 index 00000000000..ebc79d621a4 --- /dev/null +++ b/app/controllers/users_groups_controller.rb @@ -0,0 +1,40 @@ +class UsersGroupsController < ApplicationController + before_filter :group + + # Authorize + before_filter :authorize_admin_group! + + layout 'group' + + def create + @group.add_users(params[:user_ids].split(','), params[:group_access]) + + redirect_to people_group_path(@group), notice: 'Users were successfully added.' + end + + def update + # TODO: implement + end + + def destroy + @users_group = @group.users_groups.find(params[:id]) + @users_group.destroy + + respond_to do |format| + format.html { redirect_to people_group_path(@group), notice: 'User was successfully removed from group.' } + format.js { render nothing: true } + end + end + + protected + + def group + @group ||= Group.find_by_path(params[:group_id]) + end + + def authorize_admin_group! + unless can?(current_user, :manage_group, group) + return render_404 + end + end +end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 283119bc24c..2ffbff7af8d 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -14,4 +14,8 @@ module GroupsHelper merge_requests_group_path(@group, options) end end + + def remove_user_from_group_message(group, user) + "You are going to remove #{user.name} from #{group.name} Group. Are you sure?" + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index 0b6314cd555..70a4435e699 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -50,6 +50,10 @@ class Ability rules << project_admin_rules end + if project.group && project.group.owners.include?(user) + rules << project_admin_rules + end + rules.flatten end @@ -132,7 +136,7 @@ class Ability rules = [] # Only group owner and administrators can manage group - if group.owner == user || user.admin? + if group.owners.include?(user) || user.admin? rules << [ :manage_group, :manage_namespace diff --git a/app/models/group.rb b/app/models/group.rb index 17671c3defe..0593d9cd4bb 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -13,26 +13,28 @@ # class Group < Namespace + has_many :users_groups, dependent: :destroy + has_many :users, through: :users_groups - def add_users_to_project_teams(user_ids, project_access) - UsersProject.add_users_into_projects( - projects.map(&:id), - user_ids, - project_access - ) + after_create :add_owner + + def human_name + name end - def users - users = User.joins(:users_projects).where(users_projects: {project_id: project_ids}) - users = users << owner - users.uniq + def owners + @owners ||= (users_groups.owners.map(&:user) << owner) end - def human_name - name + def add_users(user_ids, group_access) + user_ids.compact.each do |user_id| + self.users_groups.create(user_id: user_id, group_access: group_access) + end end - def truncate_teams - UsersProject.truncate_teams(project_ids) + private + + def add_owner + self.add_users([owner.id], UsersGroup::OWNER) end end diff --git a/app/models/project_team.rb b/app/models/project_team.rb index f3e5c0e5354..20c6690e80b 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -21,6 +21,16 @@ class ProjectTeam end end + def find user_id + user = project.users.find_by_id(user_id) + + if group + user ||= group.users.find_by_id(user_id) + end + + user + end + def get_tm user_id project.users_projects.find_by_user_id(user_id) end @@ -47,23 +57,23 @@ class ProjectTeam end def members - project.users_projects + fetch_members end def guests - members.guests.map(&:user) + @guests ||= fetch_members(:guests) end def reporters - members.reporters.map(&:user) + @reporters ||= fetch_members(:reporters) end def developers - members.developers.map(&:user) + @developers ||= fetch_members(:developers) end def masters - members.masters.map(&:user) + @masters ||= fetch_members(:masters) end def import(source_project) @@ -96,4 +106,22 @@ class ProjectTeam rescue false end + + private + + def fetch_members(level = nil) + project_members = project.users_projects + group_members = group ? group.users_groups : [] + + if level + project_members = project_members.send(level) + group_members = group_members.send(level) if group + end + + (project_members + group_members).map(&:user).uniq + end + + def group + project.group + end end diff --git a/app/models/user.rb b/app/models/user.rb index 6de8d2d4c39..904d2919429 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -71,7 +71,9 @@ class User < ActiveRecord::Base has_many :keys, dependent: :destroy # Groups - has_many :groups, class_name: "Group", foreign_key: :owner_id + has_many :own_groups, class_name: "Group", foreign_key: :owner_id + has_many :users_groups, dependent: :destroy + has_many :groups, through: :users_groups # Teams has_many :own_teams, dependent: :destroy, class_name: "UserTeam", foreign_key: :owner_id @@ -230,7 +232,7 @@ class User < ActiveRecord::Base # Groups where user is an owner def owned_groups - groups + own_groups end def owned_teams @@ -239,14 +241,14 @@ class User < ActiveRecord::Base # Groups user has access to def authorized_groups - @group_ids ||= (groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) + @group_ids ||= (groups.pluck(:id) + own_groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) Group.where(id: @group_ids) end # Projects user has access to def authorized_projects - @project_ids ||= (owned_projects.pluck(:id) + projects.pluck(:id)).uniq + @project_ids ||= (owned_projects.pluck(:id) + groups.map(&:projects).flatten.map(&:id) + projects.pluck(:id)).uniq Project.where(id: @project_ids) end diff --git a/app/models/users_group.rb b/app/models/users_group.rb new file mode 100644 index 00000000000..0cb26854f9c --- /dev/null +++ b/app/models/users_group.rb @@ -0,0 +1,42 @@ +class UsersGroup < ActiveRecord::Base + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MASTER = 40 + OWNER = 50 + + def self.group_access_roles + { + "Guest" => GUEST, + "Reporter" => REPORTER, + "Developer" => DEVELOPER, + "Master" => MASTER, + "Owner" => OWNER + } + end + + attr_accessible :group_access, :user_id + + belongs_to :user + belongs_to :group + + scope :guests, -> { where(group_access: GUEST) } + scope :reporters, -> { where(group_access: REPORTER) } + scope :developers, -> { where(group_access: DEVELOPER) } + scope :masters, -> { where(group_access: MASTER) } + scope :owners, -> { where(group_access: OWNER) } + + scope :with_group, ->(group) { where(group_id: group.id) } + scope :with_user, ->(user) { where(user_id: user.id) } + + validates :group_access, inclusion: { in: UsersGroup.group_access_roles.values }, presence: true + validates :user_id, presence: true + validates :group_id, presence: true + validates :user_id, uniqueness: { scope: [:group_id], message: "already exists in group" } + + delegate :name, :username, :email, to: :user, prefix: true + + def human_access + UsersGroup.group_access_roles.key(self.group_access) + end +end diff --git a/app/models/users_project.rb b/app/models/users_project.rb index 935ecede42c..4c58b009380 100644 --- a/app/models/users_project.rb +++ b/app/models/users_project.rb @@ -129,9 +129,7 @@ class UsersProject < ActiveRecord::Base Project.access_options.key(self.project_access) end - def repo_access_human - self.class.access_roles.invert[self.project_access] - end + alias_method :human_access, :project_access_human def skip_git? !!@skip_git diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index a52d13d5237..01ddf99e97b 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -43,7 +43,7 @@ class SystemHooksService project_id: model.project_id, user_name: model.user.name, user_email: model.user.email, - project_access: model.repo_access_human + project_access: model.project_access_human }) end end diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 9c4b91b1bfa..3de0091801f 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -49,10 +49,23 @@ %strong = @group.created_at.stamp("March 1, 1999") + .ui-box + %h5.title + Projects + %small + (#{@group.projects.count}) + %ul.well-list + - @group.projects.sort_by(&:name).each do |project| + %li + %strong + = link_to project.name_with_namespace, [:admin, project] + %span.pull-right.light + %span.monospace= project.path_with_namespace + ".git" + .span6 .ui-box %h5.title - Add user to Group projects: + Add user(s): .ui-box-body.form-holder %p.light Read more about project permissions @@ -64,30 +77,18 @@ %div.prepend-top-10 = select_tag :project_access, options_for_select(Project.access_options), {class: "project-access-select chosen span2"} %hr - = submit_tag 'Add user to projects in group', class: "btn btn-create" + = submit_tag 'Add users into group', class: "btn btn-create" .ui-box %h5.title - Users from Group projects + Users from #{@group.name} Group %small - (#{@group.users.count}) + (#{@group.users_groups.count}) %ul.well-list - - @group.users.sort_by(&:name).each do |user| + - @group.users_groups.order('group_access DESC').each do |member| + - user = member.user %li{class: dom_class(user)} %strong = link_to user.name, admin_user_path(user) %span.pull-right.light - = pluralize user.authorized_projects.in_namespace(@group).count, 'project' + = member.human_access - .span6 - .ui-box - %h5.title - Projects - %small - (#{@group.projects.count}) - %ul.well-list - - @group.projects.sort_by(&:name).each do |project| - %li - %strong - = link_to project.name_with_namespace, [:admin, project] - %span.pull-right.light - %span.monospace= project.path_with_namespace + ".git" diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml index 78db1c23320..786a52a6011 100644 --- a/app/views/groups/_new_group_member.html.haml +++ b/app/views/groups/_new_group_member.html.haml @@ -1,18 +1,18 @@ -= form_for @team_member, as: :team_member, url: team_members_group_path(@group) do |f| += form_for @users_group, url: group_users_groups_path(@group) do |f| %fieldset - %legend= "New Team member(s) for projects in #{@group.name}" + %legend= "New Group member(s) for #{@group.name}" - %h6 1. Choose people you want in the team + %h6 1. Choose people you want in the group .clearfix = f.label :user_ids, "People" - .input= users_select_tag(:user_ids, multiple: true) + .input= users_select_tag(:user_ids, multiple: true, class: 'input-large') %h6 2. Set access level for them .clearfix - = f.label :project_access, "Project Access" - .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen" + = f.label :group_access, "Group Access" + .input= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select chosen" .form-actions = hidden_field_tag :redirect_to, people_group_path(@group) - = f.submit 'Add', class: "btn btn-save" + = f.submit 'Add users into group', class: "btn btn-create" diff --git a/app/views/groups/_new_member.html.haml b/app/views/groups/_new_member.html.haml deleted file mode 100644 index 4a762ed39ed..00000000000 --- a/app/views/groups/_new_member.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -= form_for @team_member, as: :team_member, url: project_team_members_path(@project, @team_member) do |f| - %fieldset - %legend= "New Team member(s) for #{@project.name}" - - %h6 1. Choose people you want in the team - .clearfix - = f.label :user_ids, "People" - .input= users_select_tag(:user_ids, multiple: true) - - %h6 2. Set access level for them - .clearfix - = f.label :project_access, "Project Access" - .input= select_tag :project_access, options_for_select(Project.access_options, @team_member.project_access), class: "project-access-select chosen" - - .form-actions - = hidden_field_tag :redirect_to, people_group_path(@group, project_id: @project.id) - = f.submit 'Add', class: "btn btn-save" - diff --git a/app/views/groups/_people_filter.html.haml b/app/views/groups/_people_filter.html.haml deleted file mode 100644 index ee63743eb4f..00000000000 --- a/app/views/groups/_people_filter.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -= form_tag people_group_path(@group), method: 'get' do - %fieldset - %legend Projects: - %ul.nav.nav-pills.nav-stacked - - @projects.each do |project| - %li{class: ("active" if params[:project_id] == project.id.to_s)} - = link_to people_group_path(@group, project_id: project.id) do - = project.name_with_namespace - %small.pull-right= project.users.count - - if @projects.blank? - %p.nothing_here_message This group has no projects yet - - %fieldset - %hr - = link_to "Reset", people_group_path(@group), class: 'btn pull-right' - diff --git a/app/views/groups/people.html.haml b/app/views/groups/people.html.haml index 3e4eb082f56..5b595ad3584 100644 --- a/app/views/groups/people.html.haml +++ b/app/views/groups/people.html.haml @@ -1,20 +1,20 @@ +- can_manage_group = current_user.can? :manage_group, @group .row - .span3 - = render 'people_filter' - .span9 - - if can?(current_user, :manage_group, @group) - = render (@project ? "new_member" : "new_group_member") + .span6 + - if can_manage_group + = render "new_group_member" + - else + .light-well + %h4.nothing_here_message + Only group owners can manage group members + .span6 .ui-box %h5.title - Team + #{@group.name} Group Members %small - (#{@users.size}) + (#{@members.count}) %ul.well-list - - @users.each do |user| - %li - = image_tag gravatar_icon(user.email, 16), class: "avatar s16" - %strong= user.name - %span.cgray= user.email - - if @group.owner == user - %span.btn.btn-small.disabled.pull-right Group Owner - + - @members.each do |member| + = render 'users_groups/users_group', member: member, show_controls: can_manage_group + %p.light + Group members get access to all projects in this group diff --git a/app/views/issues/_form.html.haml b/app/views/issues/_form.html.haml index 57460e623ab..38aea6f06dc 100644 --- a/app/views/issues/_form.html.haml +++ b/app/views/issues/_form.html.haml @@ -21,7 +21,7 @@ Assign to .input .pull-left - = f.select(:assignee_id, @project.users.alphabetically.collect {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'}) + = f.select(:assignee_id, @project.team.members.sort_by(&:name).map {|p| [ p.name, p.id ] }, { include_blank: "Select a user" }, {class: 'chosen'}) .pull-right = link_to 'Assign to me', '#', class: 'btn btn-small assign-to-me-link' diff --git a/app/views/issues/_issues.html.haml b/app/views/issues/_issues.html.haml index cc8d8d9cae2..68598684f90 100644 --- a/app/views/issues/_issues.html.haml +++ b/app/views/issues/_issues.html.haml @@ -7,7 +7,7 @@ %span.update_issues_text Update selected issues with .left = select_tag('update[status]', options_for_select(['open', 'closed']), prompt: "Status") - = select_tag('update[assignee_id]', options_from_collection_for_select(@project.users.all, "id", "name", params[:assignee_id]), prompt: "Assignee") + = select_tag('update[assignee_id]', options_from_collection_for_select(@project.team.members, "id", "name", params[:assignee_id]), prompt: "Assignee") = select_tag('update[milestone_id]', options_from_collection_for_select(issues_active_milestones, "id", "title", params[:milestone_id]), prompt: "Milestone") = hidden_field_tag 'update[issues_ids]', [] = hidden_field_tag :status, params[:status] @@ -50,7 +50,7 @@ Any = link_to project_issues_with_filter_path(@project, assignee_id: 0) do Unassigned - - @project.users.sort_by(&:name).each do |user| + - @project.team.members.sort_by(&:name).each do |user| %li = link_to project_issues_with_filter_path(@project, assignee_id: user.id) do = image_tag gravatar_icon(user.email), class: "avatar s16" diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml index 346bbd2daf3..272c5e4402f 100644 --- a/app/views/projects/_settings_nav.html.haml +++ b/app/views/projects/_settings_nav.html.haml @@ -6,7 +6,7 @@ = nav_link(controller: [:team_members, :teams]) do = link_to project_team_index_path(@project), class: "team-tab tab" do %i.icon-group - Project Members + Members = nav_link(controller: :deploy_keys) do = link_to project_deploy_keys_path(@project) do %span diff --git a/app/views/team_members/_group_members.html.haml b/app/views/team_members/_group_members.html.haml new file mode 100644 index 00000000000..7d9333d38e6 --- /dev/null +++ b/app/views/team_members/_group_members.html.haml @@ -0,0 +1,10 @@ +.ui-box + %h5.title + %strong #{@group.name} Group + members (#{@group.users_groups.count}) + .pull-right + = link_to people_group_path(@group), class: 'btn btn-small' do + %i.icon-edit + %ul.well-list + - @group.users_groups.order('group_access DESC').each do |member| + = render 'users_groups/users_group', member: member, show_controls: false diff --git a/app/views/team_members/_team.html.haml b/app/views/team_members/_team.html.haml index 4ff170ac86e..2a663bd9941 100644 --- a/app/views/team_members/_team.html.haml +++ b/app/views/team_members/_team.html.haml @@ -1,10 +1,9 @@ -- can_admin_project = (can? current_user, :admin_project, @project) -- team.each do |access, members| - - role = Project.access_options.key(access).pluralize - .ui-box{class: role.downcase} +.team-table + - can_admin_project = (can? current_user, :admin_project, @project) + .ui-box %h5.title - = role - %span.light (#{members.size}) + %strong #{@project.name} Project + members (#{members.count}) %ul.well-list - - members.sort_by(&:user_name).each do |team_member| + - members.each do |team_member| = render 'team_members/team_member', member: team_member, current_user_can_admin_project: can_admin_project diff --git a/app/views/team_members/_team_member.html.haml b/app/views/team_members/_team_member.html.haml index d829a79213c..4bd4fca6718 100644 --- a/app/views/team_members/_team_member.html.haml +++ b/app/views/team_members/_team_member.html.haml @@ -1,27 +1,17 @@ - user = member.user - allow_admin = current_user_can_admin_project %li{id: dom_id(user), class: "team_member_row user_#{user.id}"} - .row - .span4 - = link_to user, title: user.name, class: "dark" do - = image_tag gravatar_icon(user.email, 32), class: "avatar s32" - %strong= truncate(user.name, lenght: 40) - %br - %small.cgray= user.username + .pull-right + - if allow_admin + .pull-left + = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f| + = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit" + + = 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 + %i.icon-minus.icon-white + = image_tag gravatar_icon(user.email, 32), class: "avatar s32" + %p + %strong= user.name + %span.cgray= user.username - .span4.pull-right - - if allow_admin - .left - = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f| - = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit" - .pull-right - - if current_user == user - %span.label.label-success This is you! - - if @project.namespace_owner == user - %span.label.label-info Owner - - elsif user.blocked? - %span.label.label-error Blocked - - elsif allow_admin - = 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 - %i.icon-minus.icon-white diff --git a/app/views/team_members/index.html.haml b/app/views/team_members/index.html.haml index c0f7ee4330d..bae7215323c 100644 --- a/app/views/team_members/index.html.haml +++ b/app/views/team_members/index.html.haml @@ -1,10 +1,6 @@ = render "projects/settings_nav" %h3.page_title - Project Members - (#{@project.users.count}) - %small - Read more about project permissions - %strong= link_to "here", help_permissions_path, class: "vlink" + Users with access to this project - if can? current_user, :admin_team_member, @project %span.pull-right @@ -15,42 +11,25 @@ = link_to new_project_team_member_path(@project), class: "btn btn-primary small grouped", title: "New Team Member" do New Team Member -%hr +%p.light + Read more about project permissions + %strong= link_to "here", help_permissions_path, class: "vlink" .clearfix -.row - .span3 - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if !params[:type])} - = link_to project_team_index_path(type: nil) do - All - %li{class: ("active" if params[:type] == 'masters')} - = link_to project_team_index_path(type: 'masters') do - Masters - %span.pull-right= @project.users_projects.masters.count - %li{class: ("active" if params[:type] == 'developers')} - = link_to project_team_index_path(type: 'developers') do - Developers - %span.pull-right= @project.users_projects.developers.count - %li{class: ("active" if params[:type] == 'reporters')} - = link_to project_team_index_path(type: 'reporters') do - Reporters - %span.pull-right= @project.users_projects.reporters.count - %li{class: ("active" if params[:type] == 'guests')} - = link_to project_team_index_path(type: 'guests') do - Guests - %span.pull-right= @project.users_projects.guests.count - - - if @assigned_teams.present? - %h5 - Assigned teams - (#{@project.user_teams.count}) - %div - = render "team_members/assigned_teams", assigned_teams: @assigned_teams - - .span9 - %div.team-table - = render "team_members/team", team: @team - - +- if @group + .row + .span6 + = render "team_members/group_members" + .span6 + = render "team_members/team", members: @users_projects + +- else + = render "team_members/team", members: @users_projects + +- if @assigned_teams.present? + %h5 + Assigned teams + (#{@project.user_teams.count}) + %div + = render "team_members/assigned_teams", assigned_teams: @assigned_teams diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml new file mode 100644 index 00000000000..31a829952ac --- /dev/null +++ b/app/views/users_groups/_users_group.html.haml @@ -0,0 +1,16 @@ +- user = member.user +- return unless user +%li{class: dom_class(member)} + = image_tag gravatar_icon(user.email, 16), class: "avatar s16" + %strong= user.name + %span.cgray= user.username + + %span.pull-right + - if @group.owners.include?(user) + %span.label.label-info Group Owner + - else + = member.human_access + + - if show_controls && user != current_user + = 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 + %i.icon-minus.icon-white diff --git a/config/routes.rb b/config/routes.rb index 39c79635c40..b431cb23c83 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -149,10 +149,10 @@ Gitlab::Application.routes.draw do member do get :issues get :merge_requests - get :search get :people - post :team_members end + + resources :users_groups, only: [:create, :update, :destroy] end # diff --git a/db/migrate/20130617095603_create_users_groups.rb b/db/migrate/20130617095603_create_users_groups.rb new file mode 100644 index 00000000000..2efc04f1151 --- /dev/null +++ b/db/migrate/20130617095603_create_users_groups.rb @@ -0,0 +1,11 @@ +class CreateUsersGroups < ActiveRecord::Migration + def change + create_table :users_groups do |t| + t.integer :group_access, null: false + t.integer :group_id, null: false + t.integer :user_id, null: false + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index c3c751e3a19..415e9723456 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130614132337) do +ActiveRecord::Schema.define(:version => 20130617095603) do create_table "deploy_keys_projects", :force => true do |t| t.integer "deploy_key_id", :null => false @@ -53,8 +53,8 @@ ActiveRecord::Schema.define(:version => 20130614132337) do t.integer "assignee_id" t.integer "author_id" t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.integer "position", :default => 0 t.string "branch_name" t.text "description" @@ -71,8 +71,8 @@ ActiveRecord::Schema.define(:version => 20130614132337) do create_table "keys", :force => true do |t| t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.text "key" t.string "title" t.string "identifier" @@ -89,8 +89,8 @@ ActiveRecord::Schema.define(:version => 20130614132337) do t.integer "author_id" t.integer "assignee_id" t.string "title" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.text "st_commits", :limit => 2147483647 t.text "st_diffs", :limit => 2147483647 t.integer "milestone_id" @@ -139,8 +139,8 @@ ActiveRecord::Schema.define(:version => 20130614132337) do t.text "note" t.string "noteable_type" t.integer "author_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.integer "project_id" t.string "attachment" t.string "line_code" @@ -158,8 +158,8 @@ ActiveRecord::Schema.define(:version => 20130614132337) do t.string "name" t.string "path" t.text "description" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.integer "creator_id" t.string "default_branch" t.boolean "issues_enabled", :default => true, :null => false @@ -206,8 +206,8 @@ ActiveRecord::Schema.define(:version => 20130614132337) do t.text "content" t.integer "author_id", :null => false t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.string "file_name" t.datetime "expires_at" t.boolean "private", :default => true, :null => false @@ -228,9 +228,6 @@ ActiveRecord::Schema.define(:version => 20130614132337) do t.datetime "created_at" end - add_index "taggings", ["tag_id"], :name => "index_taggings_on_tag_id" - add_index "taggings", ["taggable_id", "taggable_type", "context"], :name => "index_taggings_on_taggable_id_and_taggable_type_and_context" - create_table "tags", :force => true do |t| t.string "name" end @@ -262,53 +259,60 @@ ActiveRecord::Schema.define(:version => 20130614132337) do end create_table "users", :force => true do |t| - t.string "email", :default => "", :null => false - t.string "encrypted_password", :default => "", :null => false + t.string "email", :default => "", :null => false + t.string "encrypted_password", :limit => 128, :default => "", :null => false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", :default => 0 + t.integer "sign_in_count", :default => 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.string "name" - t.boolean "admin", :default => false, :null => false - t.integer "projects_limit", :default => 10 - t.string "skype", :default => "", :null => false - t.string "linkedin", :default => "", :null => false - t.string "twitter", :default => "", :null => false + t.boolean "admin", :default => false, :null => false + t.integer "projects_limit", :default => 10 + t.string "skype", :default => "", :null => false + t.string "linkedin", :default => "", :null => false + t.string "twitter", :default => "", :null => false t.string "authentication_token" - t.integer "theme_id", :default => 1, :null => false + t.integer "theme_id", :default => 1, :null => false t.string "bio" - t.integer "failed_attempts", :default => 0 + t.integer "failed_attempts", :default => 0 t.datetime "locked_at" t.string "extern_uid" t.string "provider" t.string "username" - t.boolean "can_create_group", :default => true, :null => false - t.boolean "can_create_team", :default => true, :null => false + t.boolean "can_create_group", :default => true, :null => false + t.boolean "can_create_team", :default => true, :null => false t.string "state" - t.integer "color_scheme_id", :default => 1, :null => false - t.integer "notification_level", :default => 1, :null => false + t.integer "color_scheme_id", :default => 1, :null => false + t.integer "notification_level", :default => 1, :null => false t.datetime "password_expires_at" t.integer "created_by_id" end add_index "users", ["admin"], :name => "index_users_on_admin" add_index "users", ["email"], :name => "index_users_on_email", :unique => true - add_index "users", ["extern_uid", "provider"], :name => "index_users_on_extern_uid_and_provider", :unique => true add_index "users", ["name"], :name => "index_users_on_name" add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true add_index "users", ["username"], :name => "index_users_on_username" + create_table "users_groups", :force => true do |t| + t.integer "group_access", :null => false + t.integer "group_id", :null => false + t.integer "user_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + create_table "users_projects", :force => true do |t| t.integer "user_id", :null => false t.integer "project_id", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.integer "project_access", :default => 0, :null => false t.integer "notification_level", :default => 3, :null => false end @@ -320,8 +324,8 @@ ActiveRecord::Schema.define(:version => 20130614132337) do create_table "web_hooks", :force => true do |t| t.string "url" t.integer "project_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at" + t.datetime "updated_at" t.string "type", :default => "ProjectHook" t.integer "service_id" end diff --git a/features/steps/admin/admin_groups.rb b/features/steps/admin/admin_groups.rb index d780d9c96d9..2f98b8100dc 100644 --- a/features/steps/admin/admin_groups.rb +++ b/features/steps/admin/admin_groups.rb @@ -45,7 +45,7 @@ class AdminGroups < Spinach::FeatureSteps within "#new_team_member" do select "Reporter", from: "project_access" end - click_button "Add user to projects in group" + click_button "Add users into group" end Then 'I should see "John" in team list in every project as "Reporter"' do diff --git a/features/steps/group/group.rb b/features/steps/group/group.rb index 102bc440d82..a6b9aa606c6 100644 --- a/features/steps/group/group.rb +++ b/features/steps/group/group.rb @@ -39,11 +39,11 @@ class Groups < Spinach::FeatureSteps And 'I select user "John" from list with role "Reporter"' do user = User.find_by_name("John") - within "#new_team_member" do + within ".new_users_group" do select2(user.id, from: "#user_ids", multiple: true) - select "Reporter", from: "project_access" + select "Reporter", from: "group_access" end - click_button "Add" + click_button "Add users into group" end Then 'I should see user "John" in team list' do diff --git a/features/steps/project/project_active_tab.rb b/features/steps/project/project_active_tab.rb index 5170ab82e8a..a0ee32b9ec0 100644 --- a/features/steps/project/project_active_tab.rb +++ b/features/steps/project/project_active_tab.rb @@ -45,7 +45,7 @@ class ProjectActiveTab < Spinach::FeatureSteps # Sub Tabs: Home Given 'I click the "Team" tab' do - click_link('Project Members') + click_link('Members') end Given 'I click the "Attachments" tab' do @@ -73,7 +73,7 @@ class ProjectActiveTab < Spinach::FeatureSteps end Then 'the active sub tab should be Team' do - ensure_active_sub_tab('Project Members') + ensure_active_sub_tab('Members') end Then 'the active sub tab should be Attachments' do diff --git a/features/steps/project/project_team_management.rb b/features/steps/project/project_team_management.rb index ffd2aa24676..7e9533de8f6 100644 --- a/features/steps/project/project_team_management.rb +++ b/features/steps/project/project_team_management.rb @@ -30,14 +30,20 @@ class ProjectTeamManagement < Spinach::FeatureSteps end Then 'I should see "Mike" in team list as "Reporter"' do - within '.reporters' do + user = User.find_by_name("Mike") + + within "#user_#{user.id}" do page.should have_content('Mike') + page.find('#team_member_project_access').value.should == access_value(:reporter) end end Given 'I should see "Sam" in team list as "Developer"' do - within '.developers' do + user = User.find_by_name("Sam") + + within "#user_#{user.id}" do page.should have_content('Sam') + page.find('#team_member_project_access').value.should == access_value(:developer) end end @@ -49,8 +55,10 @@ class ProjectTeamManagement < Spinach::FeatureSteps end And 'I should see "Sam" in team list as "Reporter"' do - within '.reporters' do + user = User.find_by_name("Sam") + within ".user_#{user.id}" do page.should have_content('Sam') + page.find('#team_member_project_access').value.should == access_value(:reporter) end end @@ -103,4 +111,10 @@ class ProjectTeamManagement < Spinach::FeatureSteps click_link('Remove user from team') end end + + private + + def access_value(key) + UsersProject.roles_hash[key].to_s + end end diff --git a/spec/factories/users_groups.rb b/spec/factories/users_groups.rb new file mode 100644 index 00000000000..cb272e35c92 --- /dev/null +++ b/spec/factories/users_groups.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :users_group do + group_access { UsersGroup::OWNER } + group + user + end +end diff --git a/spec/models/users_group_spec.rb b/spec/models/users_group_spec.rb new file mode 100644 index 00000000000..c802b8213d5 --- /dev/null +++ b/spec/models/users_group_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe UsersGroup do + describe "Associations" do + it { should belong_to(:group) } + it { should belong_to(:user) } + end + + describe "Mass assignment" do + it { should_not allow_mass_assignment_of(:group_id) } + end + + describe "Validation" do + let!(:users_group) { create(:users_group) } + + it { should validate_presence_of(:user_id) } + it { should validate_uniqueness_of(:user_id).scoped_to(:group_id).with_message(/already exists/) } + + it { should validate_presence_of(:group_id) } + it { should ensure_inclusion_of(:group_access).in_array(UsersGroup.group_access_roles.values) } + end + + describe "Delegate methods" do + it { should respond_to(:user_name) } + it { should respond_to(:user_email) } + end +end |