Feature #22802 » manual_set_issue_position_in_versions_v3.patch
app/controllers/issues_controller.rb | ||
---|---|---|
192 | 192 | |
193 | 193 |
respond_to do |format| |
194 | 194 |
format.html { redirect_back_or_default issue_path(@issue, previous_and_next_issue_ids_params) } |
195 |
format.js { render :nothing => true } |
|
195 | 196 |
format.api { render_api_ok } |
196 | 197 |
end |
197 | 198 |
else |
198 | 199 |
respond_to do |format| |
199 | 200 |
format.html { render :action => 'edit' } |
201 |
format.js { render :nothing => true, :status => 422 } |
|
200 | 202 |
format.api { render_validation_errors(@issue) } |
201 | 203 |
end |
202 | 204 |
end |
... | ... | |
380 | 382 |
# Overrides Redmine::MenuManager::MenuController::ClassMethods for |
381 | 383 |
# when the "New issue" tab is enabled |
382 | 384 |
def current_menu_item |
383 |
if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym)
|
|
385 |
if Setting.new_item_menu_tab == '1' && [:new, :create].include?(action_name.to_sym) |
|
384 | 386 |
:new_issue |
385 | 387 |
else |
386 | 388 |
super |
app/controllers/versions_controller.rb | ||
---|---|---|
46 | 46 | |
47 | 47 |
@issues_by_version = {} |
48 | 48 |
if @selected_tracker_ids.any? && @versions.any? |
49 | ||
49 | 50 |
issues = Issue.visible. |
50 | 51 |
includes(:project, :tracker). |
51 | 52 |
preload(:status, :priority, :fixed_version). |
52 | 53 |
where(:tracker_id => @selected_tracker_ids, :project_id => project_ids, :fixed_version_id => @versions.map(&:id)). |
53 |
order("#{Project.table_name}.lft, #{Tracker.table_name}.position, #{Issue.table_name}.id")
|
|
54 |
order(order_issues_by)
|
|
54 | 55 |
@issues_by_version = issues.group_by(&:fixed_version) |
55 | 56 |
end |
56 | 57 |
@versions.reject! {|version| !project_ids.include?(version.project_id) && @issues_by_version[version].blank?} |
... | ... | |
67 | 68 |
@issues = @version.fixed_issues.visible. |
68 | 69 |
includes(:status, :tracker, :priority). |
69 | 70 |
preload(:project). |
70 |
reorder("#{Tracker.table_name}.position, #{Issue.table_name}.id").
|
|
71 |
reorder(order_issues_by).
|
|
71 | 72 |
to_a |
72 | 73 |
} |
73 | 74 |
format.api |
... | ... | |
180 | 181 |
@selected_tracker_ids = (default_trackers || selectable_trackers).collect {|t| t.id.to_s } |
181 | 182 |
end |
182 | 183 |
end |
184 | ||
185 |
def order_issues_by |
|
186 |
if Setting.manual_issue_position_in_versions == '1' |
|
187 |
return "COALESCE(#{Issue.table_name}.position, 999999)" |
|
188 |
else |
|
189 |
return "#{Tracker.table_name}.position, #{Issue.table_name}.id" |
|
190 |
end |
|
191 |
end |
|
183 | 192 |
end |
app/models/issue.rb | ||
---|---|---|
56 | 56 | |
57 | 57 |
acts_as_activity_provider :scope => preload(:project, :author, :tracker, :status), |
58 | 58 |
:author_key => :author_id |
59 | ||
59 |
acts_as_positioned :scope => [:fixed_version_id] |
|
60 | 60 |
DONE_RATIO_OPTIONS = %w(issue_field issue_status) |
61 | 61 | |
62 | 62 |
attr_accessor :deleted_attachment_ids |
... | ... | |
468 | 468 |
:if => lambda {|issue, user| (issue.new_record? || issue.attributes_editable?(user)) && |
469 | 469 |
user.allowed_to?(:manage_subtasks, issue.project)} |
470 | 470 | |
471 |
safe_attributes 'position', |
|
472 |
:if => lambda {|issue, user| user.allowed_to?(:change_issue_position_in_version, issue.project)} |
|
473 | ||
471 | 474 |
safe_attributes 'deleted_attachment_ids', |
472 | 475 |
:if => lambda {|issue, user| issue.attachments_deletable?(user)} |
473 | 476 | |
... | ... | |
781 | 784 | |
782 | 785 |
# Returns the names of attributes that are journalized when updating the issue |
783 | 786 |
def journalized_attribute_names |
784 |
names = Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on) |
|
787 |
names = Issue.column_names - %w(id root_id lft rgt lock_version position created_on updated_on closed_on)
|
|
785 | 788 |
if tracker |
786 | 789 |
names -= tracker.disabled_core_fields |
787 | 790 |
end |
app/views/settings/_issues.html.erb | ||
---|---|---|
13 | 13 | |
14 | 14 |
<p><%= setting_check_box :display_subprojects_issues %></p> |
15 | 15 | |
16 |
<p><%= setting_check_box :manual_issue_position_in_versions %></p> |
|
17 | ||
16 | 18 |
<p><%= setting_select :issue_done_ratio, Issue::DONE_RATIO_OPTIONS.collect {|i| [l("setting_issue_done_ratio_#{i}"), i]} %></p> |
17 | 19 | |
18 | 20 |
<p><%= setting_multiselect :non_working_week_days, (1..7).map {|d| [day_name(d), d.to_s]}, :inline => true %></p> |
... | ... | |
20 | 22 |
<p><%= setting_text_field :issues_export_limit, :size => 6 %></p> |
21 | 23 | |
22 | 24 |
<p><%= setting_text_field :gantt_items_limit, :size => 6 %></p> |
25 | ||
23 | 26 |
</div> |
24 | 27 | |
25 | 28 |
<fieldset class="box"> |
app/views/versions/index.html.erb | ||
---|---|---|
26 | 26 |
<%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%> |
27 | 27 |
<table class="list related-issues"> |
28 | 28 |
<caption><%= l(:label_related_issues) %></caption> |
29 |
<% issues.each do |issue| -%> |
|
30 |
<tr class="hascontextmenu"> |
|
31 |
<td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td> |
|
32 |
<td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td> |
|
33 |
</tr> |
|
34 |
<% end -%> |
|
29 |
<tbody> |
|
30 |
<% issues.each do |issue| -%> |
|
31 |
<tr class="hascontextmenu"> |
|
32 |
<td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td> |
|
33 |
<td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td> |
|
34 |
<% if Setting.manual_issue_position_in_versions == '1' && User.current.allowed_to?(:change_issue_position_in_version, version.project) %> |
|
35 |
<td class="sortable"><%= reorder_handle(issue) %></td> |
|
36 |
<% end%> |
|
37 |
</tr> |
|
38 |
<% end -%> |
|
39 |
</tbody> |
|
35 | 40 |
</table> |
36 | 41 |
<% end %> |
37 | 42 |
<% end %> |
... | ... | |
100 | 105 |
<% html_title(l(:label_roadmap)) %> |
101 | 106 | |
102 | 107 |
<%= context_menu %> |
108 | ||
109 |
<% if Setting.manual_issue_position_in_versions == '1' && User.current.allowed_to?(:change_issue_position_in_version, @project) %> |
|
110 |
<%= javascript_tag do %> |
|
111 |
$(function() { $("table.related-issues tbody").positionedItems(); }); |
|
112 |
<% end %> |
|
113 |
<% end %> |
app/views/versions/show.html.erb | ||
---|---|---|
38 | 38 |
<% if @issues.present? %> |
39 | 39 |
<%= form_tag({}, :data => {:cm_url => issues_context_menu_path}) do -%> |
40 | 40 |
<table class="list related-issues"> |
41 |
<caption><%= l(:label_related_issues) %></caption> |
|
42 |
<%- @issues.each do |issue| -%> |
|
43 |
<tr class="issue hascontextmenu"> |
|
44 |
<td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td> |
|
45 |
<td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td> |
|
46 |
</tr> |
|
47 |
<% end %> |
|
41 |
<caption><%= l(:label_related_issues) %></caption> |
|
42 |
<tbody> |
|
43 |
<%- @issues.each do |issue| -%> |
|
44 |
<tr class="issue hascontextmenu"> |
|
45 |
<td class="checkbox"><%= check_box_tag 'ids[]', issue.id, false, :id => nil %></td> |
|
46 |
<td class="subject"><%= link_to_issue(issue, :project => (@project != issue.project)) %></td> |
|
47 |
<% if Setting.manual_issue_position_in_versions == '1' && !@version.closed? && User.current.allowed_to?(:change_issue_position_in_version, @version.project) %> |
|
48 |
<td class="sortable"><%= reorder_handle(issue) %></td> |
|
49 |
<% end%> |
|
50 |
</tr> |
|
51 |
<% end %> |
|
52 |
</tbody> |
|
48 | 53 |
</table> |
49 | 54 |
<% end %> |
50 | 55 |
<%= context_menu %> |
... | ... | |
54 | 59 |
<%= call_hook :view_versions_show_bottom, :version => @version %> |
55 | 60 | |
56 | 61 |
<% html_title @version.name %> |
62 | ||
63 |
<% if Setting.manual_issue_position_in_versions == '1' && !@version.closed? && User.current.allowed_to?(:change_issue_position_in_version, @version.project) %> |
|
64 |
<%= javascript_tag do %> |
|
65 |
$(function() { $("table.related-issues tbody").positionedItems(); }); |
|
66 |
<% end %> |
|
67 |
<% end %> |
config/locales/en.yml | ||
---|---|---|
448 | 448 |
setting_attachment_extensions_allowed: Allowed extensions |
449 | 449 |
setting_attachment_extensions_denied: Disallowed extensions |
450 | 450 |
setting_new_item_menu_tab: Project menu tab for creating new objects |
451 |
setting_manual_issue_position_in_versions: Enable manually set issue position in versions |
|
451 | 452 | |
452 | 453 |
permission_add_project: Create project |
453 | 454 |
permission_add_subprojects: Create subprojects |
... | ... | |
514 | 515 |
permission_manage_subtasks: Manage subtasks |
515 | 516 |
permission_manage_related_issues: Manage related issues |
516 | 517 |
permission_import_issues: Import issues |
518 |
permission_change_issue_position_in_version: Change issue position in version |
|
517 | 519 | |
518 | 520 |
project_module_issue_tracking: Issue tracking |
519 | 521 |
project_module_time_tracking: Time tracking |
config/settings.yml | ||
---|---|---|
171 | 171 |
default: 'derived' |
172 | 172 |
link_copied_issue: |
173 | 173 |
default: 'ask' |
174 |
manual_issue_position_in_versions: |
|
175 |
default: 0 |
|
174 | 176 |
issue_group_assignment: |
175 | 177 |
default: 0 |
176 | 178 |
default_issue_start_date_to_creation_date: |
db/migrate/20160901154541_add_issue_position.rb | ||
---|---|---|
1 |
class AddIssuePosition < ActiveRecord::Migration |
|
2 |
def up |
|
3 |
add_column :issues, :position, :integer |
|
4 |
end |
|
5 | ||
6 |
def down |
|
7 |
remove_column :issues, :position |
|
8 |
end |
|
9 |
end |
lib/redmine.rb | ||
---|---|---|
110 | 110 |
map.permission :view_private_notes, {}, :read => true, :require => :member |
111 | 111 |
map.permission :set_notes_private, {}, :require => :member |
112 | 112 |
map.permission :delete_issues, {:issues => :destroy}, :require => :member |
113 |
map.permission :change_issue_position_in_version, {} |
|
113 | 114 |
# Queries |
114 | 115 |
map.permission :manage_public_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :member |
115 | 116 |
map.permission :save_queries, {:queries => [:new, :create, :edit, :update, :destroy]}, :require => :loggedin |
public/stylesheets/application.css | ||
---|---|---|
442 | 442 | |
443 | 443 |
div#roadmap .related-issues { margin-bottom: 1em; } |
444 | 444 |
div#roadmap .related-issues td.checkbox { display: none; } |
445 |
div#roadmap .related-issues td.sortable { text-align: right; } |
|
445 | 446 |
div#roadmap .wiki h1:first-child { display: none; } |
446 | 447 |
div#roadmap .wiki h1 { font-size: 120%; } |
447 | 448 |
div#roadmap .wiki h2 { font-size: 110%; } |
test/fixtures/issues.yml | ||
---|---|---|
40 | 40 |
rgt: 2 |
41 | 41 |
lock_version: 3 |
42 | 42 |
done_ratio: 30 |
43 |
position: 1 |
|
43 | 44 |
issues_003: |
44 | 45 |
created_on: 2006-07-19 21:07:27 +02:00 |
45 | 46 |
project_id: 1 |
test/functional/versions_controller_test.rb | ||
---|---|---|
77 | 77 |
assert_select 'h3', :text => /Subproject version/ |
78 | 78 |
end |
79 | 79 | |
80 |
def test_issues_order_when_manual_issue_position_in_versions_is_disabled |
|
81 |
with_settings :manual_issue_position_in_versions => '0' do |
|
82 |
get :index, :params => {:project_id => 1} |
|
83 |
assert_response :success |
|
84 | ||
85 |
assert_select '#roadmap article:first-child' do |
|
86 |
assert_select '.related-issues tr:nth-child(1) a[href=?]', '/issues/12', :text => 'Bug #12' |
|
87 |
assert_select '.related-issues tr:nth-child(2) a[href=?]', '/issues/2', :text => 'Feature request #2' |
|
88 |
end |
|
89 | ||
90 |
get :show, :params => { :id => 2 } |
|
91 |
assert_response :success |
|
92 | ||
93 |
assert_select '.related-issues' do |
|
94 |
assert_select 'tr:nth-child(1) a[href=?]', '/issues/12', :text => 'Bug #12' |
|
95 |
assert_select 'tr:nth-child(2) a[href=?]', '/issues/2', :text => 'Feature request #2' |
|
96 |
end |
|
97 |
end |
|
98 |
end |
|
99 | ||
100 |
def test_issues_order_when_manual_issue_position_in_versions_is_enabled |
|
101 |
with_settings :manual_issue_position_in_versions => '1' do |
|
102 |
get :index, :params => {:project_id => 1} |
|
103 |
assert_response :success |
|
104 | ||
105 |
assert_select '#roadmap article:first-child' do |
|
106 |
assert_select '.related-issues tr:nth-child(1) a[href=?]', '/issues/2', :text => 'Feature request #2' |
|
107 |
assert_select '.related-issues tr:nth-child(2) a[href=?]', '/issues/12', :text => 'Bug #12' |
|
108 |
end |
|
109 | ||
110 |
get :show, :params => { :id => 2 } |
|
111 |
assert_response :success |
|
112 | ||
113 |
assert_select '.related-issues' do |
|
114 |
assert_select 'tr:nth-child(1) a[href=?]', '/issues/2', :text => 'Feature request #2' |
|
115 |
assert_select 'tr:nth-child(2) a[href=?]', '/issues/12', :text => 'Bug #12' |
|
116 |
end |
|
117 |
end |
|
118 |
end |
|
119 | ||
120 |
def test_user_with_permission_can_change_issue_position |
|
121 |
role = Role.find(1) |
|
122 |
role.add_permission! :change_issue_position_in_version |
|
123 | ||
124 |
@request.session[:user_id] = 2 |
|
125 |
with_settings :manual_issue_position_in_versions => '1' do |
|
126 |
get :index, :params => {:project_id => 1} |
|
127 |
assert_response :success |
|
128 | ||
129 |
assert_select '#roadmap article' do |
|
130 |
assert_select '.related-issues tr td.sortable', 2 |
|
131 |
end |
|
132 | ||
133 |
get :show, :params => { :id => 2 } |
|
134 |
assert_response :success |
|
135 | ||
136 |
assert_select '.related-issues' do |
|
137 |
assert_select '.related-issues tr td.sortable', 2 |
|
138 |
end |
|
139 |
end |
|
140 |
end |
|
141 | ||
142 |
def test_user_without_permission_cannot_change_issue_position |
|
143 |
@request.session[:user_id] = 2 |
|
144 |
with_settings :manual_issue_position_in_versions => '1' do |
|
145 |
get :index, :params => {:project_id => 1} |
|
146 |
assert_response :success |
|
147 | ||
148 |
assert_select '#roadmap article' do |
|
149 |
assert_select '.related-issues tr td.sortable', 0 |
|
150 |
end |
|
151 | ||
152 |
get :show, :params => { :id => 2 } |
|
153 |
assert_response :success |
|
154 | ||
155 |
assert_select '.related-issues' do |
|
156 |
assert_select '.related-issues tr td.sortable', 0 |
|
157 |
end |
|
158 |
end |
|
159 |
end |
|
160 | ||
80 | 161 |
def test_index_should_prepend_shared_versions |
81 | 162 |
get :index, :params => {:project_id => 1} |
82 | 163 |
assert_response :success |