From 588c83df008ac6ccc08a08ea39ed53cdd2ef1e65 Mon Sep 17 00:00:00 2001 From: MAEDA Go Date: Sun, 10 May 2026 18:05:01 +0900 Subject: [PATCH] Add tracker setting for private issues by default --- app/controllers/issues_controller.rb | 6 + app/models/issue.rb | 5 + app/models/tracker.rb | 1 + app/views/trackers/_form.html.erb | 1 + config/locales/en.yml | 1 + ...0000_add_private_by_default_to_trackers.rb | 5 + test/functional/issues_controller_test.rb | 114 ++++++++++++++++++ test/functional/trackers_controller_test.rb | 2 + test/integration/api_test/issues_test.rb | 12 ++ 9 files changed, 147 insertions(+) create mode 100644 db/migrate/20260510000000_add_private_by_default_to_trackers.rb diff --git a/app/controllers/issues_controller.rb b/app/controllers/issues_controller.rb index ef4b227e1..b0ba4bb90 100644 --- a/app/controllers/issues_controller.rb +++ b/app/controllers/issues_controller.rb @@ -630,6 +630,12 @@ class IssuesController < ApplicationController # so we can use the default version for the new project attrs.delete(:fixed_version_id) end + if action_name == 'new' && + %w[issue_project_id issue_tracker_id].include?(params[:form_update_triggered_by]) && + attrs[:is_private] != '1' + # Drop the unchecked value so the selected tracker's private default can be applied. + attrs.delete(:is_private) + end attrs[:assigned_to_id] = User.current.id if attrs[:assigned_to_id] == 'me' @issue.safe_attributes = attrs diff --git a/app/models/issue.rb b/app/models/issue.rb index a71b002ad..4c16fb6af 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -616,6 +616,11 @@ class Issue < ApplicationRecord if new_record? && !statuses_allowed.include?(status) self.status = statuses_allowed.first || default_status end + # Use the selected tracker's private default when the form has no explicit value. + if new_record? && tracker&.private_by_default? && + !attrs.key?('is_private') && safe_attribute?('is_private', user) + attrs['is_private'] = '1' + end if (u = attrs.delete('assigned_to_id')) && safe_attribute?('assigned_to_id') self.assigned_to_id = u end diff --git a/app/models/tracker.rb b/app/models/tracker.rb index f693d15f5..e33ec333c 100644 --- a/app/models/tracker.rb +++ b/app/models/tracker.rb @@ -74,6 +74,7 @@ class Tracker < ApplicationRecord 'name', 'default_status_id', 'is_in_roadmap', + 'private_by_default', 'core_fields', 'position', 'custom_field_ids', diff --git a/app/views/trackers/_form.html.erb b/app/views/trackers/_form.html.erb index 33e9452e5..c355d899e 100644 --- a/app/views/trackers/_form.html.erb +++ b/app/views/trackers/_form.html.erb @@ -10,6 +10,7 @@ :include_blank => @tracker.default_status.nil?, :required => true %>

+

<%= f.check_box :private_by_default %>

<%= f.check_box :is_in_roadmap %>

<%= f.textarea :description, :rows => 4 %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index 789694d67..3f5bbbdeb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -326,6 +326,7 @@ en: field_is_public: Public field_parent: Subproject of field_is_in_roadmap: Issues displayed in roadmap + field_private_by_default: Private by default field_login: Login field_mail_notification: Email notifications field_admin: Administrator diff --git a/db/migrate/20260510000000_add_private_by_default_to_trackers.rb b/db/migrate/20260510000000_add_private_by_default_to_trackers.rb new file mode 100644 index 000000000..e98078141 --- /dev/null +++ b/db/migrate/20260510000000_add_private_by_default_to_trackers.rb @@ -0,0 +1,5 @@ +class AddPrivateByDefaultToTrackers < ActiveRecord::Migration[8.1] + def change + add_column :trackers, :private_by_default, :boolean, :default => false, :null => false + end +end diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index e02fd8ce8..f7b083a05 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -4241,6 +4241,60 @@ class IssuesControllerTest < Redmine::ControllerTest end end + def test_new_should_check_private_if_tracker_is_private_by_default + Tracker.find(1).update! :private_by_default => true + @request.session[:user_id] = 2 + get(:new, :params => {:project_id => 1}) + assert_response :success + + assert_select 'input[name=?][checked=checked]', 'issue[is_private]' + end + + def test_update_form_for_new_issue_should_apply_private_by_default_when_submitted_private_is_unchecked + Tracker.find(2).update! :private_by_default => true + @request.session[:user_id] = 2 + # Simulates switching from an unchecked tracker to a tracker that is private by default. + post( + :new, + :params => { + :project_id => 1, + :issue => { + :tracker_id => 2, + :is_private => '0' + }, + :form_update_triggered_by => 'issue_tracker_id' + } + ) + assert_response :success + + assert_select 'select[name=?]', 'issue[tracker_id]' do + assert_select 'option[value=?][selected=selected]', '2' + end + assert_select 'input[name=?][checked=checked]', 'issue[is_private]' + end + + def test_update_form_for_new_issue_should_keep_submitted_private_when_checked + @request.session[:user_id] = 2 + # Simulates keeping the user's checked private value when switching trackers. + post( + :new, + :params => { + :project_id => 1, + :issue => { + :tracker_id => 2, + :is_private => '1' + }, + :form_update_triggered_by => 'issue_tracker_id' + } + ) + assert_response :success + + assert_select 'select[name=?]', 'issue[tracker_id]' do + assert_select 'option[value=?][selected=selected]', '2' + end + assert_select 'input[name=?][checked=checked]', 'issue[is_private]' + end + def test_update_form_for_new_issue_should_ignore_version_when_changing_project version = Version.generate!(:project_id => 1) Project.find(1).update_attribute :default_version_id, version.id @@ -4902,6 +4956,66 @@ class IssuesControllerTest < Redmine::ControllerTest assert issue.is_private? end + def test_post_create_should_respect_private_by_default_per_tracker_setting + @request.session[:user_id] = 2 + tracker = Tracker.find(1) + tracker.update! :private_by_default => true + + assert_difference 'Issue.count' do + post( + :create, + :params => { + :project_id => 1, + :issue => { + :tracker_id => tracker.id, + :subject => 'This is a private issue by default' + } + } + ) + end + issue = Issue.order(id: :desc).first + assert issue.is_private? + + assert_difference 'Issue.count' do + post( + :create, + :params => { + :project_id => 1, + :issue => { + :tracker_id => tracker.id, + :subject => 'This is a public issue', + :is_private => '0' + } + } + ) + end + issue = Issue.order(id: :desc).first + assert_not issue.is_private? + end + + def test_post_create_should_not_apply_private_by_default_without_permission + role = Role.find(1) + role.remove_permission! :set_issues_private + role.remove_permission! :set_own_issues_private + Tracker.find(1).update! :private_by_default => true + @request.session[:user_id] = 2 + + assert_difference 'Issue.count' do + post( + :create, + :params => { + :project_id => 1, + :issue => { + :tracker_id => 1, + :subject => 'This is a public issue' + } + } + ) + end + issue = Issue.order(id: :desc).first + assert_not issue.is_private? + end + def test_create_without_project_id @request.session[:user_id] = 2 assert_difference 'Issue.count' do diff --git a/test/functional/trackers_controller_test.rb b/test/functional/trackers_controller_test.rb index a4a741791..e55d8ffc8 100644 --- a/test/functional/trackers_controller_test.rb +++ b/test/functional/trackers_controller_test.rb @@ -121,6 +121,7 @@ class TrackersControllerTest < Redmine::ControllerTest :tracker => { :name => 'New tracker', :default_status_id => 1, + :private_by_default => '1', :project_ids => ['1', '', ''], :custom_field_ids => ['1', '6', ''] } @@ -129,6 +130,7 @@ class TrackersControllerTest < Redmine::ControllerTest assert_redirected_to :action => 'index' tracker = Tracker.order(id: :desc).first assert_equal 'New tracker', tracker.name + assert tracker.private_by_default? assert_equal [1], tracker.project_ids.sort assert_equal Tracker::CORE_FIELDS, tracker.core_fields assert_equal [1, 6], tracker.custom_field_ids.sort diff --git a/test/integration/api_test/issues_test.rb b/test/integration/api_test/issues_test.rb index b8f676a43..cadadc1ec 100644 --- a/test/integration/api_test/issues_test.rb +++ b/test/integration/api_test/issues_test.rb @@ -728,6 +728,18 @@ class Redmine::ApiTest::IssuesTest < Redmine::ApiTest::Base assert_response :unprocessable_content end + test "POST /issues.json should use the tracker private_by_default setting" do + Tracker.find(1).update! :private_by_default => true + + issue = new_record(Issue) do + post( + '/issues.json', + :params => {:issue => {:project_id => 1, :tracker_id => 1, :subject => 'API'}}, + :headers => credentials('jsmith')) + end + assert issue.is_private? + end + test "PUT /issues/:id.xml" do assert_difference('Journal.count') do put( -- 2.50.1