diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d4f7339a0..b9867fa3d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -123,6 +123,14 @@ module ProjectsHelper grouped_options_for_select(grouped, project.default_issue_query_id) end + def project_default_tracker_options(project, role = nil) + trackers = project.trackers.to_a + trackers = trackers.select { |t| role.permissions_tracker?(:add_issues, t) } if role + + selected = project.project_default_tracker(role)&.tracker_id + options_for_select(trackers.map { |t| [t.name, t.id] }, selected) + end + def format_version_sharing(sharing) sharing = 'none' unless Version::VERSION_SHARINGS.include?(sharing) l("label_version_sharing_#{sharing}") diff --git a/app/models/issue.rb b/app/models/issue.rb index 8e131118c..9fc4c0596 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -448,6 +448,11 @@ class Issue < ActiveRecord::Base self.fixed_version_id = project.default_version_id end end + # Set tracker to the project default tracker with role + if new_record? && tracker.nil? && project + self.tracker_id = project.default_tracker&.id + end + self.project end diff --git a/app/models/project.rb b/app/models/project.rb index 11f4a953d..1ef8de665 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -41,6 +41,7 @@ class Project < ActiveRecord::Base has_many :versions, :dependent => :destroy belongs_to :default_version, :class_name => 'Version' belongs_to :default_assigned_to, :class_name => 'Principal' + has_many :project_default_trackers, :autosave => true has_many :time_entries, :dependent => :destroy # Specific overridden Activities has_many :time_entry_activities, :dependent => :destroy @@ -835,7 +836,9 @@ class Project < ActiveRecord::Base 'parent_id', 'default_version_id', 'default_issue_query_id', - 'default_assigned_to_id') + 'default_assigned_to_id', + 'default_tracker_id_for_all_roles', + 'default_tracker_ids_by_role') safe_attributes( 'is_public', @@ -908,6 +911,41 @@ class Project < ActiveRecord::Base super(attrs, user) end + def default_tracker + tracker_id = if self.has_default_tracker_for_all_roles? + self.project_default_tracker&.tracker_id + else + role_ids = User.current.roles_for_project(self).map(&:id) + role = Role.givable.sorted.where(id: role_ids).first + + self.project_default_tracker(role)&.tracker_id + end + + self.trackers.find_by(id: tracker_id) + end + + def project_default_tracker(role = nil) + self.project_default_trackers.find_by(role_id: role&.id) + end + + def has_default_tracker_for_all_roles? + self.project_default_trackers.where.not(role_id: nil).empty? + end + + def default_tracker_id_for_all_roles=(tracker_id) + self.project_default_trackers.each(&:mark_for_destruction) + self.project_default_trackers.build(role_id: nil, tracker_id: tracker_id) + end + + def default_tracker_ids_by_role=(tracker_ids_by_role) + self.project_default_trackers.each(&:mark_for_destruction) + + Role.givable.sorted.find_each do |role| + tracker_id = tracker_ids_by_role[role.id.to_s] + self.project_default_trackers.build(role_id: role.id, tracker_id: tracker_id) + end + end + # Returns an auto-generated project identifier based on the last identifier used def self.next_identifier p = Project.order('id DESC').first diff --git a/app/models/project_default_tracker.rb b/app/models/project_default_tracker.rb new file mode 100644 index 000000000..af5d4b4cf --- /dev/null +++ b/app/models/project_default_tracker.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +# Redmine - project management software +# Copyright (C) 2006-2023 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class ProjectDefaultTracker < ActiveRecord::Base + self.table_name = "#{table_name_prefix}project_default_trackers#{table_name_suffix}" + + belongs_to :tracker +end diff --git a/app/views/projects/settings/_issues.html.erb b/app/views/projects/settings/_issues.html.erb index 4fe977fb8..e92956f8c 100644 --- a/app/views/projects/settings/_issues.html.erb +++ b/app/views/projects/settings/_issues.html.erb @@ -46,6 +46,44 @@ <% if @project.safe_attribute?('default_issue_query_id') %>

<%= f.select :default_issue_query_id, project_default_issue_query_options(@project), include_blank: l(:label_none) %><%=l 'text_allowed_queries_to_select' %>

<% end %> + +
+ + + + + + + + + + + + <% Role.givable.sorted.each do |role| %> + + + + + <% end %> +
+ + + <%= select_tag 'project[default_tracker_id_for_all_roles]', project_default_tracker_options(@project), prompt: l(:label_none) %> +
+ +
<%= role.name %> + <%= select_tag "project[default_tracker_ids_by_role][#{role.id}]", project_default_tracker_options(@project, role), prompt: l(:label_none) %> +
+
+

<%= submit_tag l(:button_save) %>

diff --git a/config/locales/en.yml b/config/locales/en.yml index 3f60e9e73..afca35f1a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1412,3 +1412,6 @@ en: text_user_destroy_confirmation: "Are you sure you want to delete this user and remove all references to them? This cannot be undone. Often, locking a user instead of deleting them is the better solution. To confirm, please enter their login (%{login}) below." text_project_destroy_enter_identifier: "To confirm, please enter the project's identifier (%{identifier}) below." field_name_or_email_or_login: Name, email or login + label_default_tracker: Default tracker + label_default_tracker_for_all_roles: For all roles + label_default_tracker_by_role: By role diff --git a/config/locales/ja.yml b/config/locales/ja.yml index d5b08b4b3..762787a17 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1438,3 +1438,6 @@ ja: text_select_apply_issue_status: このステータスを適用 field_name_or_email_or_login: 姓名・メールアドレス・ログインID text_default_active_job_queue_changed: キューアダプターがデフォルト (開発・テスト用) 以外のものに変更済み + label_default_tracker: デフォルトのトラッカー + label_default_tracker_for_all_roles: すべてのロールで共通 + label_default_tracker_by_role: ロールごとに設定する diff --git a/db/migrate/20231031075958_create_project_default_trackers.rb b/db/migrate/20231031075958_create_project_default_trackers.rb new file mode 100644 index 000000000..3ce099562 --- /dev/null +++ b/db/migrate/20231031075958_create_project_default_trackers.rb @@ -0,0 +1,11 @@ +class CreateProjectDefaultTrackers < ActiveRecord::Migration[6.1] + def change + create_table :project_default_trackers do |t| + t.integer :project_id, :null => false + t.integer :role_id, :null => true + t.integer :tracker_id, :null => true + end + + add_index :project_default_trackers, [:project_id, :tracker_id, :role_id], :unique => true, :name => :project_default_trackers_role_id + end +end diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index b8580cd15..23d98e814 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -3529,6 +3529,20 @@ class IssuesControllerTest < Redmine::ControllerTest end end + def test_new_should_preselect_default_tracker + ProjectDefaultTracker.create!( + :project_id => 1, :tracker_id => 3, :role_id => nil + ) + assert_not_equal Project.find(1).trackers.first.id, 3 + + @request.session[:user_id] = 2 + get(:new, :params => {:project_id => 1}) + assert_response :success + assert_select 'select[name=?]', 'issue[tracker_id]' do + assert_select 'option[value=?][selected=selected]', '3' + end + end + def test_get_new_with_list_custom_field @request.session[:user_id] = 2 get( diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb index 0ac0ecc36..8f403cd35 100644 --- a/test/functional/projects_controller_test.rb +++ b/test/functional/projects_controller_test.rb @@ -1047,6 +1047,7 @@ class ProjectsControllerTest < Redmine::ControllerTest assert_select 'input[name=?]', 'project[issue_custom_field_ids][]' assert_select 'select[name=?]', 'project[default_version_id]', 1 assert_select 'select[name=?]', 'project[default_assigned_to_id]', 1 + assert_select 'select[name=?]', 'project[default_tracker_id_for_all_roles]', 1 end def test_update diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index 14124f7bd..f3add5087 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -651,6 +651,45 @@ class IssueTest < ActiveSupport::TestCase assert_equal version, issue.fixed_version end + def test_setting_project_should_set_tracker_to_default_tracker_for_all_roles + issue = Issue.new(:project_id => 1) + assert_nil issue.tracker + + ProjectDefaultTracker.create!( + :project_id => 1, :tracker_id => 3, :role_id => nil + ) + + issue = Issue.new(:project_id => 1) + assert_equal 3, issue.tracker_id + assert_not_equal Project.find(1).trackers.first, issue.tracker + end + + def test_setting_project_should_set_tracker_to_default_tracker_by_role + User.current = User.find_by_login('jsmith') + issue = Issue.new(:project_id => 1) + assert_nil issue.tracker_id + + ProjectDefaultTracker.create!( + :project_id => 1, :tracker_id => 3, :role_id => 1 + ) + User.current = User.find_by_login('jsmith') + + issue = Issue.new(:project_id => 1) + assert_equal 3, issue.tracker_id + assert_equal 1, User.current.roles_for_project(Project.find(1)).first.id + end + + def test_setting_project_should_not_set_a_disallowed_tracker + disallowed_tracker = Tracker.generate! + ProjectDefaultTracker.create!( + :project_id => 1, :tracker_id => disallowed_tracker.id, :role_id => nil + ) + + issue = Issue.new(:project_id => 1) + assert_nil issue.tracker_id + assert_not_includes Project.find(1).trackers.pluck(:id), disallowed_tracker.id + end + def test_default_assigned_to_based_on_category_should_be_set_on_create user = User.find(3) category = IssueCategory.create!(:project_id => 1, :name => 'With default assignee', :assigned_to_id => 3)