Commit 6b16f8a7a84a91bafac97e1e7e2390e32877eabb

Authored by Dmitriy Zaporozhets
2 parents 2358489f eb4272e2

Merge branch 'improve/user_removal' of /home/git/repositories/gitlab/gitlabhq

app/assets/javascripts/admin.js.coffee
@@ -23,10 +23,16 @@ class Admin @@ -23,10 +23,16 @@ class Admin
23 e.preventDefault() 23 e.preventDefault()
24 $(this).hide() 24 $(this).hide()
25 modal.show() 25 modal.show()
26 - 26 +
27 $('.change-owner-cancel-link').bind "click", (e) -> 27 $('.change-owner-cancel-link').bind "click", (e) ->
28 e.preventDefault() 28 e.preventDefault()
29 modal.hide() 29 modal.hide()
30 $('.change-owner-link').show() 30 $('.change-owner-link').show()
31 31
  32 + $('li.users_project').bind 'ajax:success', ->
  33 + Turbolinks.visit(location.href)
  34 +
  35 + $('li.users_group').bind 'ajax:success', ->
  36 + Turbolinks.visit(location.href)
  37 +
32 @Admin = Admin 38 @Admin = Admin
app/controllers/admin/members_controller.rb
@@ -1,9 +0,0 @@ @@ -1,9 +0,0 @@
1 -class Admin::MembersController < Admin::ApplicationController  
2 - def destroy  
3 - user = User.find_by_username(params[:id])  
4 - project = Project.find_with_namespace(params[:project_id])  
5 - project.users_projects.where(user_id: user).first.destroy  
6 -  
7 - redirect_to :back  
8 - end  
9 -end  
app/controllers/admin/users_controller.rb
@@ -83,9 +83,10 @@ class Admin::UsersController &lt; Admin::ApplicationController @@ -83,9 +83,10 @@ class Admin::UsersController &lt; Admin::ApplicationController
83 end 83 end
84 84
85 def destroy 85 def destroy
86 - if user.personal_projects.count > 0  
87 - redirect_to admin_users_path, alert: "User is a project owner and can't be removed." and return  
88 - end 86 + # 1. Remove groups where user is the only owner
  87 + user.solo_owned_groups.map(&:destroy)
  88 +
  89 + # 2. Remove user with all authored content including personal projects
89 user.destroy 90 user.destroy
90 91
91 respond_to do |format| 92 respond_to do |format|
app/models/group.rb
@@ -23,7 +23,7 @@ class Group &lt; Namespace @@ -23,7 +23,7 @@ class Group &lt; Namespace
23 end 23 end
24 24
25 def owners 25 def owners
26 - @owners ||= (users_groups.owners.map(&:user) << owner) 26 + @owners ||= (users_groups.owners.map(&:user) << owner).uniq
27 end 27 end
28 28
29 def add_users(user_ids, group_access) 29 def add_users(user_ids, group_access)
app/models/project_team.rb
@@ -32,7 +32,15 @@ class ProjectTeam @@ -32,7 +32,15 @@ class ProjectTeam
32 end 32 end
33 33
34 def find_tm(user_id) 34 def find_tm(user_id)
35 - project.users_projects.find_by_user_id(user_id) 35 + tm = project.users_projects.find_by_user_id(user_id)
  36 +
  37 + # If user is not in project members
  38 + # we should check for group membership
  39 + if group && !tm
  40 + tm = group.users_groups.find_by_user_id(user_id)
  41 + end
  42 +
  43 + tm
36 end 44 end
37 45
38 def add_user(user, access) 46 def add_user(user, access)
app/models/user.rb
@@ -135,7 +135,7 @@ class User &lt; ActiveRecord::Base @@ -135,7 +135,7 @@ class User &lt; ActiveRecord::Base
135 # Remove user from all groups 135 # Remove user from all groups
136 user.users_groups.find_each do |membership| 136 user.users_groups.find_each do |membership|
137 # skip owned resources 137 # skip owned resources
138 - next if membership.group.owner == user 138 + next if membership.group.owners.include?(user)
139 139
140 return false unless membership.destroy 140 return false unless membership.destroy
141 end 141 end
@@ -376,4 +376,10 @@ class User &lt; ActiveRecord::Base @@ -376,4 +376,10 @@ class User &lt; ActiveRecord::Base
376 self.send("#{attr}=", Sanitize.clean(value)) if value.present? 376 self.send("#{attr}=", Sanitize.clean(value)) if value.present?
377 end 377 end
378 end 378 end
  379 +
  380 + def solo_owned_groups
  381 + @solo_owned_groups ||= owned_groups.select do |group|
  382 + group.owners == [self]
  383 + end
  384 + end
379 end 385 end
app/views/admin/users/show.html.haml
@@ -7,15 +7,11 @@ @@ -7,15 +7,11 @@
7 %span.cred (Admin) 7 %span.cred (Admin)
8 8
9 .pull-right 9 .pull-right
10 - = link_to edit_admin_user_path(@user), class: "btn grouped btn-small" do 10 + = link_to edit_admin_user_path(@user), class: "btn grouped" do
11 %i.icon-edit 11 %i.icon-edit
12 Edit 12 Edit
13 - - unless @user == current_user  
14 - - if @user.blocked?  
15 - = link_to 'Unblock', unblock_admin_user_path(@user), method: :put, class: "btn grouped btn-small success"  
16 - - else  
17 - = link_to 'Block', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn grouped btn-small btn-remove"  
18 - = link_to 'Destroy', [:admin, @user], confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn grouped btn-small btn-remove" 13 + - if @user.blocked?
  14 + = link_to 'Unblock', unblock_admin_user_path(@user), method: :put, class: "btn grouped success"
19 %hr 15 %hr
20 16
21 .row 17 .row
@@ -63,24 +59,56 @@ @@ -63,24 +59,56 @@
63 %strong 59 %strong
64 = link_to @user.created_by.name, [:admin, @user.created_by] 60 = link_to @user.created_by.name, [:admin, @user.created_by]
65 61
  62 + - unless @user == current_user
  63 + .alert
  64 + %h4 Block user
  65 + %br
  66 + %p Blocking user has the following effects:
  67 + %ul
  68 + %li User will not be able to login
  69 + %li User will not be able to access git repositories
  70 + %li User will be removed from joined projects and groups
  71 + %li Personal projects will be left
  72 + %li Owned groups will be left
  73 + = link_to 'Block user', block_admin_user_path(@user), confirm: 'USER WILL BE BLOCKED! Are you sure?', method: :put, class: "btn btn-remove"
  74 +
  75 + .alert.alert-error
  76 + %h4
  77 + Remove user
  78 + %br
  79 + %p Deleting a user has the following effects:
  80 + %ul
  81 + %li All user content like authored issues, snippets, comments will be removed
  82 + - rp = @user.personal_projects.count
  83 + - unless rp.zero?
  84 + %li #{pluralize rp, 'personal project'} will be removed and cannot be restored
  85 + - if @user.solo_owned_groups.present?
  86 + %li
  87 + Next groups with all content will be removed:
  88 + %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
  89 + = link_to 'Remove user', [:admin, @user], confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?", method: :delete, class: "btn btn-remove"
  90 +
  91 + .span6
66 - if @user.users_groups.present? 92 - if @user.users_groups.present?
67 .ui-box 93 .ui-box
68 .title Groups: 94 .title Groups:
69 %ul.well-list 95 %ul.well-list
70 - @user.users_groups.each do |user_group| 96 - @user.users_groups.each do |user_group|
71 - group = user_group.group 97 - group = user_group.group
72 - %li 98 + %li.users_group
73 %strong= link_to group.name, admin_group_path(group) 99 %strong= link_to group.name, admin_group_path(group)
74 .pull-right 100 .pull-right
75 %span.light= user_group.human_access 101 %span.light= user_group.human_access
  102 + - unless user_group.owner?
  103 + = link_to group_users_group_path(group, user_group), confirm: remove_user_from_group_message(group, @user), method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
  104 + %i.icon-remove.icon-white
76 105
77 - .span6  
78 .ui-box 106 .ui-box
79 .title Projects (#{@projects.count}) 107 .title Projects (#{@projects.count})
80 %ul.well-list 108 %ul.well-list
81 - @projects.sort_by(&:name_with_namespace).each do |project| 109 - @projects.sort_by(&:name_with_namespace).each do |project|
82 - tm = project.team.find_tm(@user.id) 110 - tm = project.team.find_tm(@user.id)
83 - %li 111 + %li.users_project
84 = link_to admin_project_path(project), class: dom_class(project) do 112 = link_to admin_project_path(project), class: dom_class(project) do
85 - if project.namespace 113 - if project.namespace
86 = project.namespace.human_name 114 = project.namespace.human_name
@@ -94,5 +122,9 @@ @@ -94,5 +122,9 @@
94 %span.light Owner 122 %span.light Owner
95 - else 123 - else
96 %span.light= tm.human_access 124 %span.light= tm.human_access
97 - = link_to admin_project_member_path(project, tm.user), confirm: remove_from_project_team_message(project, @user), method: :delete, class: "btn btn-small btn-remove" do  
98 - %i.icon-remove 125 +
  126 + - if tm.respond_to? :project
  127 + = link_to project_team_member_path(project, @user), confirm: remove_from_project_team_message(project, @user), remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do
  128 + %i.icon-remove
  129 +
  130 +
config/routes.rb
@@ -89,11 +89,7 @@ Gitlab::Application.routes.draw do @@ -89,11 +89,7 @@ Gitlab::Application.routes.draw do
89 89
90 resource :logs, only: [:show] 90 resource :logs, only: [:show]
91 resource :background_jobs, controller: 'background_jobs', only: [:show] 91 resource :background_jobs, controller: 'background_jobs', only: [:show]
92 -  
93 - resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] do  
94 - resources :members, only: [:destroy]  
95 - end  
96 - 92 + resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show]
97 root to: "dashboard#index" 93 root to: "dashboard#index"
98 end 94 end
99 95
lib/gitlab/access.rb
@@ -44,5 +44,9 @@ module Gitlab @@ -44,5 +44,9 @@ module Gitlab
44 def human_access 44 def human_access
45 Gitlab::Access.options_with_owner.key(access_field) 45 Gitlab::Access.options_with_owner.key(access_field)
46 end 46 end
  47 +
  48 + def owner?
  49 + access_field == OWNER
  50 + end
47 end 51 end
48 end 52 end
spec/routing/admin_routing_spec.rb
@@ -75,13 +75,6 @@ describe Admin::ProjectsController, &quot;routing&quot; do @@ -75,13 +75,6 @@ describe Admin::ProjectsController, &quot;routing&quot; do
75 end 75 end
76 end 76 end
77 77
78 -# DELETE /admin/projects/:project_id/members/:id(.:format) admin/projects/members#destroy {id: /[^\/]+/, project_id: /[^\/]+/}  
79 -describe Admin::MembersController, "routing" do  
80 - it "to #destroy" do  
81 - delete("/admin/projects/test/members/1").should route_to('admin/members#destroy', project_id: 'test', id: '1')  
82 - end  
83 -end  
84 -  
85 # admin_hook_test GET /admin/hooks/:hook_id/test(.:format) admin/hooks#test 78 # admin_hook_test GET /admin/hooks/:hook_id/test(.:format) admin/hooks#test
86 # admin_hooks GET /admin/hooks(.:format) admin/hooks#index 79 # admin_hooks GET /admin/hooks(.:format) admin/hooks#index
87 # POST /admin/hooks(.:format) admin/hooks#create 80 # POST /admin/hooks(.:format) admin/hooks#create