Patch #43364 ยป project_selector_hierarchy_sync.patch
app/helpers/application_helper.rb | ||
---|---|---|
473 | 473 |
end |
474 | 474 |
classes = (ancestors.empty? ? 'root' : 'child') |
475 | 475 |
classes += ' archived' if project.archived? |
476 |
s << "<li class='#{classes}'><div class='#{classes}'>" |
|
476 |
s << "<li class='#{classes}' data-project-id='#{project.id}'><div class='#{classes}'>"
|
|
477 | 477 |
s << h(block ? capture(project, &block) : project.name) |
478 | 478 |
s << "</div>\n" |
479 | 479 |
ancestors << project |
app/javascript/controllers/project_selector_controller.js | ||
---|---|---|
1 |
import { Controller } from "@hotwired/stimulus"; |
|
2 | ||
3 |
export default class extends Controller { |
|
4 |
toggleChildProjects(event) { |
|
5 |
const checkbox = event.target; |
|
6 |
if (checkbox.type !== 'checkbox') return; |
|
7 | ||
8 |
const listItem = checkbox.closest('li'); |
|
9 |
if (!listItem) return; |
|
10 | ||
11 |
const childList = listItem.querySelector('ul.projects'); |
|
12 |
if (!childList) return; |
|
13 | ||
14 |
const childCheckboxes = childList.querySelectorAll('input[type="checkbox"]'); |
|
15 | ||
16 |
childCheckboxes.forEach(child => { |
|
17 |
child.checked = checkbox.checked; |
|
18 |
}); |
|
19 |
} |
|
20 |
} |
app/views/custom_fields/_visibility_by_project_selector.html.erb | ||
---|---|---|
1 | 1 |
<fieldset class="box"><legend><%= toggle_checkboxes_link("#custom_field_project_ids input[type=checkbox]:enabled") %><%= l(:label_project_plural) %></legend> |
2 | 2 |
<p><%= f.check_box :is_for_all, :data => {:disables => '#custom_field_project_ids input'} %></p> |
3 | 3 | |
4 |
<div id="custom_field_project_ids"> |
|
4 |
<div id="custom_field_project_ids" data-controller="project-selector">
|
|
5 | 5 |
<% project_ids = @custom_field.project_ids.to_a %> |
6 | 6 |
<%= render_project_nested_lists(Project.all) do |p| |
7 |
content_tag('label', check_box_tag('custom_field[project_ids][]', p.id, project_ids.include?(p.id), :id => nil) + ' ' + p.to_s) |
|
7 |
content_tag('label', check_box_tag('custom_field[project_ids][]', p.id, project_ids.include?(p.id), :id => nil, :data => {:action => 'change->project-selector#toggleChildProjects'}) + ' ' + p.to_s)
|
|
8 | 8 |
end %> |
9 | 9 |
<%= hidden_field_tag('custom_field[project_ids][]', '', :id => nil) %> |
10 | 10 |
</div> |
11 |
</fieldset> |
|
11 |
</fieldset> |
test/system/custom_field_project_selector_test.rb | ||
---|---|---|
1 |
# frozen_string_literal: true |
|
2 | ||
3 |
require_relative '../application_system_test_case' |
|
4 | ||
5 |
class CustomFieldProjectSelectorTest < ApplicationSystemTestCase |
|
6 |
setup do |
|
7 |
@parent = Project.generate!(name: 'Parent Project') |
|
8 |
@child1 = Project.generate!(name: 'Child Project 1', parent_id: @parent.id) |
|
9 |
@child2 = Project.generate!(name: 'Child Project 2', parent_id: @parent.id) |
|
10 |
@grandchild = Project.generate!(name: 'Grandchild Project', parent_id: @child1.id) |
|
11 |
end |
|
12 | ||
13 |
def test_checking_parent_project_should_check_child_projects |
|
14 |
log_user('admin', 'admin') |
|
15 | ||
16 |
custom_field = IssueCustomField.generate!( |
|
17 |
field_format: 'string', |
|
18 |
name: 'Test Field', |
|
19 |
is_for_all: false |
|
20 |
) |
|
21 | ||
22 |
visit "/custom_fields/#{custom_field.id}/edit" |
|
23 | ||
24 |
within '#custom_field_project_ids' do |
|
25 |
parent_checkbox = find("input[value='#{@parent.id}']") |
|
26 |
parent_checkbox.click |
|
27 | ||
28 |
child1_checkbox = find("input[value='#{@child1.id}']") |
|
29 |
child2_checkbox = find("input[value='#{@child2.id}']") |
|
30 | ||
31 |
assert child1_checkbox.checked?, 'Child project 1 should be checked' |
|
32 |
assert child2_checkbox.checked?, 'Child project 2 should be checked' |
|
33 |
end |
|
34 |
end |
|
35 | ||
36 |
def test_unchecking_parent_project_should_uncheck_child_projects |
|
37 |
log_user('admin', 'admin') |
|
38 | ||
39 |
custom_field = IssueCustomField.generate!( |
|
40 |
field_format: 'string', |
|
41 |
name: 'Test Field', |
|
42 |
is_for_all: false, |
|
43 |
project_ids: [@parent.id, @child1.id, @child2.id] |
|
44 |
) |
|
45 | ||
46 |
visit "/custom_fields/#{custom_field.id}/edit" |
|
47 | ||
48 |
within '#custom_field_project_ids' do |
|
49 |
parent_checkbox = find("input[value='#{@parent.id}']") |
|
50 |
child1_checkbox = find("input[value='#{@child1.id}']") |
|
51 |
child2_checkbox = find("input[value='#{@child2.id}']") |
|
52 | ||
53 |
assert parent_checkbox.checked?, 'Parent should be checked initially' |
|
54 |
assert child1_checkbox.checked?, 'Child 1 should be checked initially' |
|
55 |
assert child2_checkbox.checked?, 'Child 2 should be checked initially' |
|
56 | ||
57 |
parent_checkbox.click |
|
58 | ||
59 |
assert !child1_checkbox.checked?, 'Child project 1 should be unchecked' |
|
60 |
assert !child2_checkbox.checked?, 'Child project 2 should be unchecked' |
|
61 |
end |
|
62 |
end |
|
63 | ||
64 |
def test_grandchild_projects_are_also_synced |
|
65 |
log_user('admin', 'admin') |
|
66 | ||
67 |
custom_field = IssueCustomField.generate!( |
|
68 |
field_format: 'string', |
|
69 |
name: 'Test Field', |
|
70 |
is_for_all: false |
|
71 |
) |
|
72 | ||
73 |
visit "/custom_fields/#{custom_field.id}/edit" |
|
74 | ||
75 |
within '#custom_field_project_ids' do |
|
76 |
parent_checkbox = find("input[value='#{@parent.id}']") |
|
77 |
parent_checkbox.click |
|
78 | ||
79 |
grandchild_checkbox = find("input[value='#{@grandchild.id}']") |
|
80 |
assert grandchild_checkbox.checked?, 'Grandchild project should be checked' |
|
81 |
end |
|
82 |
end |
|
83 |
end |