diff --git a/app/controllers/issue_moves_controller.rb b/app/controllers/issue_moves_controller.rb deleted file mode 100644 index 234d4d7..0000000 --- a/app/controllers/issue_moves_controller.rb +++ /dev/null @@ -1,85 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -class IssueMovesController < ApplicationController - menu_item :issues - - default_search_scope :issues - before_filter :find_issues, :check_project_uniqueness - before_filter :authorize - - def new - prepare_for_issue_move - render :layout => false if request.xhr? - end - - def create - prepare_for_issue_move - - if request.post? - new_tracker = params[:new_tracker_id].blank? ? nil : @target_project.trackers.find_by_id(params[:new_tracker_id]) - unsaved_issue_ids = [] - moved_issues = [] - @issues.each do |issue| - issue.reload - call_hook(:controller_issues_move_before_save, { :params => params, :issue => issue, :target_project => @target_project, :copy => !!@copy }) - if r = issue.move_to_project(@target_project, new_tracker, {:copy => @copy, :attributes => extract_changed_attributes_for_move(params), :notes => @notes}) - moved_issues << r - else - unsaved_issue_ids << issue.id - end - end - set_flash_from_bulk_issue_save(@issues, unsaved_issue_ids) - - if params[:follow] - if @issues.size == 1 && moved_issues.size == 1 - redirect_to :controller => 'issues', :action => 'show', :id => moved_issues.first - else - redirect_to :controller => 'issues', :action => 'index', :project_id => (@target_project || @project) - end - else - redirect_to :controller => 'issues', :action => 'index', :project_id => @project - end - return - end - end - - private - - def prepare_for_issue_move - @issues.sort! - @copy = params[:copy_options] && params[:copy_options][:copy] - @allowed_projects = Issue.allowed_target_projects_on_move - @target_project = @allowed_projects.detect {|p| p.id.to_s == params[:new_project_id]} if params[:new_project_id] - @target_project ||= @project - @trackers = @target_project.trackers - @available_statuses = Workflow.available_statuses(@project) - @notes = params[:notes] - @notes ||= '' - end - - def extract_changed_attributes_for_move(params) - changed_attributes = {} - [:assigned_to_id, :status_id, :start_date, :due_date, :priority_id].each do |valid_attribute| - unless params[valid_attribute].blank? - changed_attributes[valid_attribute] = (params[valid_attribute] == 'none' ? nil : params[valid_attribute]) - end - end - changed_attributes - end - -end diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index ff8d2d0..4cbfa3c 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -206,9 +206,11 @@ class IssuesController < ApplicationController end end - # Bulk edit a set of issues + # Bulk edit/copy a set of issues def bulk_edit @issues.sort! + @copy = params[:copy].present? + @notes = params[:notes] if User.current.allowed_to?(:move_issues, @projects) @allowed_projects = Issue.allowed_target_projects_on_move @@ -226,18 +228,21 @@ class IssuesController < ApplicationController @assignables = target_projects.map(&:assignable_users).inject{|memo,a| memo & a} @trackers = target_projects.map(&:trackers).inject{|memo,t| memo & t} - @notes = params[:notes] render :layout => false if request.xhr? end def bulk_update @issues.sort! + @copy = params[:copy].present? attributes = parse_params_for_bulk_issue_attributes(params) unsaved_issue_ids = [] moved_issues = [] @issues.each do |issue| issue.reload + if @copy + issue = Issue.new.copy_from(issue) + end journal = issue.init_journal(User.current, params[:notes]) issue.safe_attributes = attributes call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) diff --git a/app/helpers/issue_moves_helper.rb b/app/helpers/issue_moves_helper.rb deleted file mode 100644 index 0027836..0000000 --- a/app/helpers/issue_moves_helper.rb +++ /dev/null @@ -1,4 +0,0 @@ -# encoding: utf-8 -# -module IssueMovesHelper -end diff --git a/app/views/context_menus/issues.html.erb b/app/views/context_menus/issues.html.erb index f824975..628433c 100644 --- a/app/views/context_menus/issues.html.erb +++ b/app/views/context_menus/issues.html.erb @@ -110,7 +110,7 @@
- <% if @target_project %> + <% if @copy %> + <%= hidden_field_tag 'copy', '1' %> + <%= submit_tag l(:button_copy) %> + <%= submit_tag l(:button_copy_and_follow), :name => 'follow' %> + <% elsif @target_project %> <%= submit_tag l(:button_move) %> <%= submit_tag l(:button_move_and_follow), :name => 'follow' %> <% else %> diff --git a/config/routes.rb b/config/routes.rb index 7814fe8..ae5f0f8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -40,9 +40,6 @@ ActionController::Routing::Routes.draw do |map| end end - map.resources :issue_moves, :only => [:new, :create], - :path_prefix => '/issues', :as => 'move' - # Misc issue routes. TODO: move into resources map.auto_complete_issues '/issues/auto_complete', :controller => 'auto_completes', :action => 'issues', :conditions => { :method => :get } diff --git a/lib/redmine.rb b/lib/redmine.rb index 6fdb219..b33b584 100644 --- a/lib/redmine.rb +++ b/lib/redmine.rb @@ -76,7 +76,7 @@ Redmine::AccessControl.map do |map| map.permission :add_issue_notes, {:issues => [:edit, :update], :journals => [:new]} map.permission :edit_issue_notes, {:journals => :edit}, :require => :loggedin map.permission :edit_own_issue_notes, {:journals => :edit}, :require => :loggedin - map.permission :move_issues, {:issue_moves => [:new, :create]}, :require => :loggedin + map.permission :move_issues, {:issues => [:bulk_edit, :bulk_update]}, :require => :loggedin map.permission :delete_issues, {:issues => :destroy}, :require => :member # Queries map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member diff --git a/test/functional/context_menus_controller_test.rb b/test/functional/context_menus_controller_test.rb index b565009..7a18856 100644 --- a/test/functional/context_menus_controller_test.rb +++ b/test/functional/context_menus_controller_test.rb @@ -47,7 +47,7 @@ class ContextMenusControllerTest < ActionController::TestCase :attributes => { :href => '/projects/ecookbook/issues/1/copy', :class => 'icon-duplicate' } assert_tag :tag => 'a', :content => 'Copy', - :attributes => { :href => '/issues/move/new?copy_options%5Bcopy%5D=t&ids%5B%5D=1', + :attributes => { :href => '/issues/bulk_edit?copy=1&ids%5B%5D=1', :class => 'icon-copy' } assert_no_tag :tag => 'a', :content => 'Move' assert_tag :tag => 'a', :content => 'Delete', @@ -86,7 +86,7 @@ class ContextMenusControllerTest < ActionController::TestCase :attributes => { :href => "/issues/bulk_update?#{ids}&issue%5Bassigned_to_id%5D=3", :class => '' } assert_tag :tag => 'a', :content => 'Copy', - :attributes => { :href => "/issues/move/new?copy_options%5Bcopy%5D=t&#{ids}", + :attributes => { :href => "/issues/bulk_edit?copy=1&#{ids}", :class => 'icon-copy' } assert_no_tag :tag => 'a', :content => 'Move' assert_tag :tag => 'a', :content => 'Delete', diff --git a/test/functional/issue_moves_controller_test.rb b/test/functional/issue_moves_controller_test.rb deleted file mode 100644 index 26feeff..0000000 --- a/test/functional/issue_moves_controller_test.rb +++ /dev/null @@ -1,116 +0,0 @@ -require File.expand_path('../../test_helper', __FILE__) - -class IssueMovesControllerTest < ActionController::TestCase - fixtures :projects, :trackers, :issue_statuses, :issues, - :enumerations, :users, :issue_categories, - :projects_trackers, - :roles, - :member_roles, - :members, - :enabled_modules, - :workflows, - :journals, :journal_details - - def setup - User.current = nil - end - - def test_get_issue_moves_new - @request.session[:user_id] = 2 - get :new, :id => 1 - - assert_tag :tag => 'option', :content => 'eCookbook', - :attributes => { :value => '1', :selected => 'selected' } - %w(new_tracker_id status_id priority_id assigned_to_id).each do |field| - assert_tag :tag => 'option', :content => '(No change)', :attributes => { :value => '' }, - :parent => {:tag => 'select', :attributes => {:id => field}} - assert_no_tag :tag => 'option', :attributes => {:selected => 'selected'}, - :parent => {:tag => 'select', :attributes => {:id => field}} - end - - # Be sure we don't include inactive enumerations - assert ! IssuePriority.find(15).active? - assert_no_tag :option, :attributes => {:value => '15'}, - :parent => {:tag => 'select', :attributes => {:id => 'priority_id'} } - end - - def test_bulk_copy_to_another_project - @request.session[:user_id] = 2 - assert_difference 'Issue.count', 2 do - assert_no_difference 'Project.find(1).issues.count' do - post :create, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'} - end - end - assert_redirected_to '/projects/ecookbook/issues' - end - - context "#create via bulk copy" do - should "allow not changing the issue's attributes" do - @request.session[:user_id] = 2 - issue_before_move = Issue.find(1) - assert_difference 'Issue.count', 1 do - assert_no_difference 'Project.find(1).issues.count' do - post :create, :ids => [1], :new_project_id => 2, - :copy_options => {:copy => '1'}, :new_tracker_id => '', - :assigned_to_id => '', :status_id => '', - :start_date => '', :due_date => '' - end - end - issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2}) - assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id - assert_equal issue_before_move.status_id, issue_after_move.status_id - assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id - end - - should "allow changing the issue's attributes" do - # Fixes random test failure with Mysql - # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2}) - # doesn't return the expected results - Issue.delete_all("project_id=2") - - @request.session[:user_id] = 2 - assert_difference 'Issue.count', 2 do - assert_no_difference 'Project.find(1).issues.count' do - post :create, :ids => [1, 2], :new_project_id => 2, - :copy_options => {:copy => '1'}, :new_tracker_id => '', - :assigned_to_id => 4, :status_id => 3, - :start_date => '2009-12-01', :due_date => '2009-12-31' - end - end - - copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2}) - assert_equal 2, copied_issues.size - copied_issues.each do |issue| - assert_equal 2, issue.project_id, "Project is incorrect" - assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect" - assert_equal 3, issue.status_id, "Status is incorrect" - assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect" - assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect" - end - end - - should "allow adding a note when copying" do - @request.session[:user_id] = 2 - assert_difference 'Issue.count', 1 do - post :create, :ids => [1], :copy_options => {:copy => '1'}, - :notes => 'Copying one issue', :new_tracker_id => '', - :assigned_to_id => 4, :status_id => 3, - :start_date => '2009-12-01', :due_date => '2009-12-31' - end - - issue = Issue.first(:order => 'id DESC') - assert_equal 1, issue.journals.size - journal = issue.journals.first - assert_equal 0, journal.details.size - assert_equal 'Copying one issue', journal.notes - end - end - - def test_copy_to_another_project_should_follow_when_needed - @request.session[:user_id] = 2 - post :create, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :follow => '1' - issue = Issue.first(:order => 'id DESC') - assert_redirected_to :controller => 'issues', :action => 'show', :id => issue - end - -end diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 234cb41..66bd660 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -2319,6 +2319,87 @@ class IssuesControllerTest < ActionController::TestCase assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier end + def test_bulk_copy_to_another_project + @request.session[:user_id] = 2 + assert_difference 'Issue.count', 2 do + assert_no_difference 'Project.find(1).issues.count' do + post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1' + end + end + assert_redirected_to '/projects/ecookbook/issues' + end + + def test_bulk_copy_should_allow_not_changing_the issue_attributes + @request.session[:user_id] = 2 + issue_before_move = Issue.find(1) + assert_difference 'Issue.count', 1 do + assert_no_difference 'Project.find(1).issues.count' do + post :bulk_update, :ids => [1], :copy => '1', + :issue => { + :project_id => '2', :tracker_id => '', :assigned_to_id => '', + :status_id => '', :start_date => '', :due_date => '' + } + end + end + issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2}) + assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id + assert_equal issue_before_move.status_id, issue_after_move.status_id + assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id + end + + def test_bulk_copy_should_allow_changing_the_issue_attributes + # Fixes random test failure with Mysql + # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2}) + # doesn't return the expected results + Issue.delete_all("project_id=2") + + @request.session[:user_id] = 2 + assert_difference 'Issue.count', 2 do + assert_no_difference 'Project.find(1).issues.count' do + post :bulk_update, :ids => [1, 2], :copy => '1', + :issue => { + :project_id => '2', :tracker_id => '', :assigned_to_id => '4', + :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31' + } + end + end + + copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2}) + assert_equal 2, copied_issues.size + copied_issues.each do |issue| + assert_equal 2, issue.project_id, "Project is incorrect" + assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect" + assert_equal 3, issue.status_id, "Status is incorrect" + assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect" + assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect" + end + end + + def test_bulk_copy_should_allow_adding_a_note + @request.session[:user_id] = 2 + assert_difference 'Issue.count', 1 do + post :bulk_update, :ids => [1], :copy => '1', + :notes => 'Copying one issue', + :issue => { + :project_id => '', :tracker_id => '', :assigned_to_id => '4', + :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31' + } + end + + issue = Issue.first(:order => 'id DESC') + assert_equal 1, issue.journals.size + journal = issue.journals.first + assert_equal 0, journal.details.size + assert_equal 'Copying one issue', journal.notes + end + + def test_bulk_copy_to_another_project_should_follow_when_needed + @request.session[:user_id] = 2 + post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1' + issue = Issue.first(:order => 'id DESC') + assert_redirected_to :controller => 'issues', :action => 'show', :id => issue + end + def test_destroy_issue_with_no_time_entries assert_nil TimeEntry.find_by_issue_id(2) @request.session[:user_id] = 2 diff --git a/test/integration/routing/issue_moves_test.rb b/test/integration/routing/issue_moves_test.rb deleted file mode 100644 index b4dcee9..0000000 --- a/test/integration/routing/issue_moves_test.rb +++ /dev/null @@ -1,31 +0,0 @@ -# Redmine - project management software -# Copyright (C) 2006-2011 Jean-Philippe Lang -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU General Public License -# as published by the Free Software Foundation; either version 2 -# of the License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -require File.expand_path('../../../test_helper', __FILE__) - -class RoutingIssueMovesTest < ActionController::IntegrationTest - def test_issue_moves - assert_routing( - { :method => 'get', :path => "/issues/move/new" }, - { :controller => 'issue_moves', :action => 'new' } - ) - assert_routing( - { :method => 'post', :path => "/issues/move" }, - { :controller => 'issue_moves', :action => 'create' } - ) - end -end