From 4c85804b058cc562028b69763d8cf779894ccfb7 Mon Sep 17 00:00:00 2001 From: MAEDA Go Date: Sat, 25 Apr 2026 14:53:47 +0900 Subject: [PATCH] Add an option to control issue assignment to groups --- app/models/group.rb | 2 + app/models/project.rb | 14 +++-- app/views/groups/_form.html.erb | 3 + config/locales/en.yml | 1 + config/locales/ja.yml | 1 + .../20260425000000_add_assignable_to_users.rb | 7 +++ test/functional/groups_controller_test.rb | 25 +++++--- test/functional/issues_controller_test.rb | 58 +++++++++++++++++++ test/unit/issue_test.rb | 20 +++++++ 9 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 db/migrate/20260425000000_add_assignable_to_users.rb diff --git a/app/models/group.rb b/app/models/group.rb index 300b59b46..13c449a7b 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -39,8 +39,10 @@ class Group < Principal scope :named, lambda {|arg| where("LOWER(#{table_name}.lastname) = LOWER(?)", arg.to_s.strip)} scope :givable, lambda {where(:type => 'Group')} + # users.assignable is currently interpreted only for Group assignees. safe_attributes( 'name', + 'assignable', 'twofa_required', 'user_ids', 'custom_field_values', diff --git a/app/models/project.rb b/app/models/project.rb index 5c832144a..b89b56c18 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -604,16 +604,22 @@ class Project < ApplicationRecord def assignable_users(tracker=nil) return @assignable_users[tracker] if @assignable_users && @assignable_users[tracker] - types = ['User'] - types << 'Group' if Setting.issue_group_assignment? - scope = Principal. active. joins(:members => :roles). - where(:type => types, :members => {:project_id => id}, :roles => {:assignable => true}). + where(:members => {:project_id => id}, :roles => {:assignable => true}). distinct. sorted + if Setting.issue_group_assignment? + # users.assignable is currently interpreted only for Group assignees. + scope = scope.where(:type => 'User').or( + scope.where(:type => 'Group', :assignable => true) + ) + else + scope = scope.where(:type => 'User') + end + if tracker # Rejects users that cannot the view the tracker roles = diff --git a/app/views/groups/_form.html.erb b/app/views/groups/_form.html.erb index 723b9f203..b2707cdd5 100644 --- a/app/views/groups/_form.html.erb +++ b/app/views/groups/_form.html.erb @@ -4,6 +4,9 @@

<%= f.text_field :name, :required => true, :size => 60, :disabled => !@group.safe_attribute?('name') %>

<% unless @group.builtin? %> + <% if Setting.issue_group_assignment? %> +

<%= f.check_box :assignable, :label => :field_group_issue_assignable %>

+ <% end %>

<%= f.check_box :twofa_required, disabled: !Setting.twofa_optional? %> <% if Setting.twofa_required? %> <%= l 'twofa_text_group_required' %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 8315e12ce..b52cefd35 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -416,6 +416,7 @@ en: field_full_width_layout: Full width layout field_digest: Checksum field_default_assigned_to: Default assignee + field_group_issue_assignable: Issues can be assigned to this group field_recently_used_projects: Number of recently used projects in jump box field_history_default_tab: Issue's history default tab field_unique_id: Unique ID diff --git a/config/locales/ja.yml b/config/locales/ja.yml index c12354b77..d551eb626 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1233,6 +1233,7 @@ ja: label_last_notes: 最新のコメント field_digest: チェックサム field_default_assigned_to: デフォルトの担当者 + field_group_issue_assignable: このグループにチケットを割り当て可能 setting_show_custom_fields_on_registration: アカウント登録画面でカスタムフィールドを表示 label_no_preview_alternative_html: このファイルはプレビューできません。 %{link} してください。 label_no_preview_download: ダウンロード diff --git a/db/migrate/20260425000000_add_assignable_to_users.rb b/db/migrate/20260425000000_add_assignable_to_users.rb new file mode 100644 index 000000000..621c2557a --- /dev/null +++ b/db/migrate/20260425000000_add_assignable_to_users.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddAssignableToUsers < ActiveRecord::Migration[8.1] + def change + add_column :users, :assignable, :boolean, :default => true, :null => false + end +end diff --git a/test/functional/groups_controller_test.rb b/test/functional/groups_controller_test.rb index a8407e8e8..7f9d88a26 100644 --- a/test/functional/groups_controller_test.rb +++ b/test/functional/groups_controller_test.rb @@ -166,20 +166,29 @@ class GroupsControllerTest < Redmine::ControllerTest assert_select 'div#tab-content-memberships a.icon-link-break', :text => 'Remove' end + def test_edit_should_hide_assignable_checkbox_when_group_assignment_is_disabled + with_settings :issue_group_assignment => '0' do + get(:edit, :params => {:id => 10}) + end + assert_response :success + assert_select 'input[name=?]', 'group[assignable]', :count => 0 + end + def test_update new_name = 'New name' - put( - :update, - :params => { - :id => 10, - :group => { - :name => new_name + with_settings :issue_group_assignment => '1' do + put( + :update, + :params => { + :id => 10, + :group => {:name => new_name, :assignable => '0'} } - } - ) + ) + end assert_redirected_to '/groups' group = Group.find(10) assert_equal new_name, group.name + assert_not Group.find(10).assignable? end def test_update_with_failure diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index b553bd411..85c35163b 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -4335,6 +4335,33 @@ class IssuesControllerTest < Redmine::ControllerTest assert_equal group, issue.assigned_to end + def test_post_new_with_non_assignable_group_assignment + group = Group.find(11) + group.update!(:assignable => false) + project = Project.find(1) + project.members << Member.new(:principal => group, :roles => [Role.givable.first]) + + with_settings :issue_group_assignment => '1' do + @request.session[:user_id] = 2 + assert_no_difference 'Issue.count' do + post( + :create, + :params => { + :project_id => project.id, + :issue => { + :tracker_id => 3, + :status_id => 1, + :subject => 'This is the test_new_with_non_assignable_group_assignment issue', + :assigned_to_id => group.id + } + } + ) + end + end + assert_response :success + assert_select_error /Assignee is invalid/i + end + def test_post_create_without_start_date_and_default_start_date_is_not_creation_date with_settings :default_issue_start_date_to_creation_date => 0 do @request.session[:user_id] = 2 @@ -7579,6 +7606,37 @@ class IssuesControllerTest < Redmine::ControllerTest end end + def test_bulk_update_with_non_assignable_group_assignee + group = Group.find(11) + group.update!(:assignable => false) + project = Project.find(1) + project.members << Member.new(:principal => group, :roles => [Role.givable.first]) + + @request.session[:user_id] = 2 + original_assignees = Issue.where(:id => [1, 2]).collect(&:assigned_to) + + with_settings :issue_group_assignment => '1' do + post( + :bulk_update, + :params => { + :ids => [1, 2], + :notes => 'Bulk editing', + :issue => { + :priority_id => '', + :assigned_to_id => group.id, + :custom_field_values => { + '2' => '' + } + } + } + ) + end + + assert_response :success + assert_equal original_assignees, Issue.where(:id => [1, 2]).collect(&:assigned_to) + assert_select_error /Assignee is invalid/i + end + def test_bulk_update_on_different_projects @request.session[:user_id] = 2 # update issues priority diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index a5c0bbb87..3ca67900e 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -2736,6 +2736,15 @@ class IssueTest < ActiveSupport::TestCase end end + test "#assignable_users with issue_group_assignment should exclude non-assignable groups" do + issue = Issue.new(:project => Project.find(2)) + Group.find(11).update!(:assignable => false) + + with_settings :issue_group_assignment => '1' do + assert_not_include Group.find(11), issue.assignable_users + end + end + def test_assignable_users_should_not_include_builtin_groups Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [1]) Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [1]) @@ -2759,6 +2768,17 @@ class IssueTest < ActiveSupport::TestCase assert_not_include user, issue2.assignable_users end + def test_assignable_users_should_include_the_current_group_assignee_even_if_group_is_no_longer_assignable + group = Group.generate! + Member.create!(:project_id => 1, :principal => group, :role_ids => [1]) + + with_settings :issue_group_assignment => '1' do + issue = Issue.generate!(:project_id => 1, :assigned_to => group) + group.update!(:assignable => false) + assert_include group, Issue.find(issue.id).assignable_users + end + end + def test_create_should_send_email_notification ActionMailer::Base.deliveries.clear issue = Issue.new(:project_id => 1, :tracker_id => 1, -- 2.50.1