Project

General

Profile

Feature #31322 » 20191218-03-close-all-open-subtasks.diff

Toshi MARUYAMA, 2019-12-19 03:03

View differences:

app/controllers/issues_controller.rb
184 184
    if request.xhr?
185 185
      if params[:check_go_to_close_confirm]
186 186
        result = true
187
        status = nil
187 188
        status_id = params[:status_id].to_i
189
        can_close_all_descendants = true
188 190
        if status_id <= 0
189 191
          result = false
190 192
        else
......
192 194
          if !status.is_closed
193 195
            result = false
194 196
          else
195
            result = !@issue.descendants.open.empty?
197
            opened_descendants = []
198
            @issue.descendants.open.sort_by(&:lft).each do |issue|
199
              unless issue.visible?
200
                can_close_all_descendants = false
201
                break
202
              end
203

  
204
              unless issue.new_statuses_allowed_to.map{|i| i.id}.include?(status.id)
205
                can_close_all_descendants = false
206
                break
207
              end
208

  
209
              opened_descendants << issue
210
            end
211
            if can_close_all_descendants && opened_descendants.empty?
212
              result = false
213
              can_close_all_descendants = false
214
            end
215
          end
216
          if result
217
            session[:can_close_descendant] = {}
218
            session[:can_close_descendant][:can_close_all] = can_close_all_descendants
219
            if can_close_all_descendants
220
              session[:can_close_descendant][:status_id] = status.id
221
              session[:can_close_descendant][:status_name] = status.name
222
              session[:can_close_descendant][:ids] = opened_descendants.map{|i| i.id}
223
            end
196 224
          end
197 225
        end
198 226
        render :json => {:result => result}
199 227
        return
200 228
      end
201 229

  
230
      if !session.has_key?(:can_close_descendant)
231
        return
232
      end
233

  
234
      render_data = {
235
          :status_name => session[:can_close_descendant][:status_name],
236
          :can_close_all => session[:can_close_descendant][:can_close_all]
237
      }
202 238
      render(
203 239
        :partial => 'close_confirm',
204 240
        :layout => false,
205
        :locals => {}
241
        :locals => render_data
206 242
      )
207 243
      return
208 244
    end
......
226 262

  
227 263
    if saved
228 264
      render_attachment_warning_if_needed(@issue)
265
      if params[:close_descendants] && session.has_key?(:can_close_descendant)
266
        close_descendant_ids = session[:can_close_descendant][:ids]
267
        session.delete(:can_close_descendant)
268
        close_descendant_ids.each do |id|
269
          issue = Issue.find(id)
270
          issue.init_journal(User.current, params[:issue][:notes])
271
          issue.safe_attributes = {'status_id' => params[:issue][:status_id].to_i}
272
          issue.save
273
        end
274
      end
275

  
229 276
      unless @issue.current_journal.new_record? || params[:no_flash]
230 277
        flash[:notice] = l(:notice_successful_update)
231 278
      end
app/views/issues/_close_confirm.js.erb
2 2
  '<%= escape_javascript(
3 3
         render(
4 4
           :partial => 'close_confirm_dialog',
5
           :locals  => {}
5
           :locals  => {
6
             :status_name => status_name,
7
             :can_close_all => can_close_all
8
           }
6 9
         )
7 10
       )
8 11
   %>'
app/views/issues/_close_confirm_dialog.html.erb
1 1
<h3 class="title">&nbsp;</h3>
2
<p>
3
  <%= l(:text_close_parent_issue_whose_subtasks_are_open_confirmation) %>
4
</p>
2
<% if can_close_all %>
3
  <p>
4
    <%= l(:text_close_parent_issue_and_whose_subtasks_are_open,
5
          :issue_id => params[:id]) %>
6
  </p>
7
  <p>
8
    <label class="block">
9
      <%= radio_button_tag 'choice', '1' %>
10
      <%= l(:text_close_parent_issue_and_whose_subtasks_are_open_choice_close_also_all_subtasks,
11
            :status => status_name) %>
12
    </label>
13
    <label class="block">
14
      <%= radio_button_tag 'choice', '2', true %>
15
      <%= l(:text_close_parent_issue_and_whose_subtasks_are_open_choice_close_only_parent,
16
            :issue_id => params[:id]) %>
17
    </label>
18
  </p>
19
<% else %>
20
  <p>
21
    <%= l(:text_close_parent_issue_whose_subtasks_are_open_confirmation) %>
22
  </p>
23
<% end %>
5 24
<p class="buttons">
6 25
  <%= button_tag l(:button_apply), :type => "button", :onclick => "run_submit();" %>
7 26
  <%= link_to_function l(:button_cancel), "hideModal(this);" %>
......
9 28

  
10 29
<%= javascript_tag do %>
11 30
  function run_submit() {
31
    var canCloseAll = <%= can_close_all %>;
32
    if (canCloseAll) {
33
      var radioVal = $("input[name='choice']:checked").val();
34
      if (radioVal == '1') {
35
        $('<input />').attr('type', 'hidden')
36
            .attr('name', "close_descendants")
37
            .appendTo('#issue-form');
38
      }
39
    }
12 40
    $("#issue-form").off('submit');
13 41
    $("#issue-form").submit();
14 42
  }
config/locales/en.yml
1191 1191
  text_issues_destroy_confirmation: 'Are you sure you want to delete the selected issue(s)?'
1192 1192
  text_issues_destroy_descendants_confirmation: "This will also delete %{count} subtask(s)."
1193 1193
  text_close_parent_issue_whose_subtasks_are_open_confirmation: Are you sure you want to close parent issue whose subtasks are open?
1194
  text_close_parent_issue_and_whose_subtasks_are_open: Issue %{issue_id} has open subtasks.
1195
  text_close_parent_issue_and_whose_subtasks_are_open_choice_close_also_all_subtasks: Close all open subtasks by "%{status}" status too
1196
  text_close_parent_issue_and_whose_subtasks_are_open_choice_close_only_parent: Close only issue %{issue_id}
1194 1197
  text_time_entries_destroy_confirmation: 'Are you sure you want to delete the selected time entr(y/ies)?'
1195 1198
  text_select_project_modules: 'Select modules to enable for this project:'
1196 1199
  text_default_administrator_account_changed: Default administrator account changed
test/system/issues_test.rb
237 237
  test "add confirm dialog to issue submit button" do
238 238
    parent = Issue.generate!(:project_id => 1)
239 239
    child = Issue.generate!(:project_id => 1, :parent_issue_id => parent.id)
240
    hidden_child =
241
      Issue.
242
        generate!(
243
          :project_id => 1, :parent_issue_id => parent.id,
244
          :author_id => 2,
245
          :is_private => true
246
        )
247
    assert_not hidden_child.visible?(User.find(3))
240 248

  
241 249
    with_settings :close_parent_issue_whose_subtasks_are_open => 1 do
242 250
      log_user('dlopper', 'foo')
......
312 320
        end
313 321
      end
314 322

  
323
      hidden_child.status_id = 5
324
      hidden_child.save!
325

  
315 326
      visit "/issues/#{parent.id}"
316 327
      page.first(:link, 'Edit').click
317 328
      assert page.has_select?("issue_status_id", {:selected => "New"})
test/functional/issues_controller_test.rb
6241 6241
    assert_equal 'application/json', response.content_type
6242 6242
    json = ActiveSupport::JSON.decode(response.body)
6243 6243
    assert_equal({"result" => false}, json)
6244
    assert_not session.has_key?(:can_close_descendant)
6244 6245
  end
6245 6246

  
6246 6247
  test "check_go_to_close_confirm returns false if status_id is 0" do
......
6262 6263
    assert_equal 'application/json', response.content_type
6263 6264
    json = ActiveSupport::JSON.decode(response.body)
6264 6265
    assert_equal({"result" => false}, json)
6266
    assert_not session.has_key?(:can_close_descendant)
6265 6267
  end
6266 6268

  
6267 6269
  test "check_go_to_close_confirm returns false if issue does not have child" do
......
6285 6287

  
6286 6288
    assert_equal 1, issue.reload.status.id
6287 6289
    assert_equal({"result" => false}, json)
6290
    assert_not session.has_key?(:can_close_descendant)
6288 6291
  end
6289 6292

  
6290 6293
  test "check_go_to_close_confirm returns true if issue have open child" do
......
6312 6315

  
6313 6316
    assert_equal 1, parent.reload.status.id
6314 6317
    assert_equal({"result" => true}, json)
6318
    assert session.has_key?(:can_close_descendant)
6315 6319
  end
6316 6320

  
6317 6321
  test "check_go_to_close_confirm returns false if child is closed" do
......
6343 6347

  
6344 6348
    assert_equal 1, parent.reload.status.id
6345 6349
    assert_equal({"result" => false}, json)
6350
    assert_not session.has_key?(:can_close_descendant)
6346 6351
  end
6347 6352

  
6348 6353
  test "check_go_to_close_confirm returns true if child is open and not visible" do
......
6388 6393

  
6389 6394
    assert_equal 1, parent.reload.status.id
6390 6395
    assert_equal({"result" => true}, json)
6396
    assert session.has_key?(:can_close_descendant)
6397
    assert_not session[:can_close_descendant][:can_close_all]
6391 6398
  end
6392 6399

  
6393 6400
  def test_get_bulk_edit
test/functional/issues_controller_test.rb
6397 6397
    assert_not session[:can_close_descendant][:can_close_all]
6398 6398
  end
6399 6399

  
6400
  test "close all descendants which are open" do
6401
    with_settings :close_parent_issue_whose_subtasks_are_open => 1 do
6402
      parent = Issue.generate!
6403
      child =
6404
        Issue.
6405
         generate!(
6406
           :parent_issue_id => parent.id
6407
         )
6408
      grandchild1 =
6409
        Issue.
6410
          generate!(
6411
            :parent_issue_id => child.id
6412
          )
6413
      grandchild2 =
6414
        Issue.
6415
         generate!(
6416
           :parent_issue_id => child.id,
6417
           :status_id => 6
6418
         )
6419
      user = User.find(2)
6420
      assert parent.reload.visible?(user)
6421
      assert_not parent.closed?
6422
      assert child.reload.visible?(user)
6423
      assert_not child.closed?
6424
      assert grandchild1.reload.visible?(user)
6425
      assert_not grandchild1.closed?
6426
      assert grandchild2.reload.visible?(user)
6427
      assert grandchild2.closed?
6428

  
6429
      assert [parent, child, grandchild1, grandchild2].
6430
               map{|i| i.new_statuses_allowed_to(user)}.
6431
                 reduce(:&).map{|i| i.id}.include?(5)
6432
      @request.session[:user_id] = user.id
6433
      put(
6434
        :update,
6435
        :params => {
6436
          :id => parent.id,
6437
          :check_go_to_close_confirm => "",
6438
          :status_id => 5
6439
        },
6440
        :xhr => true
6441
      )
6442
      assert_response :success
6443
      assert_equal 'application/json', response.content_type
6444
      json = ActiveSupport::JSON.decode(response.body)
6445

  
6446
      assert_equal 1, parent.reload.status.id
6447
      assert_equal({"result" => true}, json)
6448
      assert session.has_key?(:can_close_descendant)
6449
      assert session[:can_close_descendant][:can_close_all]
6450
      assert_equal [child.id, grandchild1.id], session[:can_close_descendant][:ids] 
6451

  
6452
      notes = 'close all'
6453

  
6454
      assert_difference(
6455
        -> {parent.journals.count} => 1,
6456
        -> {child.journals.count} => 1,
6457
        -> {grandchild1.journals.count} => 1,
6458
        -> {grandchild2.journals.count} => 0
6459
      ) do
6460
        put(
6461
          :update,
6462
          :params => {
6463
            :id => parent.id,
6464
            :close_descendants => "",
6465
            :issue => {
6466
              :status_id => 5,
6467
              :notes => notes
6468
            }
6469
          }
6470
        )
6471
        assert_response 302
6472
      end
6473
      assert 5, parent.reload.status.id
6474
      assert_equal notes, parent.journals.last.notes
6475
      assert 5, child.reload.status.id
6476
      assert_equal notes, child.journals.last.notes
6477
      assert 5, grandchild1.reload.status.id
6478
      assert_equal notes, grandchild1.journals.last.notes
6479
      assert 6, grandchild2.reload.status.id
6480
    end
6481
  end
6482

  
6400 6483
  def test_get_bulk_edit
6401 6484
    @request.session[:user_id] = 2
6402 6485
    get(:bulk_edit, :params => {:ids => [1, 3]})
test/functional/issues_controller_test.rb
6480 6480
    end
6481 6481
  end
6482 6482

  
6483
  test "session generated by check_go_to_close_confirm should respect subtask statuses" do
6484
    with_settings :close_parent_issue_whose_subtasks_are_open => 1 do
6485
      WorkflowTransition.delete_all
6486
      WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
6487
                                 :old_status_id => 1, :new_status_id => 5)
6488
      WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
6489
                                 :old_status_id => 1, :new_status_id => 6)
6490
      WorkflowTransition.create!(:role_id => 1, :tracker_id => 2,
6491
                                 :old_status_id => 1, :new_status_id => 6)
6492
      user = User.find(2)
6493
      parent =
6494
        Issue.
6495
          generate!(
6496
            :author_id => 2,
6497
            :tracker_id => 1,
6498
            :status_id => 1
6499
          )
6500
     child1 =
6501
       Issue.
6502
         generate!(
6503
           :author_id => 2,
6504
           :parent_issue_id => parent.id,
6505
           :tracker_id => 1,
6506
           :status_id => 1
6507
         )
6508
     child2 =
6509
       Issue.
6510
         generate!(
6511
           :author_id => 2,
6512
           :parent_issue_id => parent.id,
6513
           :tracker_id => 2,
6514
           :status_id => 1
6515
         )
6516
      assert parent.reload.visible?(user)
6517
      assert child1.reload.visible?(user)
6518
      assert child2.reload.visible?(user)
6519

  
6520
      assert [5, 6].all? do |id|
6521
        parent.new_statuses_allowed_to(user).map{|s| s.id}.include?(id)
6522
      end
6523
      assert [5, 6].all? do |id|
6524
        child1.new_statuses_allowed_to(user).map{|s| s.id}.include?(id)
6525
      end
6526
      assert_not child2.new_statuses_allowed_to(user).map{|s| s.id}.include?(5)
6527
      assert child2.new_statuses_allowed_to(user).map{|s| s.id}.include?(6)
6528

  
6529
      @request.session[:user_id] = user.id
6530
      put(
6531
        :update,
6532
        :params => {
6533
          :id => parent.id,
6534
          :check_go_to_close_confirm => "",
6535
          :status_id => 6
6536
        },
6537
        :xhr => true
6538
      )
6539
      assert_response :success
6540
      assert_equal 'application/json', response.content_type
6541
      json = ActiveSupport::JSON.decode(response.body)
6542

  
6543
      assert_equal 1, parent.reload.status.id
6544
      assert_equal({"result" => true}, json)
6545
      assert session.has_key?(:can_close_descendant)
6546
      assert session[:can_close_descendant][:can_close_all]
6547
      assert_equal [child1.id, child2.id], session[:can_close_descendant][:ids] 
6548

  
6549
      @request.session.delete(:can_close_descendant)
6550

  
6551
      @request.session[:user_id] = user.id
6552
      put(
6553
        :update,
6554
        :params => {
6555
          :id => parent.id,
6556
          :check_go_to_close_confirm => "",
6557
          :status_id => 5
6558
        },
6559
        :xhr => true
6560
      )
6561
      assert_response :success
6562
      assert_equal 'application/json', response.content_type
6563
      json = ActiveSupport::JSON.decode(response.body)
6564

  
6565
      assert_equal 1, parent.reload.status.id
6566
      assert_equal({"result" => true}, json)
6567
      assert session.has_key?(:can_close_descendant)
6568
      assert_not session[:can_close_descendant][:can_close_all]
6569
      assert_not session[:can_close_descendant].has_key?(:ids)
6570
    end
6571
  end
6572

  
6483 6573
  def test_get_bulk_edit
6484 6574
    @request.session[:user_id] = 2
6485 6575
    get(:bulk_edit, :params => {:ids => [1, 3]})
test/system/issues_test.rb
337 337
    end
338 338
  end
339 339

  
340
  test "close all open subtasks" do
341
    parent = Issue.generate!(:project_id => 1)
342
    child = Issue.generate!(:project_id => 1, :parent_issue_id => parent.id)
343
    close_only_text = "Close only issue #{parent.id}"
344

  
345
    with_settings :close_parent_issue_whose_subtasks_are_open => 1 do
346
      log_user('dlopper', 'foo')
347
      visit "/issues/#{parent.id}"
348
      page.first(:link, 'Edit').click
349
      assert page.has_select?("issue_status_id", {:selected => "New"})
350
      page.find("#issue_status_id").select("Closed")
351
      assert_no_difference ['Issue.count', 'child.journals.count'] do
352
        assert_no_difference 'parent.journals.count' do
353
          page.first(:button, 'Submit').click
354
          within('#ajax-modal') do
355
            assert page.has_text?(/has open subtasks/)
356
            assert page.has_checked_field?(close_only_text)
357
            page.first(:link, 'Cancel').click
358
          end
359
          assert_equal 1, parent.reload.status.id
360
        end
361
        assert_difference 'parent.journals.count' do
362
          page.first(:button, 'Submit').click
363
          within('#ajax-modal') do
364
            assert page.has_text?(/has open subtasks/)
365
            assert page.has_checked_field?(close_only_text)
366
            page.first(:button, 'Apply').click
367
          end
368
          assert page.has_css?('#flash_notice')
369
          assert_equal 5, parent.reload.status.id
370
        end
371
      end
372
      page.first(:link, 'Edit').click
373
      assert page.has_select?("issue_status_id", {:selected => "Closed"})
374
      page.find("#issue_status_id").select("New")
375
      assert_no_difference ['Issue.count', 'child.journals.count'] do
376
        assert_difference 'parent.journals.count' do
377
          page.first(:button, 'Submit').click
378
          assert page.has_css?('#flash_notice')
379
          assert_equal 1, parent.reload.status.id
380
        end
381
      end
382
      page.first(:link, 'Edit').click
383
      assert page.has_select?("issue_status_id", {:selected => "New"})
384
      page.find("#issue_status_id").select("Closed")
385
      assert_no_difference 'Issue.count' do
386
        assert_difference ['parent.journals.count', 'child.journals.count'] do
387
          page.first(:button, 'Submit').click
388
          within('#ajax-modal') do
389
            all_text = 'Close all open subtasks by "Closed" status'
390
            assert page.has_text?(/has open subtasks/)
391
            assert page.has_checked_field?(close_only_text)
392
            page.choose(all_text)
393
            assert page.has_checked_field?(all_text)
394
            page.first(:button, 'Apply').click
395
          end
396
          assert page.has_css?('#flash_notice')
397
          assert_equal 5, parent.reload.status.id
398
          assert_equal 5, child.reload.status.id
399
        end
400
      end
401
    end
402
  end
403

  
340 404
  test "removing issue shows confirm dialog" do
341 405
    log_user('jsmith', 'jsmith')
342 406
    visit '/issues/1'
(10-10/15)