@@ -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