Feature #29470 » 0001-Option-to-set-a-tracker-only-as-subtask.patch
app/helpers/workflows_helper.rb | ||
---|---|---|
42 | 42 |
select_tag name, option_tags, {:multiple => multiple}.merge(options) |
43 | 43 |
end |
44 | 44 | |
45 |
def field_required?(field) |
|
46 |
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field) |
|
45 |
def field_required?(field, trackers=[]) |
|
46 |
if field == 'parent_issue_id' |
|
47 |
subtasks = trackers.select {|t| t.subtask_only?} |
|
48 |
subtasks.count == trackers.count |
|
49 |
else |
|
50 |
field.is_a?(CustomField) ? field.is_required? : %w(project_id tracker_id subject priority_id is_private).include?(field) |
|
51 |
end |
|
47 | 52 |
end |
48 | 53 | |
49 |
def field_permission_tag(permissions, status, field, roles) |
|
54 |
def field_permission_tag(permissions, status, field, roles, trackers=[])
|
|
50 | 55 |
name = field.is_a?(CustomField) ? field.id.to_s : field |
51 | 56 |
options = [["", ""], [l(:label_readonly), "readonly"]] |
52 |
options << [l(:label_required), "required"] unless field_required?(field) |
|
57 |
options << [l(:label_required), "required"] unless field_required?(field, trackers)
|
|
53 | 58 |
html_options = {} |
54 | 59 | |
55 | 60 |
if perm = permissions[status.id][name] |
app/models/issue.rb | ||
---|---|---|
64 | 64 |
validates_presence_of :priority, :if => Proc.new {|issue| issue.new_record? || issue.priority_id_changed?} |
65 | 65 |
validates_presence_of :status, :if => Proc.new {|issue| issue.new_record? || issue.status_id_changed?} |
66 | 66 |
validates_presence_of :author, :if => Proc.new {|issue| issue.new_record? || issue.author_id_changed?} |
67 |
validates_presence_of :parent_issue_id, :if => Proc.new {|issue| issue.tracker && issue.tracker.subtask_only? && issue.tracker_id_changed?} |
|
67 | 68 | |
68 | 69 |
validates_length_of :subject, :maximum => 255 |
69 | 70 |
validates_inclusion_of :done_ratio, :in => 0..100 |
... | ... | |
623 | 624 |
# Returns the names of attributes that are read-only for user or the current user |
624 | 625 |
# For users with multiple roles, the read-only fields are the intersection of |
625 | 626 |
# read-only fields of each role |
626 |
# The result is an array of strings where sustom fields are represented with their ids
|
|
627 |
# The result is an array of strings where custom fields are represented with their ids
|
|
627 | 628 |
# |
628 | 629 |
# Examples: |
629 | 630 |
# issue.read_only_attribute_names # => ['due_date', '2'] |
... | ... | |
635 | 636 |
# Returns the names of required attributes for user or the current user |
636 | 637 |
# For users with multiple roles, the required fields are the intersection of |
637 | 638 |
# required fields of each role |
638 |
# The result is an array of strings where sustom fields are represented with their ids
|
|
639 |
# The result is an array of strings where custom fields are represented with their ids
|
|
639 | 640 |
# |
640 | 641 |
# Examples: |
641 | 642 |
# issue.required_attribute_names # => ['due_date', '2'] |
app/models/tracker.rb | ||
---|---|---|
70 | 70 |
'name', |
71 | 71 |
'default_status_id', |
72 | 72 |
'is_in_roadmap', |
73 |
'subtask_only', |
|
73 | 74 |
'core_fields', |
74 | 75 |
'position', |
75 | 76 |
'custom_field_ids', |
app/views/issues/_attributes.html.erb | ||
---|---|---|
61 | 61 |
<div class="splitcontentright"> |
62 | 62 |
<% if @issue.safe_attribute? 'parent_issue_id' %> |
63 | 63 |
<p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10, |
64 |
:required => @issue.required_attribute?('parent_issue_id') %></p> |
|
65 |
<%= 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))}')" %>
|
|
64 |
:required => @issue.required_attribute?('parent_issue_id') || @issue.tracker.subtask_only? %></p>
|
|
65 |
<%= 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)}')" %>
|
|
66 | 66 |
<% end %> |
67 | 67 | |
68 | 68 |
<% if @issue.safe_attribute? 'start_date' %> |
app/views/trackers/_form.html.erb | ||
---|---|---|
10 | 10 |
:include_blank => @tracker.default_status.nil?, |
11 | 11 |
:required => true %> |
12 | 12 |
</p> |
13 |
<p><%= f.check_box :subtask_only %></p> |
|
13 | 14 |
<p><%= f.check_box :is_in_roadmap %></p> |
14 | 15 |
<p><%= f.text_area :description, :rows => 4 %></p> |
15 | 16 |
<p> |
app/views/trackers/index.api.rsb | ||
---|---|---|
4 | 4 |
api.id tracker.id |
5 | 5 |
api.name tracker.name |
6 | 6 |
api.default_status(:id => tracker.default_status.id, :name => tracker.default_status.name) unless tracker.default_status.nil? |
7 |
api.subtask_only tracker.subtask_only? |
|
7 | 8 |
api.description tracker.description |
8 | 9 |
end |
9 | 10 |
end |
app/views/trackers/index.html.erb | ||
---|---|---|
9 | 9 |
<thead><tr> |
10 | 10 |
<th><%=l(:label_tracker)%></th> |
11 | 11 |
<th><%=l(:field_default_status)%></th> |
12 |
<th><%=l(:field_subtask_only)%></th> |
|
12 | 13 |
<th><%=l(:field_description)%></th> |
13 | 14 |
<th></th> |
14 | 15 |
<th></th> |
... | ... | |
18 | 19 |
<tr> |
19 | 20 |
<td class="name"><%= link_to tracker.name, edit_tracker_path(tracker) %></td> |
20 | 21 |
<td><%= tracker.default_status.name %></td> |
22 |
<td class="subtask"><%= checked_image tracker.subtask_only? %></td> |
|
21 | 23 |
<td class="description"><%= tracker.description %></td> |
22 | 24 |
<td> |
23 | 25 |
<% unless tracker.workflow_rules.exists? %> |
app/views/workflows/permissions.html.erb | ||
---|---|---|
61 | 61 |
<% @fields.each do |field, name| %> |
62 | 62 |
<tr> |
63 | 63 |
<td class="name"> |
64 |
<%= name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field) %> |
|
64 |
<%= name %> <%= content_tag('span', '*', :class => 'required') if field_required?(field, @trackers) %>
|
|
65 | 65 |
</td> |
66 | 66 |
<% for status in @statuses -%> |
67 | 67 |
<td class="<%= @permissions[status.id][field].try(:join, ' ') %>" title="<%= name %> (<%= status.name %>)"> |
68 |
<%= field_permission_tag(@permissions, status, field, @roles) %> |
|
68 |
<%= field_permission_tag(@permissions, status, field, @roles, @trackers) %>
|
|
69 | 69 |
<% unless status == @statuses.last %><a href="#" class="repeat-value">»</a><% end %> |
70 | 70 |
</td> |
71 | 71 |
<% end -%> |
config/locales/en.yml | ||
---|---|---|
395 | 395 |
field_history_default_tab: Issue's history default tab |
396 | 396 |
field_unique_id: Unique ID |
397 | 397 |
field_toolbar_language_options: Code highlighting toolbar languages |
398 |
field_subtask_only: Subtask only |
|
398 | 399 | |
399 | 400 |
setting_app_title: Application title |
400 | 401 |
setting_welcome_text: Welcome text |
db/migrate/20190625215748_add_subtask_only_to_trackers.rb | ||
---|---|---|
1 |
class AddSubtaskOnlyToTrackers < ActiveRecord::Migration[5.2] |
|
2 |
def up |
|
3 |
add_column :trackers, :subtask_only, :boolean, :default => false |
|
4 |
end |
|
5 | ||
6 |
def down |
|
7 |
remove_column :trackers, :subtask_only |
|
8 |
end |
|
9 |
end |
test/fixtures/projects_trackers.yml | ||
---|---|---|
1 |
---
|
|
2 |
projects_trackers_001:
|
|
1 |
--- |
|
2 |
projects_trackers_001: |
|
3 | 3 |
project_id: 4 |
4 | 4 |
tracker_id: 3 |
5 |
projects_trackers_002:
|
|
5 |
projects_trackers_002: |
|
6 | 6 |
project_id: 1 |
7 | 7 |
tracker_id: 1 |
8 |
projects_trackers_003:
|
|
8 |
projects_trackers_003: |
|
9 | 9 |
project_id: 5 |
10 | 10 |
tracker_id: 1 |
11 |
projects_trackers_004:
|
|
11 |
projects_trackers_004: |
|
12 | 12 |
project_id: 1 |
13 | 13 |
tracker_id: 2 |
14 |
projects_trackers_005:
|
|
14 |
projects_trackers_005: |
|
15 | 15 |
project_id: 5 |
16 | 16 |
tracker_id: 2 |
17 |
projects_trackers_006:
|
|
17 |
projects_trackers_006: |
|
18 | 18 |
project_id: 5 |
19 | 19 |
tracker_id: 3 |
20 |
projects_trackers_007:
|
|
20 |
projects_trackers_007: |
|
21 | 21 |
project_id: 2 |
22 | 22 |
tracker_id: 1 |
23 |
projects_trackers_008:
|
|
23 |
projects_trackers_008: |
|
24 | 24 |
project_id: 2 |
25 | 25 |
tracker_id: 2 |
26 |
projects_trackers_009:
|
|
26 |
projects_trackers_009: |
|
27 | 27 |
project_id: 2 |
28 | 28 |
tracker_id: 3 |
29 |
projects_trackers_010:
|
|
29 |
projects_trackers_010: |
|
30 | 30 |
project_id: 3 |
31 | 31 |
tracker_id: 2 |
32 |
projects_trackers_011:
|
|
32 |
projects_trackers_011: |
|
33 | 33 |
project_id: 3 |
34 | 34 |
tracker_id: 3 |
35 |
projects_trackers_012:
|
|
35 |
projects_trackers_012: |
|
36 | 36 |
project_id: 4 |
37 | 37 |
tracker_id: 1 |
38 |
projects_trackers_013:
|
|
38 |
projects_trackers_013: |
|
39 | 39 |
project_id: 4 |
40 | 40 |
tracker_id: 2 |
41 |
projects_trackers_014:
|
|
41 |
projects_trackers_014: |
|
42 | 42 |
project_id: 1 |
43 | 43 |
tracker_id: 3 |
44 |
projects_trackers_015:
|
|
44 |
projects_trackers_015: |
|
45 | 45 |
project_id: 6 |
46 | 46 |
tracker_id: 1 |
47 | 47 |
projects_trackers_016: |
48 | 48 |
project_id: 3 |
49 | 49 |
tracker_id: 1 |
50 |
projects_trackers_017: |
|
51 |
project_id: 1 |
|
52 |
tracker_id: 4 |
test/fixtures/trackers.yml | ||
---|---|---|
1 |
---
|
|
2 |
trackers_001:
|
|
1 |
--- |
|
2 |
trackers_001: |
|
3 | 3 |
name: Bug |
4 | 4 |
id: 1 |
5 | 5 |
is_in_chlog: true |
6 | 6 |
default_status_id: 1 |
7 | 7 |
position: 1 |
8 | 8 |
description: Description for Bug tracker |
9 |
trackers_002: |
|
9 |
subtask_only: false |
|
10 |
trackers_002: |
|
10 | 11 |
name: Feature request |
11 | 12 |
id: 2 |
12 | 13 |
is_in_chlog: true |
13 | 14 |
default_status_id: 1 |
14 | 15 |
position: 2 |
15 | 16 |
description: Description for Feature request tracker |
16 |
trackers_003: |
|
17 |
subtask_only: false |
|
18 |
trackers_003: |
|
17 | 19 |
name: Support request |
18 | 20 |
id: 3 |
19 | 21 |
is_in_chlog: false |
20 | 22 |
default_status_id: 1 |
21 | 23 |
position: 3 |
24 |
subtask_only: false |
|
25 |
trackers_004: |
|
26 |
name: Subtask |
|
27 |
id: 4 |
|
28 |
is_in_chlog: false |
|
29 |
default_status_id: 1 |
|
30 |
position: 4 |
|
31 |
subtask_only: true |
test/functional/issues_controller_test.rb | ||
---|---|---|
3169 | 3169 |
get(:new, :params => {:project_id => 1}) |
3170 | 3170 |
assert_response :success |
3171 | 3171 |
assert_select 'select[name=?]', 'issue[tracker_id]' do |
3172 |
assert_select 'option', 3
|
|
3172 |
assert_select 'option', 4
|
|
3173 | 3173 |
assert_select 'option[value="1"][selected=selected]' |
3174 | 3174 |
end |
3175 | 3175 |
end |
... | ... | |
3190 | 3190 |
) |
3191 | 3191 |
assert_response :success |
3192 | 3192 |
assert_select 'select[name=?]', 'issue[tracker_id]' do |
3193 |
assert_select 'option', 2
|
|
3193 |
assert_select 'option', 3
|
|
3194 | 3194 |
assert_select 'option[value="2"][selected=selected]' |
3195 | 3195 |
assert_select 'option[value="1"]', 0 |
3196 | 3196 |
end |
test/functional/trackers_controller_test.rb | ||
---|---|---|
31 | 31 |
get :index |
32 | 32 |
assert_response :success |
33 | 33 |
assert_select 'table.trackers' |
34 | ||
35 |
# assert subtask column |
|
36 |
assert_select 'td.subtask .icon-checked', 1 |
|
34 | 37 |
end |
35 | 38 | |
36 | 39 |
def test_index_by_anonymous_should_redirect_to_login_form |
... | ... | |
49 | 52 |
get :new |
50 | 53 |
assert_response :success |
51 | 54 |
assert_select 'input[name=?]', 'tracker[name]' |
55 |
assert_select 'input[name=?]', 'tracker[subtask_only]' |
|
52 | 56 |
assert_select 'select[name=?]', 'tracker[default_status_id]' do |
53 | 57 |
assert_select 'option[value=?][selected=selected]', IssueStatus.sorted.first.id.to_s |
54 | 58 |
end |
... | ... | |
72 | 76 |
assert_equal Tracker::CORE_FIELDS, tracker.core_fields |
73 | 77 |
assert_equal [1, 6], tracker.custom_field_ids.sort |
74 | 78 |
assert_equal 0, tracker.workflow_rules.count |
79 |
assert !tracker.subtask_only |
|
75 | 80 |
end |
76 | 81 | |
77 | 82 |
def test_create_with_disabled_core_fields |
test/functional/workflows_controller_test.rb | ||
---|---|---|
246 | 246 |
end |
247 | 247 |
end |
248 | 248 | |
249 |
def test_get_permissions_with_role_and_subtask_tracker_should_mark_parent_task_field_required |
|
250 |
get :permissions, :params => {:role_id => 1, :tracker_id => 4} |
|
251 |
assert_response :success |
|
252 | ||
253 |
assert_select 'td.name', :text => 'Parent task *' |
|
254 |
end |
|
255 | ||
256 |
def test_get_permissions_with_role_and_multiple_subtask_trackers_should_mark_parent_task_field_required |
|
257 |
subtask = Tracker.find(1) |
|
258 |
subtask.subtask_only = true |
|
259 |
subtask.save! |
|
260 | ||
261 |
get :permissions, :params => {:role_id => 1, :tracker_id => [1, 4]} |
|
262 |
assert_response :success |
|
263 | ||
264 |
assert_select 'td.name', :text => 'Parent task *' |
|
265 |
end |
|
266 | ||
267 |
def test_get_permissions_with_role_and_mixed_trackers_should_not_mark_parent_task_field_required |
|
268 |
get :permissions, :params => {:role_id => 1, :tracker_id => [1, 4]} |
|
269 |
assert_response :success |
|
270 | ||
271 |
assert_select 'td.name', :text => 'Parent task' |
|
272 |
end |
|
273 | ||
249 | 274 |
def test_get_permissions_with_required_custom_field_should_not_show_required_option |
250 | 275 |
cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :tracker_ids => [1], :is_required => true) |
251 | 276 |
test/integration/api_test/trackers_test.rb | ||
---|---|---|
30 | 30 | |
31 | 31 |
assert_select 'trackers[type=array] tracker id', :text => '2' do |
32 | 32 |
assert_select '~ name', :text => 'Feature request' |
33 |
assert_select '~ subtask_only', :text => 'false' |
|
33 | 34 |
assert_select '~ description', :text => 'Description for Feature request tracker' |
34 | 35 |
end |
36 | ||
37 |
assert_select 'trackers[type=array] tracker id', :text => '4' do |
|
38 |
assert_select '~ subtask_only', :text => 'true' |
|
39 |
end |
|
35 | 40 |
end |
36 | 41 |
end |
test/unit/issue_test.rb | ||
---|---|---|
214 | 214 |
assert_include 'Parent task is invalid', issue.errors.full_messages |
215 | 215 |
end |
216 | 216 | |
217 |
def test_create_with_subtask_tracker |
|
218 |
issue = Issue.new(:project_id => 1, :tracker_id => 4, |
|
219 |
:author_id => 1, :subject => 'Group assignment', |
|
220 |
:parent_issue_id => 1) |
|
221 | ||
222 |
assert issue.save |
|
223 |
assert_equal 1, issue.parent_issue_id |
|
224 |
end |
|
225 | ||
226 |
def test_create_with_subtask_tracker_should_require_parent_task |
|
227 |
issue = Issue.new(:project_id => 1, :tracker_id => 4, |
|
228 |
:author_id => 1, :subject => 'Group assignment') |
|
229 | ||
230 |
assert !issue.save |
|
231 |
assert_include 'Parent task cannot be blank', issue.errors.full_messages |
|
232 |
end |
|
233 | ||
217 | 234 |
def assert_visibility_match(user, issues) |
218 | 235 |
assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort |
219 | 236 |
end |
... | ... | |
1689 | 1706 |
role.save! |
1690 | 1707 |
User.add_to_project(user, Project.find(1), role) |
1691 | 1708 | |
1692 |
assert_equal [1, 2, 3], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort |
|
1709 |
assert_equal [1, 2, 3, 4], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort
|
|
1693 | 1710 |
end |
1694 | 1711 | |
1695 | 1712 |
def test_allowed_target_trackers_with_one_role_allowed_on_some_trackers |
... | ... | |
1730 | 1747 |
role2.save! |
1731 | 1748 |
User.add_to_project(user, Project.find(1), [role1, role2]) |
1732 | 1749 | |
1733 |
assert_equal [1, 2, 3], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort |
|
1750 |
assert_equal [1, 2, 3, 4], Issue.new(:project => Project.find(1)).allowed_target_trackers(user).ids.sort
|
|
1734 | 1751 |
end |
1735 | 1752 | |
1736 | 1753 |
def test_allowed_target_trackers_should_not_consider_roles_without_add_issues_permission |
test/unit/lib/redmine/acts/positioned_without_scope_test.rb | ||
---|---|---|
26 | 26 |
t = Tracker.generate |
27 | 27 |
t.save! |
28 | 28 | |
29 |
assert_equal 4, t.reload.position
|
|
29 |
assert_equal 5, t.reload.position
|
|
30 | 30 |
end |
31 | 31 | |
32 | 32 |
def test_create_should_insert_at_given_position |
... | ... | |
35 | 35 |
t.save! |
36 | 36 | |
37 | 37 |
assert_equal 2, t.reload.position |
38 |
assert_equal [1, 3, 4, 2], Tracker.order(:id).pluck(:position) |
|
38 |
assert_equal [1, 3, 4, 5, 2], Tracker.order(:id).pluck(:position)
|
|
39 | 39 |
end |
40 | 40 | |
41 | 41 |
def test_destroy_should_remove_position |
... | ... | |
43 | 43 |
Tracker.generate! |
44 | 44 |
t.destroy |
45 | 45 | |
46 |
assert_equal [1, 2, 3, 4], Tracker.order(:id).pluck(:position) |
|
46 |
assert_equal [1, 2, 3, 4, 5], Tracker.order(:id).pluck(:position)
|
|
47 | 47 |
end |
48 | 48 | |
49 | 49 |
def test_update_should_update_positions |
50 | 50 |
t = Tracker.generate! |
51 |
assert_equal 4, t.position
|
|
51 |
assert_equal 5, t.position
|
|
52 | 52 | |
53 | 53 |
t.position = 2 |
54 | 54 |
t.save! |
55 |
assert_equal [1, 3, 4, 2], Tracker.order(:id).pluck(:position) |
|
55 |
assert_equal [1, 3, 4, 5, 2], Tracker.order(:id).pluck(:position)
|
|
56 | 56 |
end |
57 | 57 |
end |
test/unit/tracker_test.rb | ||
---|---|---|
140 | 140 |
assert tracker.respond_to?(:description) |
141 | 141 |
assert_equal tracker.description, "Description for Bug tracker" |
142 | 142 |
end |
143 | ||
144 |
def test_set_tracker_subtask_only |
|
145 |
tracker = Tracker.find(1) |
|
146 |
tracker.subtask_only = true |
|
147 | ||
148 |
assert tracker.save! |
|
149 |
assert tracker.subtask_only? |
|
150 |
end |
|
143 | 151 |
end |