Commit 6d28ecd6 authored by Thong Kuah's avatar Thong Kuah

Merge branch '337135-issues-sort-alphabetically' into 'master'

Resolve "Issues: sort alphabetically"

See merge request gitlab-org/gitlab!67234
parents ffd1231d 3973db2b
......@@ -20,6 +20,7 @@ const MILESTONE_DUE = 'milestone_due';
const POPULARITY = 'popularity';
const WEIGHT = 'weight';
const LABEL_PRIORITY = 'label_priority';
const TITLE = 'title';
export const RELATIVE_POSITION = 'relative_position';
export const LOADING_LIST_ITEMS_LENGTH = 8;
export const PAGE_SIZE = 20;
......@@ -41,6 +42,8 @@ export const sortOrderMap = {
relative_position: { order_by: RELATIVE_POSITION, sort: ASC },
weight_desc: { order_by: WEIGHT, sort: DESC },
weight: { order_by: WEIGHT, sort: ASC },
title: { order_by: TITLE, sort: ASC },
title_desc: { order_by: TITLE, sort: DESC },
};
export const availableSortOptionsJira = [
......@@ -144,6 +147,8 @@ export const POPULARITY_DESC = 'POPULARITY_DESC';
export const PRIORITY_ASC = 'PRIORITY_ASC';
export const PRIORITY_DESC = 'PRIORITY_DESC';
export const RELATIVE_POSITION_ASC = 'RELATIVE_POSITION_ASC';
export const TITLE_ASC = 'TITLE_ASC';
export const TITLE_DESC = 'TITLE_DESC';
export const UPDATED_ASC = 'UPDATED_ASC';
export const UPDATED_DESC = 'UPDATED_DESC';
export const WEIGHT_ASC = 'WEIGHT_ASC';
......@@ -161,6 +166,7 @@ const LABEL_PRIORITY_ASC_SORT = 'label_priority_asc';
const POPULARITY_ASC_SORT = 'popularity_asc';
const WEIGHT_DESC_SORT = 'weight_desc';
const BLOCKING_ISSUES_DESC_SORT = 'blocking_issues_desc';
const TITLE_DESC_SORT = 'title_desc';
export const urlSortParams = {
[PRIORITY_ASC]: PRIORITY_ASC_SORT,
......@@ -181,6 +187,8 @@ export const urlSortParams = {
[WEIGHT_ASC]: WEIGHT,
[WEIGHT_DESC]: WEIGHT_DESC_SORT,
[BLOCKING_ISSUES_DESC]: BLOCKING_ISSUES_DESC_SORT,
[TITLE_ASC]: TITLE,
[TITLE_DESC]: TITLE_DESC_SORT,
};
export const MAX_LIST_SIZE = 10;
......
......@@ -10,6 +10,8 @@ module Types
value 'RELATIVE_POSITION_ASC', 'Relative position by ascending order.', value: :relative_position_asc
value 'SEVERITY_ASC', 'Severity from less critical to more critical.', value: :severity_asc
value 'SEVERITY_DESC', 'Severity from more critical to less critical.', value: :severity_desc
value 'TITLE_ASC', 'Title by ascending order.', value: :title_asc
value 'TITLE_DESC', 'Title by descending order.', value: :title_desc
value 'POPULARITY_ASC', 'Number of upvotes (awarded "thumbs up" emoji) by ascending order.', value: :popularity_asc
value 'POPULARITY_DESC', 'Number of upvotes (awarded "thumbs up" emoji) by descending order.', value: :popularity_desc
end
......
......@@ -37,7 +37,8 @@ module SortingHelper
sort_value_contacted_date => sort_title_contacted_date,
sort_value_relative_position => sort_title_relative_position,
sort_value_size => sort_title_size,
sort_value_expire_date => sort_title_expire_date
sort_value_expire_date => sort_title_expire_date,
sort_value_title => sort_title_title
}
end
# rubocop: enable Metrics/AbcSize
......@@ -188,7 +189,8 @@ module SortingHelper
sort_value_due_date_later => sort_value_due_date,
sort_value_merged_recently => sort_value_merged_date,
sort_value_closed_recently => sort_value_closed_date,
sort_value_least_popular => sort_value_popularity
sort_value_least_popular => sort_value_popularity,
sort_value_title_desc => sort_value_title
}
end
......@@ -205,7 +207,8 @@ module SortingHelper
sort_value_closed_date => sort_value_closed_recently,
sort_value_closed_earlier => sort_value_closed_recently,
sort_value_popularity => sort_value_least_popular,
sort_value_most_popular => sort_value_least_popular
sort_value_most_popular => sort_value_least_popular,
sort_value_title => sort_value_title_desc
}.merge(issuable_sort_option_overrides)
end
......
......@@ -138,6 +138,10 @@ module SortingTitlesValuesHelper
s_('SortOptions|Start soon')
end
def sort_title_title
s_('SortOptions|Title')
end
def sort_title_upvotes
s_('SortOptions|Most popular')
end
......@@ -307,6 +311,14 @@ module SortingTitlesValuesHelper
'start_date_asc'
end
def sort_value_title
'title_asc'
end
def sort_value_title_desc
'title_desc'
end
def sort_value_upvotes
'upvotes_desc'
end
......
......@@ -26,6 +26,7 @@ module Issuable
include UpdatedAtFilterable
include ClosedAtFilterable
include VersionedDescription
include SortableTitle
TITLE_LENGTH_MAX = 255
TITLE_HTML_LENGTH_MAX = 800
......@@ -293,6 +294,8 @@ module Issuable
when 'popularity', 'popularity_desc', 'upvotes_desc' then order_upvotes_desc
when 'priority', 'priority_asc' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
when 'priority_desc' then order_due_date_and_labels_priority('DESC', excluded_labels: excluded_labels)
when 'title_asc' then order_title_asc.with_order_id_desc
when 'title_desc' then order_title_desc.with_order_id_desc
else order_by(method)
end
......
# frozen_string_literal: true
module SortableTitle
extend ActiveSupport::Concern
included do
scope :order_title_asc, -> { reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower)) }
scope :order_title_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:title].lower)) }
end
class_methods do
def simple_sorts
super.merge(
{
'title_asc' => -> { order_title_asc },
'title_desc' => -> { order_title_desc }
}
)
end
end
end
......@@ -21,5 +21,6 @@
= sortable_item(sort_title_merged_date, page_filter_path(sort: sort_value_merged_date), sort_title) if viewing_merge_requests
= sortable_item(sort_title_closed_date, page_filter_path(sort: sort_value_closed_date), sort_title) if viewing_merge_requests
= sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues
= sortable_item(sort_title_title, page_filter_path(sort: sort_value_title), sort_title) if viewing_issues
= render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title)
= issuable_sort_direction_button(sort_value)
......@@ -15515,6 +15515,8 @@ Values for sorting issues.
| <a id="issuesortseverity_desc"></a>`SEVERITY_DESC` | Severity from more critical to less critical. |
| <a id="issuesortsla_due_at_asc"></a>`SLA_DUE_AT_ASC` | Issues with earliest SLA due time shown first. |
| <a id="issuesortsla_due_at_desc"></a>`SLA_DUE_AT_DESC` | Issues with latest SLA due time shown first. |
| <a id="issuesorttitle_asc"></a>`TITLE_ASC` | Title by ascending order. |
| <a id="issuesorttitle_desc"></a>`TITLE_DESC` | Title by descending order. |
| <a id="issuesortupdated_asc"></a>`UPDATED_ASC` | Updated at ascending order. |
| <a id="issuesortupdated_desc"></a>`UPDATED_DESC` | Updated at descending order. |
| <a id="issuesortweight_asc"></a>`WEIGHT_ASC` | Weight by ascending order. |
......
......@@ -73,7 +73,7 @@ GET /issues?state=opened
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/14016) in GitLab 10.0)_ |
| `non_archived` | boolean | no | Return issues only from non-archived projects. If `false`, the response returns issues from both archived and non-archived projects. Default is `true`. _(Introduced in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/197170))_ |
| `not` | Hash | no | Return issues that do not match the parameters supplied. Accepts: `assignee_id`, `assignee_username`, `author_id`, `author_username`, `iids`, `iteration_id`, `iteration_title`, `labels`, `milestone`, `milestone_id` and `weight`. |
| `order_by` | string | no | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
| `order_by` | string | no | Return issues ordered by `created_at`, `due_date`, `label_priority`, `milestone_due`, `popularity`, `priority`, `relative_position`, `title`, `updated_at`, or `weight` fields. Default is `created_at`. |
| `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/13004) in GitLab 9.5. [Changed to snake_case](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/18935) in GitLab 11.0)_ |
| `search` | string | no | Search issues against their `title` and `description` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
......
......@@ -16,6 +16,7 @@ You can sort a list of issues several ways, including by:
- Milestone due date
- Popularity
- Priority
- Title ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67234) in GitLab 14.3)
- Weight
The available sorting options can change based on the context of the list.
......
......@@ -37,10 +37,6 @@ module EE
s_('SortOptions|Type')
end
def sort_title_title
s_('SortOptions|Title')
end
def sort_value_start_date
'start_date_asc'
end
......@@ -49,14 +45,6 @@ module EE
'end_date_asc'
end
def sort_value_title
'title_asc'
end
def sort_value_title_desc
'title_desc'
end
def sort_value_end_date_later
'end_date_desc'
end
......
......@@ -19,6 +19,7 @@ module EE
include Presentable
include IdInOrdered
include Todoable
include SortableTitle
enum state_id: {
opened: ::Epic.available_states[:opened],
......@@ -112,9 +113,6 @@ module EE
reorder(keyset_order)
end
scope :order_title_asc, -> { reorder(Arel::Nodes::Ascending.new(arel_table[:title].lower)) }
scope :order_title_desc, -> { reorder(Arel::Nodes::Descending.new(arel_table[:title].lower)) }
scope :order_closed_date_desc, -> { reorder(closed_at: :desc) }
scope :order_relative_position, -> do
......@@ -277,9 +275,7 @@ module EE
'start_date_asc' => -> { order_start_date_asc },
'start_date_desc' => -> { order_start_date_desc },
'end_date_asc' => -> { order_end_date_asc },
'end_date_desc' => -> { order_end_date_desc },
'title_asc' => -> { order_title_asc },
'title_desc' => -> { order_title_desc }
'end_date_desc' => -> { order_end_date_desc }
}
)
end
......
......@@ -381,11 +381,11 @@ RSpec.describe Issue do
describe '.simple_sorts' do
it 'includes weight with other base keys' do
expect(Issue.simple_sorts.keys).to match_array(
%w(created_asc created_at_asc created_date created_desc created_at_desc
closest_future_date closest_future_date_asc due_date due_date_asc due_date_desc
id_asc id_desc relative_position relative_position_asc
updated_desc updated_asc updated_at_asc updated_at_desc
weight weight_asc weight_desc))
%w(closest_future_date closest_future_date_asc created_asc
created_at_asc created_at_desc created_date created_desc due_date
due_date_asc due_date_desc id_asc id_desc relative_position
relative_position_asc title_asc title_desc updated_asc updated_at_asc
updated_at_desc updated_desc weight weight_asc weight_desc))
end
end
......
......@@ -34,7 +34,17 @@ module API
end
def self.sort_options
%w[created_at updated_at priority due_date relative_position label_priority milestone_due popularity]
%w[
created_at
due_date
label_priority
milestone_due
popularity
priority
relative_position
title
updated_at
]
end
def issue_finder(args = {})
......
......@@ -78,7 +78,7 @@ module API
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
optional :order_by, type: String, values: Helpers::IssuesHelpers.sort_options, default: 'created_at',
desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
desc: 'Return issues ordered by `created_at`, `due_date`, `label_priority`, `milestone_due`, `popularity`, `priority`, `relative_position`, `title`, or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
optional :due_date, type: String, values: %w[0 overdue week month next_month_and_previous_two_weeks] << '',
......
......@@ -414,6 +414,22 @@ RSpec.describe Resolvers::IssuesResolver do
end
end
end
context 'when sorting by title' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue1) { create(:issue, project: project, title: 'foo') }
let_it_be(:issue2) { create(:issue, project: project, title: 'bar') }
let_it_be(:issue3) { create(:issue, project: project, title: 'baz') }
let_it_be(:issue4) { create(:issue, project: project, title: 'Baz 2') }
it 'sorts issues ascending' do
expect(resolve_issues(sort: :title_asc).to_a).to eq [issue2, issue3, issue4, issue1]
end
it 'sorts issues descending' do
expect(resolve_issues(sort: :title_desc).to_a).to eq [issue1, issue4, issue3, issue2]
end
end
end
it 'returns issues user can see' do
......
......@@ -368,6 +368,23 @@ RSpec.describe Issuable do
expect(sorted_issue_ids).to eq(sorted_issue_ids.uniq)
end
end
context 'by title' do
let!(:issue1) { create(:issue, project: project, title: 'foo') }
let!(:issue2) { create(:issue, project: project, title: 'bar') }
let!(:issue3) { create(:issue, project: project, title: 'baz') }
let!(:issue4) { create(:issue, project: project, title: 'Baz 2') }
it 'sorts asc' do
issues = project.issues.sort_by_attribute('title_asc')
expect(issues).to eq([issue2, issue3, issue4, issue1])
end
it 'sorts desc' do
issues = project.issues.sort_by_attribute('title_desc')
expect(issues).to eq([issue1, issue4, issue3, issue2])
end
end
end
describe '#subscribed?' do
......
......@@ -165,8 +165,8 @@ RSpec.describe Issue do
expect(described_class.simple_sorts.keys).to include(
*%w(created_asc created_at_asc created_date created_desc created_at_desc
closest_future_date closest_future_date_asc due_date due_date_asc due_date_desc
id_asc id_desc relative_position relative_position_asc
updated_desc updated_asc updated_at_asc updated_at_desc))
id_asc id_desc relative_position relative_position_asc updated_desc updated_asc
updated_at_asc updated_at_desc title_asc title_desc))
end
end
......@@ -203,6 +203,25 @@ RSpec.describe Issue do
end
end
describe '.order_title' do
let_it_be(:issue1) { create(:issue, title: 'foo') }
let_it_be(:issue2) { create(:issue, title: 'bar') }
let_it_be(:issue3) { create(:issue, title: 'baz') }
let_it_be(:issue4) { create(:issue, title: 'Baz 2') }
context 'sorting ascending' do
subject { described_class.order_title_asc }
it { is_expected.to eq([issue2, issue3, issue4, issue1]) }
end
context 'sorting descending' do
subject { described_class.order_title_desc }
it { is_expected.to eq([issue1, issue4, issue3, issue2]) }
end
end
describe '#order_by_position_and_priority' do
let(:project) { reusable_project }
let(:p1) { create(:label, title: 'P1', project: project, priority: 1) }
......
......@@ -815,6 +815,18 @@ RSpec.describe API::Issues do
expect_paginated_array_response([closed_issue.id, issue.id])
end
it 'sorts by title asc when requested' do
get api('/issues', user), params: { order_by: :title, sort: :asc }
expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'sorts by title desc when requested' do
get api('/issues', user), params: { order_by: :title, sort: :desc }
expect_paginated_array_response([closed_issue.id, issue.id])
end
context 'with issues list sort options' do
it 'accepts only predefined order by params' do
API::Helpers::IssuesHelpers.sort_options.each do |sort_opt|
......@@ -824,7 +836,7 @@ RSpec.describe API::Issues do
end
it 'fails to sort with non predefined options' do
%w(milestone title abracadabra).each do |sort_opt|
%w(milestone abracadabra).each do |sort_opt|
get api('/issues', user), params: { order_by: sort_opt, sort: 'asc' }
expect(response).to have_gitlab_http_status(:bad_request)
end
......
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