Feature #22802 » 0002-Fixes-on-top-of-issue-ordering.patch
app/controllers/versions_controller.rb | ||
---|---|---|
177 | 177 | |
178 | 178 |
def order_issues_by |
179 | 179 |
if Setting.manual_issue_position_in_versions == '1' |
180 |
return "COALESCE(#{Issue.table_name}.position, 999999), #{Issue.table_name}.id" |
|
180 |
return "COALESCE(#{Issue.table_name}.fixed_version_position, 999999), #{Issue.table_name}.id"
|
|
181 | 181 |
else |
182 | 182 |
return "#{Tracker.table_name}.position, #{Issue.table_name}.id" |
183 | 183 |
end |
app/models/issue.rb | ||
---|---|---|
54 | 54 |
acts_as_activity_provider :scope => proc {preload(:project, :author, :tracker, :status)}, |
55 | 55 |
:author_key => :author_id |
56 | 56 | |
57 |
acts_as_positioned :scope => [:fixed_version_id] |
|
57 |
acts_as_positioned :scope => [:fixed_version_id], :column => :fixed_version_position
|
|
58 | 58 | |
59 | 59 |
DONE_RATIO_OPTIONS = %w(issue_field issue_status) |
60 | 60 | |
... | ... | |
513 | 513 |
user.allowed_to?(:manage_subtasks, issue.project) |
514 | 514 |
end) |
515 | 515 |
safe_attributes( |
516 |
'position', |
|
516 |
'fixed_version_position',
|
|
517 | 517 |
:if => lambda {|issue, user| user.allowed_to?(:change_issue_position_in_version, issue.project)} |
518 | 518 |
) |
519 | 519 |
safe_attributes( |
... | ... | |
867 | 867 | |
868 | 868 |
# Returns the names of attributes that are journalized when updating the issue |
869 | 869 |
def journalized_attribute_names |
870 |
names = Issue.column_names - %w(id root_id lft rgt lock_version position created_on updated_on closed_on) |
|
870 |
names = Issue.column_names - %w(id root_id lft rgt lock_version fixed_version_position created_on updated_on closed_on)
|
|
871 | 871 |
if tracker |
872 | 872 |
names -= tracker.disabled_core_fields |
873 | 873 |
end |
app/models/issue_query.rb | ||
---|---|---|
49 | 49 |
QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date", :groupable => true), |
50 | 50 |
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", |
51 | 51 |
:totalable => true), |
52 |
QueryColumn.new(:position, :sortable => "#{Issue.table_name}.position"),
|
|
52 |
QueryColumn.new(:fixed_version_position, :sortable => "#{Issue.table_name}.fixed_version_position"),
|
|
53 | 53 |
QueryColumn.new( |
54 | 54 |
:total_estimated_hours, |
55 | 55 |
:sortable => |
... | ... | |
183 | 183 |
add_available_filter "start_date", :type => :date |
184 | 184 |
add_available_filter "due_date", :type => :date |
185 | 185 |
add_available_filter "estimated_hours", :type => :float |
186 |
add_available_filter "position", :type => :integer |
|
186 |
add_available_filter "fixed_version_position", :type => :integer
|
|
187 | 187 | |
188 | 188 |
if User.current.allowed_to?(:view_time_entries, project, :global => true) |
189 | 189 |
add_available_filter "spent_time", :type => :float, :label => :label_spent_time |
db/migrate/20200315154300_add_issue_position.rb | ||
---|---|---|
1 | 1 |
class AddIssuePosition < ActiveRecord::Migration[5.2] |
2 | 2 |
def self.up |
3 |
add_column :issues, :position, :integer |
|
3 |
add_column :issues, :fixed_version_position, :integer
|
|
4 | 4 |
end |
5 | 5 | |
6 | 6 |
def self.down |
7 |
remove_column :issues, :position |
|
7 |
remove_column :issues, :fixed_version_position
|
|
8 | 8 |
end |
9 | 9 |
end |
lib/redmine/acts/positioned.rb | ||
---|---|---|
34 | 34 |
# or an array of symbols |
35 | 35 |
def acts_as_positioned(options = {}) |
36 | 36 |
class_attribute :positioned_options |
37 |
self.positioned_options = {:scope => Array(options[:scope])} |
|
37 |
self.positioned_options = {:scope => Array(options[:scope]), :column => options[:colunm] || :position }
|
|
38 | 38 | |
39 | 39 |
send :include, Redmine::Acts::Positioned::InstanceMethods |
40 | 40 | |
... | ... | |
70 | 70 |
end |
71 | 71 | |
72 | 72 |
def set_default_position |
73 |
if position.nil? |
|
74 |
self.position = position_scope.maximum(:position).to_i + (new_record? ? 1 : 0) |
|
73 |
column = self.positioned_options[:column] |
|
74 | ||
75 |
if send(column).nil? |
|
76 |
self.send(column + '=', position_scope.maximum(column).to_i + (new_record? ? 1 : 0)) |
|
75 | 77 |
end |
76 | 78 |
end |
77 | 79 | |
... | ... | |
89 | 91 |
end |
90 | 92 | |
91 | 93 |
def insert_position |
92 |
position_scope.where("position >= ? AND id <> ?", position, id).update_all("position = position + 1") |
|
94 |
column = self.positioned_options[:column] |
|
95 | ||
96 |
position_scope.where("#{column} >= ? AND id <> ?", position, id).update_all("#{column} = #{column} + 1") |
|
93 | 97 |
end |
94 | 98 | |
95 | 99 |
def remove_position |
96 | 100 |
# this can be called in after_update or after_destroy callbacks |
97 | 101 |
# with different methods in Rails 5 for retrieving the previous value |
102 |
column = self.positioned_options[:column] |
|
103 | ||
98 | 104 |
previous = destroyed? ? position_was : position_before_last_save |
99 |
position_scope_was.where("position >= ? AND id <> ?", previous, id).update_all("position = position - 1")
|
|
105 |
position_scope_was.where("#{column} >= ? AND id <> ?", previous, id).update_all("#{column} = #{column} - 1")
|
|
100 | 106 |
end |
101 | 107 | |
102 | 108 |
def position_scope_changed? |
... | ... | |
104 | 110 |
end |
105 | 111 | |
106 | 112 |
def shift_positions |
113 |
column = self.positioned_options[:column] |
|
114 | ||
107 | 115 |
offset = position_before_last_save <=> position |
108 | 116 |
min, max = [position, position_before_last_save].sort |
109 |
r = position_scope.where("id <> ? AND position BETWEEN ? AND ?", id, min, max).update_all("position = position + #{offset}")
|
|
117 |
r = position_scope.where("id <> ? AND #{column} BETWEEN ? AND ?", id, min, max).update_all("#{column} = #{column} + #{offset}")
|
|
110 | 118 |
if r != max - min |
111 | 119 |
reset_positions_in_list |
112 | 120 |
end |
113 | 121 |
end |
114 | 122 | |
115 | 123 |
def reset_positions_in_list |
116 |
position_scope.reorder(:position, :id).pluck(:id).each_with_index do |record_id, p| |
|
117 |
self.class.where(:id => record_id).update_all(:position => p+1) |
|
124 |
column = self.positioned_options[:column] |
|
125 | ||
126 |
position_scope.reorder(column, :id).pluck(:id).each_with_index do |record_id, p| |
|
127 |
self.class.where(:id => record_id).update_all(column => p+1) |
|
118 | 128 |
end |
119 | 129 |
end |
120 | 130 |
end |