Commit 82ccc8bc authored by Sean McGivern's avatar Sean McGivern

Merge branch '9121-sort-relative-position' into 'master'

Support sorting issues using `relative_position`

Closes #62178

See merge request gitlab-org/gitlab-ce!28566
parents d5686516 703dd456
...@@ -41,6 +41,7 @@ module IssuableCollections ...@@ -41,6 +41,7 @@ module IssuableCollections
return if pagination_disabled? return if pagination_disabled?
@issuables = @issuables.page(params[:page]) @issuables = @issuables.page(params[:page])
@issuables = per_page_for_relative_position if params[:sort] == 'relative_position'
@issuable_meta_data = issuable_meta_data(@issuables, collection_type) @issuable_meta_data = issuable_meta_data(@issuables, collection_type)
@total_pages = issuable_page_count @total_pages = issuable_page_count
end end
...@@ -80,6 +81,11 @@ module IssuableCollections ...@@ -80,6 +81,11 @@ module IssuableCollections
(row_count.to_f / limit).ceil (row_count.to_f / limit).ceil
end end
# manual / relative_position sorting allows for 100 items on the page
def per_page_for_relative_position
@issuables.per(100) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def issuable_finder_for(finder_class) def issuable_finder_for(finder_class)
finder_class.new(current_user, finder_options) finder_class.new(current_user, finder_options)
end end
......
...@@ -3,29 +3,30 @@ ...@@ -3,29 +3,30 @@
module SortingHelper module SortingHelper
def sort_options_hash def sort_options_hash
{ {
sort_value_created_date => sort_title_created_date, sort_value_created_date => sort_title_created_date,
sort_value_downvotes => sort_title_downvotes, sort_value_downvotes => sort_title_downvotes,
sort_value_due_date => sort_title_due_date, sort_value_due_date => sort_title_due_date,
sort_value_due_date_later => sort_title_due_date_later, sort_value_due_date_later => sort_title_due_date_later,
sort_value_due_date_soon => sort_title_due_date_soon, sort_value_due_date_soon => sort_title_due_date_soon,
sort_value_label_priority => sort_title_label_priority, sort_value_label_priority => sort_title_label_priority,
sort_value_largest_group => sort_title_largest_group, sort_value_largest_group => sort_title_largest_group,
sort_value_largest_repo => sort_title_largest_repo, sort_value_largest_repo => sort_title_largest_repo,
sort_value_milestone => sort_title_milestone, sort_value_milestone => sort_title_milestone,
sort_value_milestone_later => sort_title_milestone_later, sort_value_milestone_later => sort_title_milestone_later,
sort_value_milestone_soon => sort_title_milestone_soon, sort_value_milestone_soon => sort_title_milestone_soon,
sort_value_name => sort_title_name, sort_value_name => sort_title_name,
sort_value_name_desc => sort_title_name_desc, sort_value_name_desc => sort_title_name_desc,
sort_value_oldest_created => sort_title_oldest_created, sort_value_oldest_created => sort_title_oldest_created,
sort_value_oldest_signin => sort_title_oldest_signin, sort_value_oldest_signin => sort_title_oldest_signin,
sort_value_oldest_updated => sort_title_oldest_updated, sort_value_oldest_updated => sort_title_oldest_updated,
sort_value_recently_created => sort_title_recently_created, sort_value_recently_created => sort_title_recently_created,
sort_value_recently_signin => sort_title_recently_signin, sort_value_recently_signin => sort_title_recently_signin,
sort_value_recently_updated => sort_title_recently_updated, sort_value_recently_updated => sort_title_recently_updated,
sort_value_popularity => sort_title_popularity, sort_value_popularity => sort_title_popularity,
sort_value_priority => sort_title_priority, sort_value_priority => sort_title_priority,
sort_value_upvotes => sort_title_upvotes, sort_value_upvotes => sort_title_upvotes,
sort_value_contacted_date => sort_title_contacted_date sort_value_contacted_date => sort_title_contacted_date,
sort_value_relative_position => sort_title_relative_position
} }
end end
...@@ -397,6 +398,10 @@ module SortingHelper ...@@ -397,6 +398,10 @@ module SortingHelper
s_('SortOptions|Recent last activity') s_('SortOptions|Recent last activity')
end end
def sort_title_relative_position
s_('SortOptions|Manual')
end
# Values. # Values.
def sort_value_access_level_asc def sort_value_access_level_asc
'access_level_asc' 'access_level_asc'
...@@ -545,4 +550,8 @@ module SortingHelper ...@@ -545,4 +550,8 @@ module SortingHelper
def sort_value_recently_last_activity def sort_value_recently_last_activity
'last_activity_on_desc' 'last_activity_on_desc'
end end
def sort_value_relative_position
'relative_position'
end
end end
...@@ -58,6 +58,7 @@ class Issue < ApplicationRecord ...@@ -58,6 +58,7 @@ class Issue < ApplicationRecord
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') } scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') } scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
scope :order_closest_future_date, -> { reorder('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC') } scope :order_closest_future_date, -> { reorder('CASE WHEN issues.due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - issues.due_date) ASC') }
scope :order_relative_position_asc, -> { reorder(::Gitlab::Database.nulls_last_order('relative_position', 'ASC')) }
scope :preload_associations, -> { preload(:labels, project: :namespace) } scope :preload_associations, -> { preload(:labels, project: :namespace) }
scope :with_api_entity_associations, -> { preload(:timelogs, :assignees, :author, :notes, :labels, project: [:route, { namespace: :route }] ) } scope :with_api_entity_associations, -> { preload(:timelogs, :assignees, :author, :notes, :labels, project: [:route, { namespace: :route }] ) }
...@@ -130,9 +131,10 @@ class Issue < ApplicationRecord ...@@ -130,9 +131,10 @@ class Issue < ApplicationRecord
def self.sort_by_attribute(method, excluded_labels: []) def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s case method.to_s
when 'closest_future_date' then order_closest_future_date when 'closest_future_date' then order_closest_future_date
when 'due_date' then order_due_date_asc when 'due_date' then order_due_date_asc
when 'due_date_asc' then order_due_date_asc when 'due_date_asc' then order_due_date_asc
when 'due_date_desc' then order_due_date_desc when 'due_date_desc' then order_due_date_desc
when 'relative_position' then order_relative_position_asc
else else
super super
end end
......
...@@ -10,12 +10,13 @@ ...@@ -10,12 +10,13 @@
= icon('chevron-down') = icon('chevron-down')
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort
%li %li
= sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title) = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority), sort_title)
= sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sort_title) = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date), sort_title)
= sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sort_title) = sortable_item(sort_title_recently_updated, page_filter_path(sort: sort_value_recently_updated), sort_title)
= sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone), sort_title) = sortable_item(sort_title_milestone, page_filter_path(sort: sort_value_milestone), sort_title)
= sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues
= sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title) = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title)
= sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title) = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title)
= sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues && Feature.enabled?(:manual_sorting)
= render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title) = render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
= issuable_sort_direction_button(sort_value) = issuable_sort_direction_button(sort_value)
---
title: Allow issue list to be sorted by relative order
merge_request: 28566
author:
type: added
...@@ -9124,6 +9124,9 @@ msgstr "" ...@@ -9124,6 +9124,9 @@ msgstr ""
msgid "SortOptions|Least popular" msgid "SortOptions|Least popular"
msgstr "" msgstr ""
msgid "SortOptions|Manual"
msgstr ""
msgid "SortOptions|Milestone due date" msgid "SortOptions|Milestone due date"
msgstr "" msgstr ""
......
...@@ -130,6 +130,37 @@ describe Projects::IssuesController do ...@@ -130,6 +130,37 @@ describe Projects::IssuesController do
end end
end end
context 'with relative_position sorting' do
let!(:issue_list) { create_list(:issue, 2, project: project) }
before do
sign_in(user)
project.add_developer(user)
allow(Kaminari.config).to receive(:default_per_page).and_return(1)
end
it 'overrides the number allowed on the page' do
get :index,
params: {
namespace_id: project.namespace.to_param,
project_id: project,
sort: 'relative_position'
}
expect(assigns(:issues).count).to eq 2
end
it 'allows the default number on the page' do
get :index,
params: {
namespace_id: project.namespace.to_param,
project_id: project
}
expect(assigns(:issues).count).to eq 1
end
end
context 'external authorization' do context 'external authorization' do
before do before do
sign_in user sign_in user
......
...@@ -93,6 +93,21 @@ describe Issue do ...@@ -93,6 +93,21 @@ describe Issue do
end end
end end
describe '#sort' do
let(:project) { create(:project) }
context "by relative_position" do
let!(:issue) { create(:issue, project: project) }
let!(:issue2) { create(:issue, project: project, relative_position: 2) }
let!(:issue3) { create(:issue, project: project, relative_position: 1) }
it "sorts asc with nulls at the end" do
issues = project.issues.sort_by_attribute('relative_position')
expect(issues).to eq([issue3, issue2, issue])
end
end
end
describe '#card_attributes' do describe '#card_attributes' do
it 'includes the author name' do it 'includes the author name' do
allow(subject).to receive(:author).and_return(double(name: 'Robert')) allow(subject).to receive(:author).and_return(double(name: 'Robert'))
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment