Commit 7ecfaccf143a29f6114ccf0cbe45e015d62a481f
Exists in
master
and in
4 other branches
Merge branch 'master' into stable
Showing
12 changed files
with
138 additions
and
6 deletions
Show diff stats
CHANGELOG
VERSION
app/controllers/team_members_controller.rb
| ... | ... | @@ -43,4 +43,12 @@ class TeamMembersController < ProjectResourceController |
| 43 | 43 | format.js { render nothing: true } |
| 44 | 44 | end |
| 45 | 45 | end |
| 46 | + | |
| 47 | + def apply_import | |
| 48 | + giver = Project.find(params[:source_project_id]) | |
| 49 | + status = UsersProject.import_team(giver, project) | |
| 50 | + notice = status ? "Succesfully imported" : "Import failed" | |
| 51 | + | |
| 52 | + redirect_to project_team_members_path(project), notice: notice | |
| 53 | + end | |
| 46 | 54 | end | ... | ... |
app/models/users_project.rb
| ... | ... | @@ -21,6 +21,35 @@ class UsersProject < ActiveRecord::Base |
| 21 | 21 | delegate :name, :email, to: :user, prefix: true |
| 22 | 22 | |
| 23 | 23 | class << self |
| 24 | + def import_team(source_project, target_project) | |
| 25 | + UsersProject.without_repository_callback do | |
| 26 | + UsersProject.transaction do | |
| 27 | + team = source_project.users_projects.all | |
| 28 | + | |
| 29 | + team.each do |tm| | |
| 30 | + # Skip if user already present in team | |
| 31 | + next if target_project.users.include?(tm.user) | |
| 32 | + | |
| 33 | + new_tm = tm.dup | |
| 34 | + new_tm.id = nil | |
| 35 | + new_tm.project_id = target_project.id | |
| 36 | + new_tm.save | |
| 37 | + end | |
| 38 | + end | |
| 39 | + end | |
| 40 | + | |
| 41 | + target_project.update_repository | |
| 42 | + true | |
| 43 | + rescue | |
| 44 | + false | |
| 45 | + end | |
| 46 | + | |
| 47 | + def without_repository_callback | |
| 48 | + UsersProject.skip_callback(:destroy, :after, :update_repository) | |
| 49 | + yield | |
| 50 | + UsersProject.set_callback(:destroy, :after, :update_repository) | |
| 51 | + end | |
| 52 | + | |
| 24 | 53 | def bulk_delete(project, user_ids) |
| 25 | 54 | UsersProject.transaction do |
| 26 | 55 | UsersProject.where(:user_id => user_ids, :project_id => project.id).each do |users_project| | ... | ... |
app/views/issues/_form.html.haml
| ... | ... | @@ -12,7 +12,7 @@ |
| 12 | 12 | = f.label :title do |
| 13 | 13 | %strong= "Subject *" |
| 14 | 14 | .input |
| 15 | - = f.text_field :title, maxlength: 255, class: "xxlarge gfm-input" | |
| 15 | + = f.text_field :title, maxlength: 255, class: "xxlarge gfm-input", autofocus: true | |
| 16 | 16 | .issue_middle_block |
| 17 | 17 | .issue_assignee |
| 18 | 18 | = f.label :assignee_id do | ... | ... |
app/views/issues/_show.html.haml
| ... | ... | @@ -16,7 +16,7 @@ |
| 16 | 16 | = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {closed: false }, status_only: true), method: :put, class: "btn small grouped reopen_issue", remote: true |
| 17 | 17 | - else |
| 18 | 18 | = link_to 'Close', project_issue_path(issue.project, issue, issue: {closed: true }, status_only: true), method: :put, class: "btn small grouped close_issue", remote: true |
| 19 | - = link_to edit_project_issue_path(issue.project, issue), class: "btn small edit-issue-link", remote: true do | |
| 19 | + = link_to edit_project_issue_path(issue.project, issue), class: "btn small edit-issue-link grouped", remote: true do | |
| 20 | 20 | %i.icon-edit |
| 21 | 21 | Edit |
| 22 | 22 | ... | ... |
| ... | ... | @@ -0,0 +1,17 @@ |
| 1 | += render "projects/project_head" | |
| 2 | + | |
| 3 | +%h3.page_title | |
| 4 | + = "Import team from another project" | |
| 5 | +%hr | |
| 6 | +%p.slead | |
| 7 | + Read more about team import #{link_to "here", '#', class: 'vlink'}. | |
| 8 | += form_tag apply_import_project_team_members_path(@project), method: 'post' do | |
| 9 | + %p.slead Choose project you want to use as team source: | |
| 10 | + .padded | |
| 11 | + = label_tag :source_project_id, "Project" | |
| 12 | + .input= select_tag(:source_project_id, options_from_collection_for_select(current_user.projects, :id, :name), prompt: "Select project", class: "chosen xxlarge", required: true) | |
| 13 | + | |
| 14 | + .actions | |
| 15 | + = submit_tag 'Import', class: "btn save-btn" | |
| 16 | + = link_to "Cancel", project_team_index_path(@project), class: "btn cancel-btn" | |
| 17 | + | ... | ... |
app/views/team_members/index.html.haml
| ... | ... | @@ -5,9 +5,14 @@ |
| 5 | 5 | |
| 6 | 6 | - if can? current_user, :admin_team_member, @project |
| 7 | 7 | %p.slead |
| 8 | - = link_to new_project_team_member_path(@project), class: "btn small right", title: "New Team Member" do | |
| 9 | - New Team Member | |
| 10 | 8 | Read more about project permissions |
| 11 | 9 | %strong= link_to "here", help_permissions_path, class: "vlink" |
| 12 | 10 | |
| 11 | + %span.right | |
| 12 | + = link_to import_project_team_members_path(@project), class: "btn small grouped", title: "Import team from another project" do | |
| 13 | + Import team from another project | |
| 14 | + = link_to new_project_team_member_path(@project), class: "btn success small grouped", title: "New Team Member" do | |
| 15 | + New Team Member | |
| 16 | + | |
| 17 | + .clearfix | |
| 13 | 18 | = render partial: "team_members/team", locals: {project: @project} | ... | ... |
config/routes.rb
| ... | ... | @@ -188,7 +188,6 @@ Gitlab::Application.routes.draw do |
| 188 | 188 | :via => [:get, :post], constraints: {from: /.+/, to: /.+/} |
| 189 | 189 | |
| 190 | 190 | resources :team, controller: 'team_members', only: [:index] |
| 191 | - resources :team_members | |
| 192 | 191 | resources :milestones |
| 193 | 192 | resources :labels, only: [:index] |
| 194 | 193 | resources :issues do |
| ... | ... | @@ -199,6 +198,16 @@ Gitlab::Application.routes.draw do |
| 199 | 198 | end |
| 200 | 199 | end |
| 201 | 200 | |
| 201 | + resources :team_members do | |
| 202 | + collection do | |
| 203 | + | |
| 204 | + # Used for import team | |
| 205 | + # from another project | |
| 206 | + get :import | |
| 207 | + post :apply_import | |
| 208 | + end | |
| 209 | + end | |
| 210 | + | |
| 202 | 211 | resources :notes, only: [:index, :create, :destroy] do |
| 203 | 212 | collection do |
| 204 | 213 | post :preview | ... | ... |
features/project/team_management.feature
| ... | ... | @@ -32,3 +32,10 @@ Feature: Project Team management |
| 32 | 32 | And I click link "Remove from team" |
| 33 | 33 | Then I visit project "Shop" team page |
| 34 | 34 | And I should not see "Sam" in team list |
| 35 | + | |
| 36 | + Scenario: Import team from another project | |
| 37 | + Given I own project "Website" | |
| 38 | + And "Mike" is "Website" reporter | |
| 39 | + And I click link "Import team from another project" | |
| 40 | + When I submit "Website" project for import team | |
| 41 | + Then I should see "Mike" in team list as "Reporter" | ... | ... |
features/steps/project/project_team_management.rb
| ... | ... | @@ -86,4 +86,24 @@ class ProjectTeamManagement < Spinach::FeatureSteps |
| 86 | 86 | project = Project.find_by_name("Shop") |
| 87 | 87 | project.add_access(user, :write) |
| 88 | 88 | end |
| 89 | + | |
| 90 | + Given 'I own project "Website"' do | |
| 91 | + @project = Factory :project, :name => "Website" | |
| 92 | + @project.add_access(@user, :admin) | |
| 93 | + end | |
| 94 | + | |
| 95 | + And '"Mike" is "Website" reporter' do | |
| 96 | + user = User.find_by_name("Mike") | |
| 97 | + project = Project.find_by_name("Website") | |
| 98 | + project.add_access(user, :read) | |
| 99 | + end | |
| 100 | + | |
| 101 | + And 'I click link "Import team from another project"' do | |
| 102 | + click_link "Import team from another project" | |
| 103 | + end | |
| 104 | + | |
| 105 | + When 'I submit "Website" project for import team' do | |
| 106 | + select 'Website', from: 'source_project_id' | |
| 107 | + click_button 'Import' | |
| 108 | + end | |
| 89 | 109 | end | ... | ... |
spec/models/users_project_spec.rb
| ... | ... | @@ -35,4 +35,37 @@ describe UsersProject do |
| 35 | 35 | it { should respond_to(:user_name) } |
| 36 | 36 | it { should respond_to(:user_email) } |
| 37 | 37 | end |
| 38 | + | |
| 39 | + describe :import_team do | |
| 40 | + before do | |
| 41 | + @abilities = Six.new | |
| 42 | + @abilities << Ability | |
| 43 | + | |
| 44 | + @project_1 = create :project | |
| 45 | + @project_2 = create :project | |
| 46 | + | |
| 47 | + @user_1 = create :user | |
| 48 | + @user_2 = create :user | |
| 49 | + | |
| 50 | + @project_1.add_access @user_1, :write | |
| 51 | + @project_2.add_access @user_2, :read | |
| 52 | + | |
| 53 | + @status = UsersProject.import_team(@project_1, @project_2) | |
| 54 | + end | |
| 55 | + | |
| 56 | + it { @status.should be_true } | |
| 57 | + | |
| 58 | + describe 'project 2 should get user 1 as developer. user_2 should not be changed' do | |
| 59 | + it { @project_2.users.should include(@user_1) } | |
| 60 | + it { @project_2.users.should include(@user_2) } | |
| 61 | + | |
| 62 | + it { @abilities.allowed?(@user_1, :write_project, @project_2).should be_true } | |
| 63 | + it { @abilities.allowed?(@user_2, :read_project, @project_2).should be_true } | |
| 64 | + end | |
| 65 | + | |
| 66 | + describe 'project 1 should not be changed' do | |
| 67 | + it { @project_1.users.should include(@user_1) } | |
| 68 | + it { @project_1.users.should_not include(@user_2) } | |
| 69 | + end | |
| 70 | + end | |
| 38 | 71 | end | ... | ... |