##// END OF EJS Templates
Option to copy subtasks when copying issue(s) (#6965)....
Jean-Philippe Lang -
r10144:5003927f13f5
parent child
Show More
@@ -221,6 +221,7 class IssuesController < ApplicationController
221 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
221 @categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
222 if @copy
222 if @copy
223 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
223 @attachments_present = @issues.detect {|i| i.attachments.any?}.present?
224 @subtasks_present = @issues.detect {|i| !i.leaf?}.present?
224 end
225 end
225
226
226 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
227 @safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
@@ -237,7 +238,10 class IssuesController < ApplicationController
237 @issues.each do |issue|
238 @issues.each do |issue|
238 issue.reload
239 issue.reload
239 if @copy
240 if @copy
240 issue = issue.copy({}, :attachments => params[:copy_attachments].present?)
241 issue = issue.copy({},
242 :attachments => params[:copy_attachments].present?,
243 :subtasks => params[:copy_subtasks].present?
244 )
241 end
245 end
242 journal = issue.init_journal(User.current, params[:notes])
246 journal = issue.init_journal(User.current, params[:notes])
243 issue.safe_attributes = attributes
247 issue.safe_attributes = attributes
@@ -374,7 +378,8 private
374 begin
378 begin
375 @copy_from = Issue.visible.find(params[:copy_from])
379 @copy_from = Issue.visible.find(params[:copy_from])
376 @copy_attachments = params[:copy_attachments].present? || request.get?
380 @copy_attachments = params[:copy_attachments].present? || request.get?
377 @issue.copy_from(@copy_from, :attachments => @copy_attachments)
381 @copy_subtasks = params[:copy_subtasks].present? || request.get?
382 @issue.copy_from(@copy_from, :attachments => @copy_attachments, :subtasks => @copy_subtasks)
378 rescue ActiveRecord::RecordNotFound
383 rescue ActiveRecord::RecordNotFound
379 render_404
384 render_404
380 return
385 return
@@ -77,6 +77,8 class Issue < ActiveRecord::Base
77 before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change
77 before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change
78 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
78 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
79 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
79 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
80 # Should be after_create but would be called before previous after_save callbacks
81 after_save :after_create_from_copy
80 after_destroy :update_parent_attributes
82 after_destroy :update_parent_attributes
81
83
82 # Returns a SQL conditions string used to find all issues visible by the specified user
84 # Returns a SQL conditions string used to find all issues visible by the specified user
@@ -169,6 +171,7 class Issue < ActiveRecord::Base
169 end
171 end
170 end
172 end
171 @copied_from = issue
173 @copied_from = issue
174 @copy_options = options
172 self
175 self
173 end
176 end
174
177
@@ -1000,6 +1003,30 class Issue < ActiveRecord::Base
1000 end
1003 end
1001 end
1004 end
1002
1005
1006 # Copies subtasks from the copied issue
1007 def after_create_from_copy
1008 return unless copy?
1009
1010 unless @copied_from.leaf? || @copy_options[:subtasks] == false || @subtasks_copied
1011 @copied_from.children.each do |child|
1012 unless child.visible?
1013 # Do not copy subtasks that are not visible to avoid potential disclosure of private data
1014 logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger
1015 next
1016 end
1017 copy = Issue.new.copy_from(child, @copy_options)
1018 copy.author = author
1019 copy.project = project
1020 copy.parent_issue_id = id
1021 # Children subtasks are copied recursively
1022 unless copy.save
1023 logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger
1024 end
1025 end
1026 @subtasks_copied = true
1027 end
1028 end
1029
1003 def update_nested_set_attributes
1030 def update_nested_set_attributes
1004 if root_id.nil?
1031 if root_id.nil?
1005 # issue was just created
1032 # issue was just created
@@ -77,6 +77,13
77 </p>
77 </p>
78 <% end %>
78 <% end %>
79
79
80 <% if @copy && @subtasks_present %>
81 <p>
82 <label for='copy_subtasks'><%= l(:label_copy_subtasks) %></label>
83 <%= check_box_tag 'copy_subtasks', '1', true %>
84 </p>
85 <% end %>
86
80 <%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
87 <%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
81 </div>
88 </div>
82
89
@@ -17,6 +17,12
17 <%= check_box_tag 'copy_attachments', '1', @copy_attachments %>
17 <%= check_box_tag 'copy_attachments', '1', @copy_attachments %>
18 </p>
18 </p>
19 <% end %>
19 <% end %>
20 <% if @copy_from && !@copy_from.leaf? %>
21 <p>
22 <label for="copy_subtasks"><%= l(:label_copy_subtasks) %></label>
23 <%= check_box_tag 'copy_subtasks', '1', @copy_subtasks %>
24 </p>
25 <% end %>
20
26
21 <p id="attachments_form"><label><%= l(:label_attachment_plural) %></label><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
27 <p id="attachments_form"><label><%= l(:label_attachment_plural) %></label><%= render :partial => 'attachments/form', :locals => {:container => @issue} %></p>
22
28
@@ -857,6 +857,7 en:
857 label_child_revision: Child
857 label_child_revision: Child
858 label_export_options: "%{export_format} export options"
858 label_export_options: "%{export_format} export options"
859 label_copy_attachments: Copy attachments
859 label_copy_attachments: Copy attachments
860 label_copy_subtasks: Copy subtasks
860 label_item_position: "%{position} of %{count}"
861 label_item_position: "%{position} of %{count}"
861 label_completed_versions: Completed versions
862 label_completed_versions: Completed versions
862 label_search_for_watchers: Search for watchers to add
863 label_search_for_watchers: Search for watchers to add
@@ -833,6 +833,7 fr:
833 label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
833 label_issues_visibility_own: Demandes créées par ou assignées à l'utilisateur
834 label_export_options: Options d'exportation %{export_format}
834 label_export_options: Options d'exportation %{export_format}
835 label_copy_attachments: Copier les fichiers
835 label_copy_attachments: Copier les fichiers
836 label_copy_subtasks: Copier les sous-tΓ’ches
836 label_item_position: "%{position} sur %{count}"
837 label_item_position: "%{position} sur %{count}"
837 label_completed_versions: Versions passΓ©es
838 label_completed_versions: Versions passΓ©es
838 label_session_expiration: Expiration des sessions
839 label_session_expiration: Expiration des sessions
@@ -2268,6 +2268,14 class IssuesControllerTest < ActionController::TestCase
2268 assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
2268 assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
2269 end
2269 end
2270
2270
2271 def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox
2272 @request.session[:user_id] = 2
2273 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
2274 get :new, :project_id => 1, :copy_from => issue.id
2275
2276 assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value=1]'
2277 end
2278
2271 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2279 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2272 @request.session[:user_id] = 2
2280 @request.session[:user_id] = 2
2273 get :new, :project_id => 1, :copy_from => 99999
2281 get :new, :project_id => 1, :copy_from => 99999
@@ -2349,6 +2357,37 class IssuesControllerTest < ActionController::TestCase
2349 assert_equal count + 1, copy.attachments.count
2357 assert_equal count + 1, copy.attachments.count
2350 end
2358 end
2351
2359
2360 def test_create_as_copy_should_copy_subtasks
2361 @request.session[:user_id] = 2
2362 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
2363 count = issue.descendants.count
2364
2365 assert_difference 'Issue.count', count+1 do
2366 assert_no_difference 'Journal.count' do
2367 post :create, :project_id => 1, :copy_from => issue.id,
2368 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'},
2369 :copy_subtasks => '1'
2370 end
2371 end
2372 copy = Issue.where(:parent_id => nil).first(:order => 'id DESC')
2373 assert_equal count, copy.descendants.count
2374 assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort
2375 end
2376
2377 def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks
2378 @request.session[:user_id] = 2
2379 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
2380
2381 assert_difference 'Issue.count', 1 do
2382 assert_no_difference 'Journal.count' do
2383 post :create, :project_id => 1, :copy_from => 3,
2384 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'}
2385 end
2386 end
2387 copy = Issue.where(:parent_id => nil).first(:order => 'id DESC')
2388 assert_equal 0, copy.descendants.count
2389 end
2390
2352 def test_create_as_copy_with_failure
2391 def test_create_as_copy_with_failure
2353 @request.session[:user_id] = 2
2392 @request.session[:user_id] = 2
2354 post :create, :project_id => 1, :copy_from => 1,
2393 post :create, :project_id => 1, :copy_from => 1,
@@ -3473,6 +3512,33 class IssuesControllerTest < ActionController::TestCase
3473 end
3512 end
3474 end
3513 end
3475
3514
3515 def test_bulk_copy_should_allow_not_copying_the_subtasks
3516 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
3517 @request.session[:user_id] = 2
3518
3519 assert_difference 'Issue.count', 1 do
3520 post :bulk_update, :ids => [issue.id], :copy => '1',
3521 :issue => {
3522 :project_id => ''
3523 }
3524 end
3525 end
3526
3527 def test_bulk_copy_should_allow_copying_the_subtasks
3528 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
3529 count = issue.descendants.count
3530 @request.session[:user_id] = 2
3531
3532 assert_difference 'Issue.count', count+1 do
3533 post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1',
3534 :issue => {
3535 :project_id => ''
3536 }
3537 end
3538 copy = Issue.where(:parent_id => nil).order("id DESC").first
3539 assert_equal count, copy.descendants.count
3540 end
3541
3476 def test_bulk_copy_to_another_project_should_follow_when_needed
3542 def test_bulk_copy_to_another_project_should_follow_when_needed
3477 @request.session[:user_id] = 2
3543 @request.session[:user_id] = 2
3478 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3544 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
@@ -81,6 +81,15 module ObjectHelpers
81 issue
81 issue
82 end
82 end
83
83
84 # Generates an issue with some children and a grandchild
85 def Issue.generate_with_descendants!(project, attributes={})
86 issue = Issue.generate_for_project!(project, attributes)
87 child = Issue.generate_for_project!(project, :subject => 'Child1', :parent_issue_id => issue.id)
88 Issue.generate_for_project!(project, :subject => 'Child2', :parent_issue_id => issue.id)
89 Issue.generate_for_project!(project, :subject => 'Child11', :parent_issue_id => child.id)
90 issue.reload
91 end
92
84 def Version.generate!(attributes={})
93 def Version.generate!(attributes={})
85 @generated_version_name ||= 'Version 0'
94 @generated_version_name ||= 'Version 0'
86 @generated_version_name.succ!
95 @generated_version_name.succ!
@@ -633,6 +633,41 class IssueTest < ActiveSupport::TestCase
633 assert_equal orig.status, issue.status
633 assert_equal orig.status, issue.status
634 end
634 end
635
635
636 def test_copy_should_copy_subtasks
637 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
638
639 copy = issue.reload.copy
640 copy.author = User.find(7)
641 assert_difference 'Issue.count', 1+issue.descendants.count do
642 assert copy.save
643 end
644 copy.reload
645 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
646 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
647 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
648 assert_equal copy.author, child_copy.author
649 end
650
651 def test_copy_should_copy_subtasks_to_target_project
652 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
653
654 copy = issue.copy(:project_id => 3)
655 assert_difference 'Issue.count', 1+issue.descendants.count do
656 assert copy.save
657 end
658 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
659 end
660
661 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
662 issue = Issue.generate_with_descendants!(Project.find(1), :subject => 'Parent')
663
664 copy = issue.reload.copy
665 assert_difference 'Issue.count', 1+issue.descendants.count do
666 assert copy.save
667 assert copy.save
668 end
669 end
670
636 def test_should_not_call_after_project_change_on_creation
671 def test_should_not_call_after_project_change_on_creation
637 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Test', :author_id => 1)
672 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Test', :author_id => 1)
638 issue.expects(:after_project_change).never
673 issue.expects(:after_project_change).never
General Comments 0
You need to be logged in to leave comments. Login now