# HG changeset patch
# User Toshi MARUYAMA
# Date 1575717833 -32400
# Sat Dec 07 20:23:53 2019 +0900
# Branch issue-28492-01
# Node ID dc7b926e29138ffa82ffe8bc143799b93197aa8a
# Parent 4079f1eae9f2cf89f6b7209435daccc5433e96d7
full implement (#31322)
diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb
--- a/app/controllers/issues_controller.rb
+++ b/app/controllers/issues_controller.rb
@@ -184,7 +184,9 @@ class IssuesController < ApplicationCont
if request.xhr?
if params[:check_go_to_close_confirm]
result = true
+ status = nil
status_id = params[:status_id].to_i
+ can_close_all_descendants = true
if status_id <= 0
result = false
else
@@ -192,17 +194,51 @@ class IssuesController < ApplicationCont
if !status.is_closed
result = false
else
- result = !@issue.descendants.open.empty?
+ opened_descendants = []
+ @issue.descendants.open.sort_by(&:lft).each do |issue|
+ unless issue.visible?
+ can_close_all_descendants = false
+ break
+ end
+
+ unless issue.new_statuses_allowed_to.map{|i| i.id}.include?(status.id)
+ can_close_all_descendants = false
+ break
+ end
+
+ opened_descendants << issue
+ end
+ if can_close_all_descendants && opened_descendants.empty?
+ result = false
+ can_close_all_descendants = false
+ end
+ end
+ if result
+ session[:can_close_descendant] = {}
+ session[:can_close_descendant][:can_close_all] = can_close_all_descendants
+ if can_close_all_descendants
+ session[:can_close_descendant][:status_id] = status.id
+ session[:can_close_descendant][:status_name] = status.name
+ session[:can_close_descendant][:ids] = opened_descendants.map{|i| i.id}
+ end
end
end
render :json => {:result => result}
return
end
+ if !session.has_key?(:can_close_descendant)
+ return
+ end
+
+ render_data = {
+ :status_name => session[:can_close_descendant][:status_name],
+ :can_close_all => session[:can_close_descendant][:can_close_all]
+ }
render(
:partial => 'close_confirm',
:layout => false,
- :locals => {}
+ :locals => render_data
)
return
end
@@ -226,6 +262,17 @@ class IssuesController < ApplicationCont
if saved
render_attachment_warning_if_needed(@issue)
+ if params[:close_descendants] && session.has_key?(:can_close_descendant)
+ close_descendant_ids = session[:can_close_descendant][:ids]
+ session.delete(:can_close_descendant)
+ close_descendant_ids.each do |id|
+ issue = Issue.find(id)
+ issue.init_journal(User.current, params[:issue][:notes])
+ issue.safe_attributes = {'status_id' => params[:issue][:status_id].to_i}
+ issue.save
+ end
+ end
+
unless @issue.current_journal.new_record? || params[:no_flash]
flash[:notice] = l(:notice_successful_update)
end
diff --git a/app/views/issues/_close_confirm.js.erb b/app/views/issues/_close_confirm.js.erb
--- a/app/views/issues/_close_confirm.js.erb
+++ b/app/views/issues/_close_confirm.js.erb
@@ -2,7 +2,10 @@
'<%= escape_javascript(
render(
:partial => 'close_confirm_dialog',
- :locals => {}
+ :locals => {
+ :status_name => status_name,
+ :can_close_all => can_close_all
+ }
)
)
%>'
diff --git a/app/views/issues/_close_confirm_dialog.html.erb b/app/views/issues/_close_confirm_dialog.html.erb
--- a/app/views/issues/_close_confirm_dialog.html.erb
+++ b/app/views/issues/_close_confirm_dialog.html.erb
@@ -1,7 +1,26 @@
-
- <%= l(:text_close_parent_issue_whose_subtasks_are_open_confirmation) %>
-
+<% if can_close_all %>
+
+ <%= l(:text_close_parent_issue_and_whose_subtasks_are_open,
+ :issue_id => params[:id]) %>
+
+
+
+
+
+<% else %>
+
+ <%= l(:text_close_parent_issue_whose_subtasks_are_open_confirmation) %>
+
+<% end %>
<%= button_tag l(:button_apply), :type => "button", :onclick => "run_submit();" %>
<%= link_to_function l(:button_cancel), "hideModal(this);" %>
@@ -9,6 +28,15 @@
<%= javascript_tag do %>
function run_submit() {
+ var canCloseAll = <%= can_close_all %>;
+ if (canCloseAll) {
+ var radioVal = $("input[name='choice']:checked").val();
+ if (radioVal == '1') {
+ $('').attr('type', 'hidden')
+ .attr('name', "close_descendants")
+ .appendTo('#issue-form');
+ }
+ }
$("#issue-form").off('submit');
$("#issue-form").submit();
}
diff --git a/config/locales/en.yml b/config/locales/en.yml
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1191,6 +1191,9 @@ en:
text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
text_close_parent_issue_whose_subtasks_are_open_confirmation: Are you sure you want to close parent issue whose subtasks are open?
+ text_close_parent_issue_and_whose_subtasks_are_open: Issue %{issue_id} has open subtasks.
+ text_close_parent_issue_and_whose_subtasks_are_open_choice_close_also_all_subtasks: Close all open subtasks by "%{status}" status too
+ text_close_parent_issue_and_whose_subtasks_are_open_choice_close_only_parent: Close only issue %{issue_id}
text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
text_select_project_modules: 'Select modules to enable for this project:'
text_default_administrator_account_changed: Default administrator account changed
diff --git a/test/system/issues_test.rb b/test/system/issues_test.rb
--- a/test/system/issues_test.rb
+++ b/test/system/issues_test.rb
@@ -237,6 +237,14 @@ class IssuesSystemTest < ApplicationSyst
test "add confirm dialog to issue submit button" do
parent = Issue.generate!(:project_id => 1)
child = Issue.generate!(:project_id => 1, :parent_issue_id => parent.id)
+ hidden_child =
+ Issue.
+ generate!(
+ :project_id => 1, :parent_issue_id => parent.id,
+ :author_id => 2,
+ :is_private => true
+ )
+ assert_not hidden_child.visible?(User.find(3))
with_settings :close_parent_issue_whose_subtasks_are_open => 1 do
log_user('dlopper', 'foo')
@@ -312,6 +320,9 @@ class IssuesSystemTest < ApplicationSyst
end
end
+ hidden_child.status_id = 5
+ hidden_child.save!
+
visit "/issues/#{parent.id}"
page.first(:link, 'Edit').click
assert page.has_select?("issue_status_id", {:selected => "New"})
# HG changeset patch
# User Toshi MARUYAMA
# Date 1576126547 -32400
# Thu Dec 12 13:55:47 2019 +0900
# Branch issue-28492-01
# Node ID cd2817a65d862c8e86c939903a63189f5a1309d0
# Parent dc7b926e29138ffa82ffe8bc143799b93197aa8a
add session tests to existed functional tests (#31322)
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -6241,6 +6241,7 @@ class IssuesControllerTest < Redmine::Co
assert_equal 'application/json', response.content_type
json = ActiveSupport::JSON.decode(response.body)
assert_equal({"result" => false}, json)
+ assert_not session.has_key?(:can_close_descendant)
end
test "check_go_to_close_confirm returns false if status_id is 0" do
@@ -6262,6 +6263,7 @@ class IssuesControllerTest < Redmine::Co
assert_equal 'application/json', response.content_type
json = ActiveSupport::JSON.decode(response.body)
assert_equal({"result" => false}, json)
+ assert_not session.has_key?(:can_close_descendant)
end
test "check_go_to_close_confirm returns false if issue does not have child" do
@@ -6285,6 +6287,7 @@ class IssuesControllerTest < Redmine::Co
assert_equal 1, issue.reload.status.id
assert_equal({"result" => false}, json)
+ assert_not session.has_key?(:can_close_descendant)
end
test "check_go_to_close_confirm returns true if issue have open child" do
@@ -6312,6 +6315,7 @@ class IssuesControllerTest < Redmine::Co
assert_equal 1, parent.reload.status.id
assert_equal({"result" => true}, json)
+ assert session.has_key?(:can_close_descendant)
end
test "check_go_to_close_confirm returns false if child is closed" do
@@ -6343,6 +6347,7 @@ class IssuesControllerTest < Redmine::Co
assert_equal 1, parent.reload.status.id
assert_equal({"result" => false}, json)
+ assert_not session.has_key?(:can_close_descendant)
end
test "check_go_to_close_confirm returns true if child is open and not visible" do
@@ -6388,6 +6393,8 @@ class IssuesControllerTest < Redmine::Co
assert_equal 1, parent.reload.status.id
assert_equal({"result" => true}, json)
+ assert session.has_key?(:can_close_descendant)
+ assert_not session[:can_close_descendant][:can_close_all]
end
def test_get_bulk_edit
# HG changeset patch
# User Toshi MARUYAMA
# Date 1576126547 -32400
# Thu Dec 12 13:55:47 2019 +0900
# Branch issue-28492-01
# Node ID 343f95dff315e580db08e1201cf3d1dd7fc04381
# Parent cd2817a65d862c8e86c939903a63189f5a1309d0
add "close all descendants which are open" test (#31322)
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -6397,6 +6397,89 @@ class IssuesControllerTest < Redmine::Co
assert_not session[:can_close_descendant][:can_close_all]
end
+ test "close all descendants which are open" do
+ with_settings :close_parent_issue_whose_subtasks_are_open => 1 do
+ parent = Issue.generate!
+ child =
+ Issue.
+ generate!(
+ :parent_issue_id => parent.id
+ )
+ grandchild1 =
+ Issue.
+ generate!(
+ :parent_issue_id => child.id
+ )
+ grandchild2 =
+ Issue.
+ generate!(
+ :parent_issue_id => child.id,
+ :status_id => 6
+ )
+ user = User.find(2)
+ assert parent.reload.visible?(user)
+ assert_not parent.closed?
+ assert child.reload.visible?(user)
+ assert_not child.closed?
+ assert grandchild1.reload.visible?(user)
+ assert_not grandchild1.closed?
+ assert grandchild2.reload.visible?(user)
+ assert grandchild2.closed?
+
+ assert [parent, child, grandchild1, grandchild2].
+ map{|i| i.new_statuses_allowed_to(user)}.
+ reduce(:&).map{|i| i.id}.include?(5)
+ @request.session[:user_id] = user.id
+ put(
+ :update,
+ :params => {
+ :id => parent.id,
+ :check_go_to_close_confirm => "",
+ :status_id => 5
+ },
+ :xhr => true
+ )
+ assert_response :success
+ assert_equal 'application/json', response.content_type
+ json = ActiveSupport::JSON.decode(response.body)
+
+ assert_equal 1, parent.reload.status.id
+ assert_equal({"result" => true}, json)
+ assert session.has_key?(:can_close_descendant)
+ assert session[:can_close_descendant][:can_close_all]
+ assert_equal [child.id, grandchild1.id], session[:can_close_descendant][:ids]
+
+ notes = 'close all'
+
+ assert_difference(
+ -> {parent.journals.count} => 1,
+ -> {child.journals.count} => 1,
+ -> {grandchild1.journals.count} => 1,
+ -> {grandchild2.journals.count} => 0
+ ) do
+ put(
+ :update,
+ :params => {
+ :id => parent.id,
+ :close_descendants => "",
+ :issue => {
+ :status_id => 5,
+ :notes => notes
+ }
+ }
+ )
+ assert_response 302
+ end
+ assert 5, parent.reload.status.id
+ assert_equal notes, parent.journals.last.notes
+ assert 5, child.reload.status.id
+ assert_equal notes, child.journals.last.notes
+ assert 5, grandchild1.reload.status.id
+ assert_equal notes, grandchild1.journals.last.notes
+ assert 6, grandchild2.reload.status.id
+ end
+ end
+
def test_get_bulk_edit
@request.session[:user_id] = 2
get(:bulk_edit, :params => {:ids => [1, 3]})
# HG changeset patch
# User Toshi MARUYAMA
# Date 1576126547 -32400
# Thu Dec 12 13:55:47 2019 +0900
# Branch issue-28492-01
# Node ID ed44c61959246f619426f2214e6a9371c3aa2cfc
# Parent 343f95dff315e580db08e1201cf3d1dd7fc04381
add "session generated by check_go_to_close_confirm should respect subtask statuses" functional test (#31322)
diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb
--- a/test/functional/issues_controller_test.rb
+++ b/test/functional/issues_controller_test.rb
@@ -6480,6 +6480,96 @@ class IssuesControllerTest < Redmine::Co
end
end
+ test "session generated by check_go_to_close_confirm should respect subtask statuses" do
+ with_settings :close_parent_issue_whose_subtasks_are_open => 1 do
+ WorkflowTransition.delete_all
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
+ :old_status_id => 1, :new_status_id => 5)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
+ :old_status_id => 1, :new_status_id => 6)
+ WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
+ :old_status_id => 1, :new_status_id => 6)
+ user = User.find(2)
+ parent =
+ Issue.
+ generate!(
+ :author_id => 2,
+ :tracker_id => 1,
+ :status_id => 1
+ )
+ child1 =
+ Issue.
+ generate!(
+ :author_id => 2,
+ :parent_issue_id => parent.id,
+ :tracker_id => 1,
+ :status_id => 1
+ )
+ child2 =
+ Issue.
+ generate!(
+ :author_id => 2,
+ :parent_issue_id => parent.id,
+ :tracker_id => 2,
+ :status_id => 1
+ )
+ assert parent.reload.visible?(user)
+ assert child1.reload.visible?(user)
+ assert child2.reload.visible?(user)
+
+ assert [5, 6].all? do |id|
+ parent.new_statuses_allowed_to(user).map{|s| s.id}.include?(id)
+ end
+ assert [5, 6].all? do |id|
+ child1.new_statuses_allowed_to(user).map{|s| s.id}.include?(id)
+ end
+ assert_not child2.new_statuses_allowed_to(user).map{|s| s.id}.include?(5)
+ assert child2.new_statuses_allowed_to(user).map{|s| s.id}.include?(6)
+
+ @request.session[:user_id] = user.id
+ put(
+ :update,
+ :params => {
+ :id => parent.id,
+ :check_go_to_close_confirm => "",
+ :status_id => 6
+ },
+ :xhr => true
+ )
+ assert_response :success
+ assert_equal 'application/json', response.content_type
+ json = ActiveSupport::JSON.decode(response.body)
+
+ assert_equal 1, parent.reload.status.id
+ assert_equal({"result" => true}, json)
+ assert session.has_key?(:can_close_descendant)
+ assert session[:can_close_descendant][:can_close_all]
+ assert_equal [child1.id, child2.id], session[:can_close_descendant][:ids]
+
+ @request.session.delete(:can_close_descendant)
+
+ @request.session[:user_id] = user.id
+ put(
+ :update,
+ :params => {
+ :id => parent.id,
+ :check_go_to_close_confirm => "",
+ :status_id => 5
+ },
+ :xhr => true
+ )
+ assert_response :success
+ assert_equal 'application/json', response.content_type
+ json = ActiveSupport::JSON.decode(response.body)
+
+ assert_equal 1, parent.reload.status.id
+ assert_equal({"result" => true}, json)
+ assert session.has_key?(:can_close_descendant)
+ assert_not session[:can_close_descendant][:can_close_all]
+ assert_not session[:can_close_descendant].has_key?(:ids)
+ end
+ end
+
def test_get_bulk_edit
@request.session[:user_id] = 2
get(:bulk_edit, :params => {:ids => [1, 3]})
# HG changeset patch
# User Toshi MARUYAMA
# Date 1576569171 -32400
# Tue Dec 17 16:52:51 2019 +0900
# Branch issue-28492-01
# Node ID db5ef39c6b15db4752155f4b07448a9d93a7f71e
# Parent ed44c61959246f619426f2214e6a9371c3aa2cfc
add "close all open subtasks" system test (#31322)
diff --git a/test/system/issues_test.rb b/test/system/issues_test.rb
--- a/test/system/issues_test.rb
+++ b/test/system/issues_test.rb
@@ -337,6 +337,70 @@ class IssuesSystemTest < ApplicationSyst
end
end
+ test "close all open subtasks" do
+ parent = Issue.generate!(:project_id => 1)
+ child = Issue.generate!(:project_id => 1, :parent_issue_id => parent.id)
+ close_only_text = "Close only issue #{parent.id}"
+
+ with_settings :close_parent_issue_whose_subtasks_are_open => 1 do
+ log_user('dlopper', 'foo')
+ visit "/issues/#{parent.id}"
+ page.first(:link, 'Edit').click
+ assert page.has_select?("issue_status_id", {:selected => "New"})
+ page.find("#issue_status_id").select("Closed")
+ assert_no_difference ['Issue.count', 'child.journals.count'] do
+ assert_no_difference 'parent.journals.count' do
+ page.first(:button, 'Submit').click
+ within('#ajax-modal') do
+ assert page.has_text?(/has open subtasks/)
+ assert page.has_checked_field?(close_only_text)
+ page.first(:link, 'Cancel').click
+ end
+ assert_equal 1, parent.reload.status.id
+ end
+ assert_difference 'parent.journals.count' do
+ page.first(:button, 'Submit').click
+ within('#ajax-modal') do
+ assert page.has_text?(/has open subtasks/)
+ assert page.has_checked_field?(close_only_text)
+ page.first(:button, 'Apply').click
+ end
+ assert page.has_css?('#flash_notice')
+ assert_equal 5, parent.reload.status.id
+ end
+ end
+ page.first(:link, 'Edit').click
+ assert page.has_select?("issue_status_id", {:selected => "Closed"})
+ page.find("#issue_status_id").select("New")
+ assert_no_difference ['Issue.count', 'child.journals.count'] do
+ assert_difference 'parent.journals.count' do
+ page.first(:button, 'Submit').click
+ assert page.has_css?('#flash_notice')
+ assert_equal 1, parent.reload.status.id
+ end
+ end
+ page.first(:link, 'Edit').click
+ assert page.has_select?("issue_status_id", {:selected => "New"})
+ page.find("#issue_status_id").select("Closed")
+ assert_no_difference 'Issue.count' do
+ assert_difference ['parent.journals.count', 'child.journals.count'] do
+ page.first(:button, 'Submit').click
+ within('#ajax-modal') do
+ all_text = 'Close all open subtasks by "Closed" status'
+ assert page.has_text?(/has open subtasks/)
+ assert page.has_checked_field?(close_only_text)
+ page.choose(all_text)
+ assert page.has_checked_field?(all_text)
+ page.first(:button, 'Apply').click
+ end
+ assert page.has_css?('#flash_notice')
+ assert_equal 5, parent.reload.status.id
+ assert_equal 5, child.reload.status.id
+ end
+ end
+ end
+ end
+
test "removing issue shows confirm dialog" do
log_user('jsmith', 'jsmith')
visit '/issues/1'