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