Feature #44166 » tracker_lock_feature.patch
| app/controllers/projects_controller.rb | ||
|---|---|---|
| 96 | 96 | |
| 97 | 97 |
def new |
| 98 | 98 |
@issue_custom_fields = IssueCustomField.sorted.to_a |
| 99 |
@trackers = Tracker.sorted.to_a |
|
| 99 |
@trackers = Tracker.active.sorted.to_a
|
|
| 100 | 100 |
@project = Project.new |
| 101 | 101 |
@project.safe_attributes = params[:project] |
| 102 | 102 |
end |
| 103 | 103 | |
| 104 | 104 |
def create |
| 105 | 105 |
@issue_custom_fields = IssueCustomField.sorted.to_a |
| 106 |
@trackers = Tracker.sorted.to_a |
|
| 106 |
@trackers = Tracker.active.sorted.to_a
|
|
| 107 | 107 |
@project = Project.new |
| 108 | 108 |
@project.safe_attributes = params[:project] |
| 109 | 109 | |
| ... | ... | |
| 140 | 140 | |
| 141 | 141 |
def copy |
| 142 | 142 |
@issue_custom_fields = IssueCustomField.sorted.to_a |
| 143 |
@trackers = Tracker.sorted.to_a |
|
| 143 |
@trackers = Tracker.active.sorted.to_a
|
|
| 144 | 144 |
@source_project = Project.find(params[:id]) |
| 145 | 145 |
if request.get? |
| 146 | 146 |
@project = Project.copy_from(@source_project) |
| ... | ... | |
| 201 | 201 |
@issue_custom_fields = IssueCustomField.sorted.to_a |
| 202 | 202 |
@issue_category ||= IssueCategory.new |
| 203 | 203 |
@member ||= @project.members.new |
| 204 |
@trackers = Tracker.sorted.to_a |
|
| 204 |
# Show active trackers plus any inactive trackers already assigned to this project |
|
| 205 |
@trackers = Tracker.sorted.where(:active => true).or( |
|
| 206 |
Tracker.sorted.where(:id => @project.tracker_ids) |
|
| 207 |
).sorted.to_a |
|
| 205 | 208 | |
| 206 | 209 |
@version_status = params[:version_status] || 'open' |
| 207 | 210 |
@version_name = params[:version_name] |
| app/controllers/trackers_controller.rb | ||
|---|---|---|
| 86 | 86 |
end |
| 87 | 87 |
end |
| 88 | 88 | |
| 89 |
def lock |
|
| 90 |
@tracker = Tracker.find(params[:id]) |
|
| 91 |
@tracker.update!(:active => false) |
|
| 92 |
redirect_to trackers_path |
|
| 93 |
end |
|
| 94 | ||
| 95 |
def unlock |
|
| 96 |
@tracker = Tracker.find(params[:id]) |
|
| 97 |
@tracker.update!(:active => true) |
|
| 98 |
redirect_to trackers_path |
|
| 99 |
end |
|
| 100 | ||
| 89 | 101 |
def destroy |
| 90 | 102 |
@tracker = Tracker.find(params[:id]) |
| 91 | 103 |
if @tracker.issues.empty? |
| app/models/issue.rb | ||
|---|---|---|
| 1695 | 1695 |
# Returns a scope of trackers that user can assign project issues to |
| 1696 | 1696 |
def self.allowed_target_trackers(project, user=User.current, current_tracker=nil) |
| 1697 | 1697 |
if project |
| 1698 |
scope = project.trackers.sorted |
|
| 1698 |
scope = project.trackers.sorted.active |
|
| 1699 |
# Always include the current tracker even if it is inactive |
|
| 1700 |
if current_tracker |
|
| 1701 |
scope = Tracker.sorted.where( |
|
| 1702 |
:id => scope.pluck(:id) | [current_tracker] |
|
| 1703 |
) |
|
| 1704 |
end |
|
| 1699 | 1705 |
unless user.admin? |
| 1700 | 1706 |
roles = user.roles_for_project(project).select {|r| r.has_permission?(:add_issues)}
|
| 1701 | 1707 |
unless roles.any? {|r| r.permissions_all_trackers?(:add_issues)}
|
| ... | ... | |
| 1810 | 1816 |
copy.author = author |
| 1811 | 1817 |
copy.project = project |
| 1812 | 1818 |
copy.parent_issue_id = copied_issue_ids[child.parent_id] |
| 1813 |
unless child.fixed_version.present? && child.fixed_version.status == 'open' |
|
| 1819 |
if fixed_version_id != @copied_from.fixed_version_id |
|
| 1820 |
copy.fixed_version_id = fixed_version_id |
|
| 1821 |
elsif !(child.fixed_version.present? && child.fixed_version.status == 'open') |
|
| 1814 | 1822 |
copy.fixed_version_id = nil |
| 1815 | 1823 |
end |
| 1816 | 1824 |
unless child.assigned_to_id.present? && |
| app/models/project.rb | ||
|---|---|---|
| 139 | 139 |
if !initialized.key?('trackers') && !initialized.key?('tracker_ids')
|
| 140 | 140 |
default = Setting.default_projects_tracker_ids |
| 141 | 141 |
if default.is_a?(Array) |
| 142 |
self.trackers = Tracker.where(:id => default.map(&:to_i)).sorted.to_a |
|
| 142 |
self.trackers = Tracker.where(:id => default.map(&:to_i)).active.sorted.to_a
|
|
| 143 | 143 |
else |
| 144 |
self.trackers = Tracker.sorted.to_a |
|
| 144 |
self.trackers = Tracker.active.sorted.to_a
|
|
| 145 | 145 |
end |
| 146 | 146 |
end |
| 147 | 147 |
# rubocop:enable Style/NegatedIf |
| app/models/tracker.rb | ||
|---|---|---|
| 46 | 46 | |
| 47 | 47 |
scope :sorted, lambda {order(:position)}
|
| 48 | 48 |
scope :named, lambda {|arg| where("LOWER(#{table_name}.name) = LOWER(?)", arg.to_s.strip)}
|
| 49 |
scope :active, lambda {where(:active => true)}
|
|
| 49 | 50 | |
| 50 | 51 |
# Returns the trackers that are visible by the user. |
| 51 | 52 |
# |
| ... | ... | |
| 75 | 76 |
'default_status_id', |
| 76 | 77 |
'is_in_roadmap', |
| 77 | 78 |
'private_by_default', |
| 79 |
'active', |
|
| 78 | 80 |
'core_fields', |
| 79 | 81 |
'position', |
| 80 | 82 |
'custom_field_ids', |
| app/views/trackers/index.html.erb | ||
|---|---|---|
| 11 | 11 |
<th><%=l(:field_default_status)%></th> |
| 12 | 12 |
<th><%=l(:field_description)%></th> |
| 13 | 13 |
<th></th> |
| 14 |
<th></th> |
|
| 14 |
<th class="tracker-buttons-col"></th>
|
|
| 15 | 15 |
</tr></thead> |
| 16 | 16 |
<tbody> |
| 17 | 17 |
<% for tracker in @trackers %> |
| 18 |
<tr> |
|
| 18 |
<tr class="<%= tracker.active? ? '' : 'tracker-locked' %>">
|
|
| 19 | 19 |
<td class="name"><%= link_to tracker.name, edit_tracker_path(tracker) %></td> |
| 20 | 20 |
<td><%= tracker.default_status.name %></td> |
| 21 | 21 |
<td class="description"><%= tracker.description %></td> |
| ... | ... | |
| 26 | 26 |
</span> |
| 27 | 27 |
<% end %> |
| 28 | 28 |
</td> |
| 29 |
<td class="buttons"> |
|
| 29 |
<td class="buttons tracker-buttons-col">
|
|
| 30 | 30 |
<%= reorder_handle(tracker) %> |
| 31 | 31 |
<%= link_to sprite_icon('copy', l(:button_copy)), new_tracker_path(:copy => tracker), :class => 'icon icon-copy' %>
|
| 32 |
<% if tracker.active? %> |
|
| 33 |
<%= link_to sprite_icon('lock', l(:button_lock)), lock_tracker_path(tracker), :method => :put, :class => 'icon icon-lock' %>
|
|
| 34 |
<% else %> |
|
| 35 |
<%= link_to sprite_icon('unlock', l(:button_unlock)), unlock_tracker_path(tracker), :method => :put, :class => 'icon icon-unlock' %>
|
|
| 36 |
<% end %> |
|
| 32 | 37 |
<%= delete_link tracker_path(tracker) %> |
| 33 | 38 |
</td> |
| 34 | 39 |
</tr> |
| ... | ... | |
| 36 | 41 |
</tbody> |
| 37 | 42 |
</table> |
| 38 | 43 | |
| 44 |
<style> |
|
| 45 |
tr.tracker-locked td { opacity: 0.6; }
|
|
| 46 |
table.trackers th.tracker-buttons-col, |
|
| 47 |
table.trackers td.tracker-buttons-col { width: 1%; white-space: nowrap; }
|
|
| 48 |
table.trackers td.tracker-buttons-col a.icon-lock, |
|
| 49 |
table.trackers td.tracker-buttons-col a.icon-unlock { display: inline-block; min-width: 82px; }
|
|
| 50 |
</style> |
|
| 51 | ||
| 39 | 52 |
<% html_title(l(:label_tracker_plural)) -%> |
| 40 | 53 | |
| 41 | 54 |
<%= javascript_tag do %> |
| config/locales/en.yml | ||
|---|---|---|
| 672 | 672 |
label_tracker_plural: Trackers |
| 673 | 673 |
label_tracker_all: All trackers |
| 674 | 674 |
label_tracker_new: New tracker |
| 675 |
label_tracker_locked: Locked |
|
| 675 | 676 |
label_workflow: Workflow |
| 676 | 677 |
label_issue_status: Issue status |
| 677 | 678 |
label_issue_status_plural: Issue statuses |
| config/locales/ja.yml | ||
|---|---|---|
| 1201 | 1201 |
label_no_preview: このファイルはプレビューできません |
| 1202 | 1202 |
error_no_tracker_allowed_for_new_issue_in_project: このプロジェクトにはチケットの追加が許可されているトラッカーがありません |
| 1203 | 1203 |
label_tracker_all: すべてのトラッカー |
| 1204 |
label_tracker_locked: ロック済み |
|
| 1204 | 1205 |
label_new_project_issue_tab_enabled: '"新しいチケット" タブを表示' |
| 1205 | 1206 |
setting_new_item_menu_tab: 新規オブジェクト作成タブ |
| 1206 | 1207 |
label_new_object_tab_enabled: '"+" ドロップダウンを表示' |
| config/routes.rb | ||
|---|---|---|
| 350 | 350 |
delete 'groups/:id/users/:user_id', :to => 'groups#remove_user', :id => /\d+/, :as => 'group_user' |
| 351 | 351 | |
| 352 | 352 |
resources :trackers, :except => :show do |
| 353 |
member do |
|
| 354 |
put 'lock' |
|
| 355 |
put 'unlock' |
|
| 356 |
end |
|
| 353 | 357 |
collection do |
| 354 | 358 |
match 'fields', :via => [:get, :post] |
| 355 | 359 |
end |
| db/migrate/20260612000000_add_active_to_trackers.rb | ||
|---|---|---|
| 1 |
class AddActiveToTrackers < ActiveRecord::Migration[8.1] |
|
| 2 |
def change |
|
| 3 |
add_column :trackers, :active, :boolean, :default => true, :null => false |
|
| 4 |
end |
|
| 5 |
end |
|
| test/fixtures/trackers.yml | ||
|---|---|---|
| 4 | 4 |
id: 1 |
| 5 | 5 |
default_status_id: 1 |
| 6 | 6 |
position: 1 |
| 7 |
active: true |
|
| 7 | 8 |
description: Description for Bug tracker |
| 8 | 9 |
trackers_002: |
| 9 | 10 |
name: Feature request |
| 10 | 11 |
id: 2 |
| 11 | 12 |
default_status_id: 1 |
| 12 | 13 |
position: 2 |
| 14 |
active: true |
|
| 13 | 15 |
description: Description for Feature request tracker |
| 14 | 16 |
trackers_003: |
| 15 | 17 |
name: Support request |
| 16 | 18 |
id: 3 |
| 17 | 19 |
default_status_id: 1 |
| 18 | 20 |
position: 3 |
| 21 |
active: true |
|
| test/functional/trackers_controller_test.rb | ||
|---|---|---|
| 304 | 304 |
end |
| 305 | 305 |
end |
| 306 | 306 | |
| 307 |
def test_lock |
|
| 308 |
tracker = Tracker.find(1) |
|
| 309 |
assert tracker.active? |
|
| 310 |
put :lock, :params => {:id => 1}
|
|
| 311 |
assert_redirected_to :action => 'index' |
|
| 312 |
assert_not tracker.reload.active? |
|
| 313 |
end |
|
| 314 | ||
| 315 |
def test_unlock |
|
| 316 |
tracker = Tracker.find(1) |
|
| 317 |
tracker.update!(active: false) |
|
| 318 |
put :unlock, :params => {:id => 1}
|
|
| 319 |
assert_redirected_to :action => 'index' |
|
| 320 |
assert tracker.reload.active? |
|
| 321 |
end |
|
| 322 | ||
| 323 |
def test_lock_requires_admin |
|
| 324 |
@request.session[:user_id] = 2 |
|
| 325 |
put :lock, :params => {:id => 1}
|
|
| 326 |
assert_response :forbidden |
|
| 327 |
end |
|
| 328 | ||
| 329 |
def test_index_shows_lock_button_for_active_tracker |
|
| 330 |
get :index |
|
| 331 |
assert_response :success |
|
| 332 |
assert_select 'a[href=?]', lock_tracker_path(1) |
|
| 333 |
end |
|
| 334 | ||
| 335 |
def test_index_shows_unlock_button_for_locked_tracker |
|
| 336 |
Tracker.find(1).update!(active: false) |
|
| 337 |
get :index |
|
| 338 |
assert_response :success |
|
| 339 |
assert_select 'a[href=?]', unlock_tracker_path(1) |
|
| 340 |
end |
|
| 341 | ||
| 307 | 342 |
def test_post_fields |
| 308 | 343 |
post :fields, :params => {
|
| 309 | 344 |
:trackers => {
|
| test/unit/issue_test.rb | ||
|---|---|---|
| 1539 | 1539 |
assert_equal [3, nil], copy.children.map(&:fixed_version_id) |
| 1540 | 1540 |
end |
| 1541 | 1541 | |
| 1542 |
def test_copy_should_propagate_version_change_to_subtasks |
|
| 1543 |
parent = Issue.generate!(:fixed_version_id => 3) |
|
| 1544 |
child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1', :fixed_version_id => 3) |
|
| 1545 |
child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2', :fixed_version_id => nil) |
|
| 1546 | ||
| 1547 |
new_version = Version.generate!(:project => Project.find(1)) |
|
| 1548 |
copy = parent.reload.copy(:fixed_version_id => new_version.id) |
|
| 1549 | ||
| 1550 |
assert_difference 'Issue.count', 3 do |
|
| 1551 |
assert copy.save |
|
| 1552 |
end |
|
| 1553 | ||
| 1554 |
assert_equal new_version.id, copy.fixed_version_id |
|
| 1555 |
assert_equal [new_version.id, new_version.id], copy.children.map(&:fixed_version_id) |
|
| 1556 |
end |
|
| 1557 | ||
| 1542 | 1558 |
def test_copy_should_clear_subtasks_assignee_if_is_locked |
| 1543 | 1559 |
user = User.find(2) |
| 1544 | 1560 | |
| ... | ... | |
| 1909 | 1925 |
assert_equal [], issue.allowed_target_trackers(User.find(2)).ids |
| 1910 | 1926 |
end |
| 1911 | 1927 | |
| 1928 |
def test_allowed_target_trackers_excludes_locked_trackers |
|
| 1929 |
Tracker.find(1).update!(active: false) |
|
| 1930 |
issue = Issue.new(:project => Project.find(1)) |
|
| 1931 |
ids = Issue.allowed_target_trackers(Project.find(1), User.find(1)).ids |
|
| 1932 |
assert_not_includes ids, 1 |
|
| 1933 |
assert_includes ids, 2 |
|
| 1934 |
assert_includes ids, 3 |
|
| 1935 |
end |
|
| 1936 | ||
| 1937 |
def test_allowed_target_trackers_includes_current_tracker_even_if_locked |
|
| 1938 |
tracker = Tracker.find(1) |
|
| 1939 |
tracker.update!(active: false) |
|
| 1940 |
issue = Issue.generate!(:project => Project.find(1), :tracker => tracker) |
|
| 1941 |
ids = issue.allowed_target_trackers(User.find(1)).ids |
|
| 1942 |
assert_includes ids, 1 |
|
| 1943 |
end |
|
| 1944 | ||
| 1912 | 1945 |
def test_allowed_target_trackers_should_include_current_tracker |
| 1913 | 1946 |
user = User.generate! |
| 1914 | 1947 |
role = Role.generate! |
| test/unit/tracker_test.rb | ||
|---|---|---|
| 167 | 167 |
assert tracker.respond_to?(:description) |
| 168 | 168 |
assert_equal tracker.description, "Description for Bug tracker" |
| 169 | 169 |
end |
| 170 | ||
| 171 |
def test_active_scope_returns_only_active_trackers |
|
| 172 |
Tracker.find(1).update!(active: false) |
|
| 173 |
active_ids = Tracker.active.pluck(:id) |
|
| 174 |
assert_not_includes active_ids, 1 |
|
| 175 |
assert_includes active_ids, 2 |
|
| 176 |
assert_includes active_ids, 3 |
|
| 177 |
end |
|
| 178 | ||
| 179 |
def test_tracker_is_active_by_default |
|
| 180 |
tracker = Tracker.new(:name => 'New tracker', :default_status_id => 1) |
|
| 181 |
assert tracker.active? |
|
| 182 |
end |
|
| 183 | ||
| 184 |
def test_lock_tracker |
|
| 185 |
tracker = Tracker.find(1) |
|
| 186 |
assert tracker.active? |
|
| 187 |
tracker.update!(active: false) |
|
| 188 |
assert_not tracker.reload.active? |
|
| 189 |
end |
|
| 190 | ||
| 191 |
def test_unlock_tracker |
|
| 192 |
tracker = Tracker.find(1) |
|
| 193 |
tracker.update!(active: false) |
|
| 194 |
tracker.update!(active: true) |
|
| 195 |
assert tracker.reload.active? |
|
| 196 |
end |
|
| 170 | 197 |
end |