From 697159d6a8499908b7539f0063159c8e40b17984 Mon Sep 17 00:00:00 2001 From: Marius BALTEANU Date: Wed, 26 Jun 2019 06:23:34 +0000 Subject: [PATCH] Option to set a tracker only as subtask --- app/helpers/workflows_helper.rb | 13 ++++--- app/models/issue.rb | 5 +-- app/models/tracker.rb | 1 + app/views/issues/_attributes.html.erb | 4 +-- app/views/trackers/_form.html.erb | 1 + app/views/trackers/index.api.rsb | 1 + app/views/trackers/index.html.erb | 2 ++ app/views/workflows/permissions.html.erb | 4 +-- config/locales/en.yml | 1 + ...0625215748_add_subtask_only_to_trackers.rb | 9 +++++ test/fixtures/projects_trackers.yml | 35 ++++++++++--------- test/fixtures/trackers.yml | 18 +++++++--- test/functional/issues_controller_test.rb | 4 +-- test/functional/trackers_controller_test.rb | 5 +++ test/functional/workflows_controller_test.rb | 25 +++++++++++++ test/integration/api_test/trackers_test.rb | 5 +++ test/unit/issue_test.rb | 21 +++++++++-- .../acts/positioned_without_scope_test.rb | 10 +++--- test/unit/tracker_test.rb | 8 +++++ 19 files changed, 133 insertions(+), 39 deletions(-) create mode 100644 db/migrate/20190625215748_add_subtask_only_to_trackers.rb diff --git a/app/helpers/workflows_helper.rb b/app/helpers/workflows_helper.rb index 21ee21d49..5b969503e 100644 --- a/app/helpers/workflows_helper.rb +++ b/app/helpers/workflows_helper.rb @@ -42,14 +42,19 @@ module WorkflowsHelper select_tag name, option_tags, {:multiple => multiple}.merge(options) end - def field_required?(field) - field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field) + def field_required?(field, trackers=[]) + if field == 'parent_issue_id' + subtasks = trackers.select {|t| t.subtask_only?} + subtasks.count == trackers.count + else + field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field) + end end - def field_permission_tag(permissions, status, field, roles) + def field_permission_tag(permissions, status, field, roles, trackers=[]) name = field.is_a?(CustomField) ? field.id.to_s : field options = [["", ""], [l(:label_readonly), "readonly"]] - options << [l(:label_required), "required"] unless field_required?(field) + options << [l(:label_required), "required"] unless field_required?(field, trackers) html_options = {} if perm = permissions[status.id][name] diff --git a/app/models/issue.rb b/app/models/issue.rb index 487b1b552..0ec6f0493 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -64,6 +64,7 @@ class Issue < ActiveRecord::Base validates_presence_of :priority, :if => Proc.new {|issue| issue.new_record? || issue.priority_id_changed?} validates_presence_of :status, :if => Proc.new {|issue| issue.new_record? || issue.status_id_changed?} validates_presence_of :author, :if => Proc.new {|issue| issue.new_record? || issue.author_id_changed?} + validates_presence_of :parent_issue_id, :if => Proc.new {|issue| issue.tracker && issue.tracker.subtask_only? && issue.tracker_id_changed?} validates_length_of :subject, :maximum => 255 validates_inclusion_of :done_ratio, :in => 0..100 @@ -623,7 +624,7 @@ class Issue < ActiveRecord::Base # Returns the names of attributes that are read-only for user or the current user # For users with multiple roles, the read-only fields are the intersection of # read-only fields of each role - # The result is an array of strings where sustom fields are represented with their ids + # The result is an array of strings where custom fields are represented with their ids # # Examples: # issue.read_only_attribute_names # => ['due_date', '2'] @@ -635,7 +636,7 @@ class Issue < ActiveRecord::Base # Returns the names of required attributes for user or the current user # For users with multiple roles, the required fields are the intersection of # required fields of each role - # The result is an array of strings where sustom fields are represented with their ids + # The result is an array of strings where custom fields are represented with their ids # # Examples: # issue.required_attribute_names # => ['due_date', '2'] diff --git a/app/models/tracker.rb b/app/models/tracker.rb index 9395aa9fa..c0ce61931 100644 --- a/app/models/tracker.rb +++ b/app/models/tracker.rb @@ -70,6 +70,7 @@ class Tracker < ActiveRecord::Base 'name', 'default_status_id', 'is_in_roadmap', + 'subtask_only', 'core_fields', 'position', 'custom_field_ids', diff --git a/app/views/issues/_attributes.html.erb b/app/views/issues/_attributes.html.erb index ee4ae109c..b4b1d8f79 100644 --- a/app/views/issues/_attributes.html.erb +++ b/app/views/issues/_attributes.html.erb @@ -61,8 +61,8 @@
<% if @issue.safe_attribute? 'parent_issue_id' %>

<%= f.text_field :parent_issue_id, :size => 10, - :required => @issue.required_attribute?('parent_issue_id') %>

-<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript(auto_complete_issues_path(:project_id => @issue.project, :scope => Setting.cross_project_subtasks, :status => @issue.closed? ? 'c' : 'o', :issue_id => @issue.id))}')" %> + :required => @issue.required_attribute?('parent_issue_id') || @issue.tracker.subtask_only? %>

+<%= javascript_tag "observeAutocompleteField('issue_parent_issue_id', '#{escape_javascript auto_complete_issues_path(:project_id => @issue.project, :scope => Setting.cross_project_subtasks, :status => @issue.closed? ? 'c' : 'o', :issue_id => @issue.id)}')" %> <% end %> <% if @issue.safe_attribute? 'start_date' %> diff --git a/app/views/trackers/_form.html.erb b/app/views/trackers/_form.html.erb index 27e086e56..cbae5cb37 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 :subtask_only %>

<%= f.check_box :is_in_roadmap %>

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

diff --git a/app/views/trackers/index.api.rsb b/app/views/trackers/index.api.rsb index 59affd7ae..84bf6160b 100644 --- a/app/views/trackers/index.api.rsb +++ b/app/views/trackers/index.api.rsb @@ -4,6 +4,7 @@ api.array :trackers do api.id tracker.id api.name tracker.name api.default_status(:id => tracker.default_status.id, :name => tracker.default_status.name) unless tracker.default_status.nil? + api.subtask_only tracker.subtask_only? api.description tracker.description end end diff --git a/app/views/trackers/index.html.erb b/app/views/trackers/index.html.erb index 20d09754d..3aea6988f 100644 --- a/app/views/trackers/index.html.erb +++ b/app/views/trackers/index.html.erb @@ -9,6 +9,7 @@ <%=l(:label_tracker)%> <%=l(:field_default_status)%> + <%=l(:field_subtask_only)%> <%=l(:field_description)%> @@ -18,6 +19,7 @@ <%= link_to tracker.name, edit_tracker_path(tracker) %> <%= tracker.default_status.name %> + <%= checked_image tracker.subtask_only? %> <%= tracker.description %> <% unless tracker.workflow_rules.exists? %> diff --git a/app/views/workflows/permissions.html.erb b/app/views/workflows/permissions.html.erb index 0b27c378d..36fb14d5c 100644 --- a/app/views/workflows/permissions.html.erb +++ b/app/views/workflows/permissions.html.erb @@ -61,11 +61,11 @@ <% @fields.each do |field, name| %> - <%= name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %> + <%= name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field, @trackers) %> <% for status in @statuses -%> - <%= field_permission_tag(@permissions, status, field, @roles) %> + <%= field_permission_tag(@permissions, status, field, @roles, @trackers) %> <% unless status == @statuses.last %>»<% end %> <% end -%> diff --git a/config/locales/en.yml b/config/locales/en.yml index efaea5252..caf4e180b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -395,6 +395,7 @@ en: field_history_default_tab: Issue's history default tab field_unique_id: Unique ID field_toolbar_language_options: Code highlighting toolbar languages + field_subtask_only: Subtask only setting_app_title: Application title setting_welcome_text: Welcome text diff --git a/db/migrate/20190625215748_add_subtask_only_to_trackers.rb b/db/migrate/20190625215748_add_subtask_only_to_trackers.rb new file mode 100644 index 000000000..0e3047a4e --- /dev/null +++ b/db/migrate/20190625215748_add_subtask_only_to_trackers.rb @@ -0,0 +1,9 @@ +class AddSubtaskOnlyToTrackers < ActiveRecord::Migration[5.2] + def up + add_column :trackers, :subtask_only, :boolean, :default => false + end + + def down + remove_column :trackers, :subtask_only + end +end diff --git a/test/fixtures/projects_trackers.yml b/test/fixtures/projects_trackers.yml index 54443fa0b..f793768c3 100644 --- a/test/fixtures/projects_trackers.yml +++ b/test/fixtures/projects_trackers.yml @@ -1,49 +1,52 @@ ---- -projects_trackers_001: +--- +projects_trackers_001: project_id: 4 tracker_id: 3 -projects_trackers_002: +projects_trackers_002: project_id: 1 tracker_id: 1 -projects_trackers_003: +projects_trackers_003: project_id: 5 tracker_id: 1 -projects_trackers_004: +projects_trackers_004: project_id: 1 tracker_id: 2 -projects_trackers_005: +projects_trackers_005: project_id: 5 tracker_id: 2 -projects_trackers_006: +projects_trackers_006: project_id: 5 tracker_id: 3 -projects_trackers_007: +projects_trackers_007: project_id: 2 tracker_id: 1 -projects_trackers_008: +projects_trackers_008: project_id: 2 tracker_id: 2 -projects_trackers_009: +projects_trackers_009: project_id: 2 tracker_id: 3 -projects_trackers_010: +projects_trackers_010: project_id: 3 tracker_id: 2 -projects_trackers_011: +projects_trackers_011: project_id: 3 tracker_id: 3 -projects_trackers_012: +projects_trackers_012: project_id: 4 tracker_id: 1 -projects_trackers_013: +projects_trackers_013: project_id: 4 tracker_id: 2 -projects_trackers_014: +projects_trackers_014: project_id: 1 tracker_id: 3 -projects_trackers_015: +projects_trackers_015: project_id: 6 tracker_id: 1 projects_trackers_016: project_id: 3 tracker_id: 1 +projects_trackers_017: + project_id: 1 + tracker_id: 4 diff --git a/test/fixtures/trackers.yml b/test/fixtures/trackers.yml index 41602a78f..c57c144af 100644 --- a/test/fixtures/trackers.yml +++ b/test/fixtures/trackers.yml @@ -1,21 +1,31 @@ ---- -trackers_001: +--- +trackers_001: name: Bug id: 1 is_in_chlog: true default_status_id: 1 position: 1 description: Description for Bug tracker -trackers_002: + subtask_only: false +trackers_002: name: Feature request id: 2 is_in_chlog: true default_status_id: 1 position: 2 description: Description for Feature request tracker -trackers_003: + subtask_only: false +trackers_003: name: Support request id: 3 is_in_chlog: false default_status_id: 1 position: 3 + subtask_only: false +trackers_004: + name: Subtask + id: 4 + is_in_chlog: false + default_status_id: 1 + position: 4 + subtask_only: true diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index 21b43d7c9..6f9876893 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -3169,7 +3169,7 @@ class IssuesControllerTest < Redmine::ControllerTest get(:new, :params => {:project_id => 1}) assert_response :success assert_select 'select[name=?]', 'issue[tracker_id]' do - assert_select 'option', 3 + assert_select 'option', 4 assert_select 'option[value="1"][selected=selected]' end end @@ -3190,7 +3190,7 @@ class IssuesControllerTest < Redmine::ControllerTest ) assert_response :success assert_select 'select[name=?]', 'issue[tracker_id]' do - assert_select 'option', 2 + assert_select 'option', 3 assert_select 'option[value="2"][selected=selected]' assert_select 'option[value="1"]', 0 end diff --git a/test/functional/trackers_controller_test.rb b/test/functional/trackers_controller_test.rb index eb7a2d26a..e15463e72 100644 --- a/test/functional/trackers_controller_test.rb +++ b/test/functional/trackers_controller_test.rb @@ -31,6 +31,9 @@ class TrackersControllerTest < Redmine::ControllerTest get :index assert_response :success assert_select 'table.trackers' + + # assert subtask column + assert_select 'td.subtask .icon-checked', 1 end def test_index_by_anonymous_should_redirect_to_login_form @@ -49,6 +52,7 @@ class TrackersControllerTest < Redmine::ControllerTest get :new assert_response :success assert_select 'input[name=?]', 'tracker[name]' + assert_select 'input[name=?]', 'tracker[subtask_only]' assert_select 'select[name=?]', 'tracker[default_status_id]' do assert_select 'option[value=?][selected=selected]', IssueStatus.sorted.first.id.to_s end @@ -72,6 +76,7 @@ class TrackersControllerTest < Redmine::ControllerTest assert_equal Tracker::CORE_FIELDS, tracker.core_fields assert_equal [1, 6], tracker.custom_field_ids.sort assert_equal 0, tracker.workflow_rules.count + assert !tracker.subtask_only end def test_create_with_disabled_core_fields diff --git a/test/functional/workflows_controller_test.rb b/test/functional/workflows_controller_test.rb index cf9ab2eae..51d49f6a9 100644 --- a/test/functional/workflows_controller_test.rb +++ b/test/functional/workflows_controller_test.rb @@ -246,6 +246,31 @@ class WorkflowsControllerTest < Redmine::ControllerTest end end + def test_get_permissions_with_role_and_subtask_tracker_should_mark_parent_task_field_required + get :permissions, :params => {:role_id => 1, :tracker_id => 4} + assert_response :success + + assert_select 'td.name', :text => 'Parent task *' + end + + def test_get_permissions_with_role_and_multiple_subtask_trackers_should_mark_parent_task_field_required + subtask = Tracker.find(1) + subtask.subtask_only = true + subtask.save! + + get :permissions, :params => {:role_id => 1, :tracker_id => [1, 4]} + assert_response :success + + assert_select 'td.name', :text => 'Parent task *' + end + + def test_get_permissions_with_role_and_mixed_trackers_should_not_mark_parent_task_field_required + get :permissions, :params => {:role_id => 1, :tracker_id => [1, 4]} + assert_response :success + + assert_select 'td.name', :text => 'Parent task' + end + def test_get_permissions_with_required_custom_field_should_not_show_required_option cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :tracker_ids => [1], :is_required => true) diff --git a/test/integration/api_test/trackers_test.rb b/test/integration/api_test/trackers_test.rb index 5f220024d..a1bde73f1 100644 --- a/test/integration/api_test/trackers_test.rb +++ b/test/integration/api_test/trackers_test.rb @@ -30,7 +30,12 @@ class Redmine::ApiTest::TrackersTest < Redmine::ApiTest::Base assert_select 'trackers[type=array] tracker id', :text => '2' do assert_select '~ name', :text => 'Feature request' + assert_select '~ subtask_only', :text => 'false' assert_select '~ description', :text => 'Description for Feature request tracker' end + + assert_select 'trackers[type=array] tracker id', :text => '4' do + assert_select '~ subtask_only', :text => 'true' + end end end diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index aea76e34f..2c22d3195 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -214,6 +214,23 @@ class IssueTest < ActiveSupport::TestCase assert_include 'Parent task is invalid', issue.errors.full_messages end + def test_create_with_subtask_tracker + issue = Issue.new(:project_id => 1, :tracker_id => 4, + :author_id => 1, :subject => 'Group assignment', + :parent_issue_id => 1) + + assert issue.save + assert_equal 1, issue.parent_issue_id + end + + def test_create_with_subtask_tracker_should_require_parent_task + issue = Issue.new(:project_id => 1, :tracker_id => 4, + :author_id => 1, :subject => 'Group assignment') + + assert !issue.save + assert_include 'Parent task cannot be blank', issue.errors.full_messages + end + def assert_visibility_match(user, issues) assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort end @@ -1689,7 +1706,7 @@ class IssueTest < ActiveSupport::TestCase role.save! User.add_to_project(user, Project.find(1), role) - assert_equal [1, 2, 3], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort + assert_equal [1, 2, 3, 4], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort end def test_allowed_target_trackers_with_one_role_allowed_on_some_trackers @@ -1730,7 +1747,7 @@ class IssueTest < ActiveSupport::TestCase role2.save! User.add_to_project(user, Project.find(1), [role1, role2]) - assert_equal [1, 2, 3], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort + assert_equal [1, 2, 3, 4], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort end def test_allowed_target_trackers_should_not_consider_roles_without_add_issues_permission diff --git a/test/unit/lib/redmine/acts/positioned_without_scope_test.rb b/test/unit/lib/redmine/acts/positioned_without_scope_test.rb index 79a95699a..73a5bba1e 100644 --- a/test/unit/lib/redmine/acts/positioned_without_scope_test.rb +++ b/test/unit/lib/redmine/acts/positioned_without_scope_test.rb @@ -26,7 +26,7 @@ class Redmine::Acts::PositionedWithoutScopeTest < ActiveSupport::TestCase t = Tracker.generate t.save! - assert_equal 4, t.reload.position + assert_equal 5, t.reload.position end def test_create_should_insert_at_given_position @@ -35,7 +35,7 @@ class Redmine::Acts::PositionedWithoutScopeTest < ActiveSupport::TestCase t.save! assert_equal 2, t.reload.position - assert_equal [1, 3, 4, 2], Tracker.order(:id).pluck(:position) + assert_equal [1, 3, 4, 5, 2], Tracker.order(:id).pluck(:position) end def test_destroy_should_remove_position @@ -43,15 +43,15 @@ class Redmine::Acts::PositionedWithoutScopeTest < ActiveSupport::TestCase Tracker.generate! t.destroy - assert_equal [1, 2, 3, 4], Tracker.order(:id).pluck(:position) + assert_equal [1, 2, 3, 4, 5], Tracker.order(:id).pluck(:position) end def test_update_should_update_positions t = Tracker.generate! - assert_equal 4, t.position + assert_equal 5, t.position t.position = 2 t.save! - assert_equal [1, 3, 4, 2], Tracker.order(:id).pluck(:position) + assert_equal [1, 3, 4, 5, 2], Tracker.order(:id).pluck(:position) end end diff --git a/test/unit/tracker_test.rb b/test/unit/tracker_test.rb index 89a711fdd..1e2827fbd 100644 --- a/test/unit/tracker_test.rb +++ b/test/unit/tracker_test.rb @@ -140,4 +140,12 @@ class TrackerTest < ActiveSupport::TestCase assert tracker.respond_to?(:description) assert_equal tracker.description, "Description for Bug tracker" end + + def test_set_tracker_subtask_only + tracker = Tracker.find(1) + tracker.subtask_only = true + + assert tracker.save! + assert tracker.subtask_only? + end end -- 2.22.0