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 |