From e1781235696fe23851154ebbdc913e970d3c0f3a Mon Sep 17 00:00:00 2001 From: Eric Davis Date: Fri, 4 Dec 2009 22:46:12 +0000 Subject: [PATCH] Enhanced the Issue Bulk Copy feature: * Add a Copy option to the Context menu for multiple issues. * Added assigned to, status, start and due date options to the move/copy form. * Allow Issue#move_to to change attributes on the moved/copied issue. #4117 git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@3122 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/issues_controller.rb | 12 ++++++++---- app/models/issue.rb | 10 +++++++++- app/models/workflow.rb | 11 +++++++++++ app/views/issues/context_menu.rhtml | 10 ++++++++-- app/views/issues/move.rhtml | 24 ++++++++++++++++++++++- test/functional/issues_controller_test.rb | 24 +++++++++++++++++++++++ test/unit/issue_test.rb | 32 +++++++++++++++++++++++++++++++ 7 files changed, 115 insertions(+), 8 deletions(-) diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index 336f63b1..a86653ed 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -271,13 +271,12 @@ class IssuesController < ApplicationController redirect_to(params[:back_to] || {:controller => 'issues', :action => 'index', :project_id => @project}) return end - # Find potential statuses the user could be allowed to switch issues to - @available_statuses = Workflow.find(:all, :include => :new_status, - :conditions => {:role_id => User.current.roles_for_project(@project).collect(&:id)}).collect(&:new_status).compact.uniq.sort + @available_statuses = Workflow.available_statuses(@project) @custom_fields = @project.issue_custom_fields.select {|f| f.field_format == 'list'} end def move + @copy = params[:copy_options] && params[:copy_options][:copy] @allowed_projects = [] # find projects to which the user is allowed to move the issue if User.current.admin? @@ -289,13 +288,18 @@ class IssuesController < ApplicationController @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) 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| + changed_attributes = {} + [:assigned_to_id, :status_id, :start_date, :due_date].each do |valid_attribute| + changed_attributes[valid_attribute] = params[valid_attribute] if params[valid_attribute] + end issue.init_journal(User.current) - if r = issue.move_to(@target_project, new_tracker, params[:copy_options]) + if r = issue.move_to(@target_project, new_tracker, {:copy => @copy, :attributes => changed_attributes}) moved_issues << r else unsaved_issue_ids << issue.id diff --git a/app/models/issue.rb b/app/models/issue.rb index 1facf2a5..dac64cbd 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -108,7 +108,15 @@ class Issue < ActiveRecord::Base end if options[:copy] issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h} - issue.status = self.status + issue.status = if options[:attributes] && options[:attributes][:status_id] + IssueStatus.find_by_id(options[:attributes][:status_id]) + else + self.status + end + end + # Allow bulk setting of attributes on the issue + if options[:attributes] + issue.attributes = options[:attributes] end if issue.save unless options[:copy] diff --git a/app/models/workflow.rb b/app/models/workflow.rb index 4168fdea..a96abaf1 100644 --- a/app/models/workflow.rb +++ b/app/models/workflow.rb @@ -40,4 +40,15 @@ class Workflow < ActiveRecord::Base result end + + # Find potential statuses the user could be allowed to switch issues to + def self.available_statuses(project, user=User.current) + Workflow.find(:all, + :include => :new_status, + :conditions => {:role_id => user.roles_for_project(project).collect(&:id)}). + collect(&:new_status). + compact. + uniq. + sort + end end diff --git a/app/views/issues/context_menu.rhtml b/app/views/issues/context_menu.rhtml index d2c673d7..c67c1bcd 100644 --- a/app/views/issues/context_menu.rhtml +++ b/app/views/issues/context_menu.rhtml @@ -88,8 +88,6 @@ <% if !@issue.nil? %> -
  • <%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, - :class => 'icon-copy', :disabled => !@can[:copy] %>
  • <% if @can[:log_time] -%>
  • <%= context_menu_link l(:button_log_time), {:controller => 'timelog', :action => 'edit', :issue_id => @issue}, :class => 'icon-time-add' %>
  • @@ -99,6 +97,14 @@ <% end %> <% end %> +<% if @issue.present? %> +
  • <%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'new', :project_id => @project, :copy_from => @issue}, + :class => 'icon-copy', :disabled => !@can[:copy] %>
  • +<% else %> +
  • <%= context_menu_link l(:button_copy), {:controller => 'issues', :action => 'move', :ids => @issues.collect(&:id), :copy_options => {:copy => 't'}}, + :class => 'icon-copy', :disabled => !@can[:move] %>
  • +<% end %> +
  • <%= context_menu_link l(:button_move), {:controller => 'issues', :action => 'move', :ids => @issues.collect(&:id)}, :class => 'icon-move', :disabled => !@can[:move] %>
  • <%= context_menu_link l(:button_delete), {:controller => 'issues', :action => 'destroy', :ids => @issues.collect(&:id)}, diff --git a/app/views/issues/move.rhtml b/app/views/issues/move.rhtml index 0d4717f5..0d5cb130 100644 --- a/app/views/issues/move.rhtml +++ b/app/views/issues/move.rhtml @@ -21,8 +21,30 @@

    <%= select_tag "new_tracker_id", "" + options_from_collection_for_select(@trackers, "id", "name") %>

    +

    + + <%= select_tag('assigned_to_id', content_tag('option', l(:label_no_change_option), :value => '') + + content_tag('option', l(:label_nobody), :value => 'none') + + options_from_collection_for_select(@target_project.assignable_users, :id, :name)) %> +

    + +

    + + <%= select_tag('status_id', "" + options_from_collection_for_select(@available_statuses, :id, :name)) %> +

    + +

    + + <%= text_field_tag 'start_date', '', :size => 10 %><%= calendar_for('start_date') %> +

    + +

    + + <%= text_field_tag 'due_date', '', :size => 10 %><%= calendar_for('due_date') %> +

    +

    -<%= check_box_tag "copy_options[copy]", "1" %>

    +<%= check_box_tag "copy_options[copy]", "1", @copy %>

    <%= submit_tag l(:button_move) %> diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 93e29957..8ebf6c02 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -1059,6 +1059,27 @@ class IssuesControllerTest < ActionController::TestCase assert_redirected_to 'projects/ecookbook/issues' end + context "#move via bulk copy" do + should "allow changing the issue's attributes" do + @request.session[:user_id] = 2 + assert_difference 'Issue.count', 2 do + assert_no_difference 'Project.find(1).issues.count' do + post :move, :ids => [1, 2], :new_project_id => 2, :copy_options => {:copy => '1'}, :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 + end + def test_copy_to_another_project_should_follow_when_needed @request.session[:user_id] = 2 post :move, :ids => [1], :new_project_id => 2, :copy_options => {:copy => '1'}, :follow => '1' @@ -1117,6 +1138,9 @@ class IssuesControllerTest < ActionController::TestCase assert_tag :tag => 'a', :content => 'Dave Lopper', :attributes => { :href => '/issues/bulk_edit?assigned_to_id=3&ids%5B%5D=1&ids%5B%5D=2', :class => '' } + assert_tag :tag => 'a', :content => 'Copy', + :attributes => { :href => '/issues/move?copy_options%5Bcopy%5D=t&ids%5B%5D=1&ids%5B%5D=2', + :class => 'icon-copy' } assert_tag :tag => 'a', :content => 'Move', :attributes => { :href => '/issues/move?ids%5B%5D=1&ids%5B%5D=2', :class => 'icon-move' } diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index bd37f988..6213524b 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -352,6 +352,38 @@ class IssueTest < ActiveSupport::TestCase # Custom field #2 is not associated with target tracker assert_nil copy.custom_value_for(2) end + + context "#move_to" do + context "as a copy" do + setup do + @issue = Issue.find(1) + @copy = nil + end + + should "allow assigned_to changes" do + @copy = @issue.move_to(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:assigned_to_id => 3}}) + assert_equal 3, @copy.assigned_to_id + end + + should "allow status changes" do + @copy = @issue.move_to(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:status_id => 2}}) + assert_equal 2, @copy.status_id + end + + should "allow start date changes" do + date = Date.today + @copy = @issue.move_to(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:start_date => date}}) + assert_equal date, @copy.start_date + end + + should "allow due date changes" do + date = Date.today + @copy = @issue.move_to(Project.find(3), Tracker.find(2), {:copy => true, :attributes => {:due_date => date}}) + + assert_equal date, @copy.due_date + end + end + end def test_recipients_should_not_include_users_that_cannot_view_the_issue issue = Issue.find(12) -- 2.11.0