Commit 6239c882 authored by James Lopez's avatar James Lopez

Merge branch '29713-graphql-add-issue-priority-sort' into 'master'

Graphql: Sort issues by priority

See merge request gitlab-org/gitlab!18901
parents 016ab959 c2463711
...@@ -52,6 +52,8 @@ module Resolvers ...@@ -52,6 +52,8 @@ module Resolvers
type Types::IssueType, null: true type Types::IssueType, null: true
NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc].freeze
def resolve(**args) def resolve(**args)
# The project could have been loaded in batch by `BatchLoader`. # The project could have been loaded in batch by `BatchLoader`.
# At this point we need the `id` of the project to query for issues, so # At this point we need the `id` of the project to query for issues, so
...@@ -70,7 +72,15 @@ module Resolvers ...@@ -70,7 +72,15 @@ module Resolvers
args[:iids] ||= [args[:iid]].compact args[:iids] ||= [args[:iid]].compact
args[:attempt_project_search_optimizations] = args[:search].present? args[:attempt_project_search_optimizations] = args[:search].present?
IssuesFinder.new(context[:current_user], args).execute issues = IssuesFinder.new(context[:current_user], args).execute
if non_stable_cursor_sort?(args[:sort])
# Certain complex sorts are not supported by the stable cursor pagination yet.
# In these cases, we use offset pagination, so we return the correct connection.
Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(issues)
else
issues
end
end end
def self.resolver_complexity(args, child_complexity:) def self.resolver_complexity(args, child_complexity:)
...@@ -79,5 +89,9 @@ module Resolvers ...@@ -79,5 +89,9 @@ module Resolvers
complexity complexity
end end
def non_stable_cursor_sort?(sort)
NON_STABLE_CURSOR_SORTS.include?(sort)
end
end end
end end
...@@ -4,5 +4,8 @@ module Types ...@@ -4,5 +4,8 @@ module Types
class IssuableSortEnum < SortEnum class IssuableSortEnum < SortEnum
graphql_name 'IssuableSort' graphql_name 'IssuableSort'
description 'Values for sorting issuables' description 'Values for sorting issuables'
value 'PRIORITY_ASC', 'Priority by ascending order', value: :priority_asc
value 'PRIORITY_DESC', 'Priority by descending order', value: :priority_desc
end end
end end
---
title: Graphql query for issues can now be sorted by priority
merge_request: 18901
author:
type: added
...@@ -4398,6 +4398,16 @@ enum IssueSort { ...@@ -4398,6 +4398,16 @@ enum IssueSort {
""" """
DUE_DATE_DESC DUE_DATE_DESC
"""
Priority by ascending order
"""
PRIORITY_ASC
"""
Priority by descending order
"""
PRIORITY_DESC
""" """
Relative position by ascending order Relative position by ascending order
""" """
......
...@@ -12546,6 +12546,18 @@ ...@@ -12546,6 +12546,18 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "PRIORITY_ASC",
"description": "Priority by ascending order",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "PRIORITY_DESC",
"description": "Priority by descending order",
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "DUE_DATE_ASC", "name": "DUE_DATE_ASC",
"description": "Due date by ascending order", "description": "Due date by ascending order",
......
...@@ -6,16 +6,16 @@ describe Resolvers::IssuesResolver do ...@@ -6,16 +6,16 @@ describe Resolvers::IssuesResolver do
include GraphqlHelpers include GraphqlHelpers
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let(:project) { create(:project) } let_it_be(:project) { create(:project) }
context "with a project" do context "with a project" do
describe '#resolve' do describe '#resolve' do
describe 'sorting' do describe 'sorting' do
context 'when sorting by weight' do context 'when sorting by weight' do
let!(:weight_issue1) { create(:issue, project: project, weight: 5) } let_it_be(:weight_issue1) { create(:issue, project: project, weight: 5) }
let!(:weight_issue2) { create(:issue, project: project, weight: nil) } let_it_be(:weight_issue2) { create(:issue, project: project, weight: nil) }
let!(:weight_issue3) { create(:issue, project: project, weight: 1) } let_it_be(:weight_issue3) { create(:issue, project: project, weight: 1) }
let!(:weight_issue4) { create(:issue, project: project, weight: nil) } let_it_be(:weight_issue4) { create(:issue, project: project, weight: nil) }
before do before do
project.add_developer(current_user) project.add_developer(current_user)
......
...@@ -125,12 +125,12 @@ describe Resolvers::IssuesResolver do ...@@ -125,12 +125,12 @@ describe Resolvers::IssuesResolver do
end end
context 'when sorting by due date' do context 'when sorting by due date' do
let(:project) { create(:project) } let_it_be(:project) { create(:project) }
let!(:due_issue1) { create(:issue, project: project, due_date: 3.days.from_now) } let_it_be(:due_issue1) { create(:issue, project: project, due_date: 3.days.from_now) }
let!(:due_issue2) { create(:issue, project: project, due_date: nil) } let_it_be(:due_issue2) { create(:issue, project: project, due_date: nil) }
let!(:due_issue3) { create(:issue, project: project, due_date: 2.days.ago) } let_it_be(:due_issue3) { create(:issue, project: project, due_date: 2.days.ago) }
let!(:due_issue4) { create(:issue, project: project, due_date: nil) } let_it_be(:due_issue4) { create(:issue, project: project, due_date: nil) }
it 'sorts issues ascending' do it 'sorts issues ascending' do
expect(resolve_issues(sort: :due_date_asc)).to eq [due_issue3, due_issue1, due_issue4, due_issue2] expect(resolve_issues(sort: :due_date_asc)).to eq [due_issue3, due_issue1, due_issue4, due_issue2]
...@@ -142,17 +142,38 @@ describe Resolvers::IssuesResolver do ...@@ -142,17 +142,38 @@ describe Resolvers::IssuesResolver do
end end
context 'when sorting by relative position' do context 'when sorting by relative position' do
let(:project) { create(:project) } let_it_be(:project) { create(:project) }
let!(:relative_issue1) { create(:issue, project: project, relative_position: 2000) } let_it_be(:relative_issue1) { create(:issue, project: project, relative_position: 2000) }
let!(:relative_issue2) { create(:issue, project: project, relative_position: nil) } let_it_be(:relative_issue2) { create(:issue, project: project, relative_position: nil) }
let!(:relative_issue3) { create(:issue, project: project, relative_position: 1000) } let_it_be(:relative_issue3) { create(:issue, project: project, relative_position: 1000) }
let!(:relative_issue4) { create(:issue, project: project, relative_position: nil) } let_it_be(:relative_issue4) { create(:issue, project: project, relative_position: nil) }
it 'sorts issues ascending' do it 'sorts issues ascending' do
expect(resolve_issues(sort: :relative_position_asc)).to eq [relative_issue3, relative_issue1, relative_issue4, relative_issue2] expect(resolve_issues(sort: :relative_position_asc)).to eq [relative_issue3, relative_issue1, relative_issue4, relative_issue2]
end end
end end
context 'when sorting by priority' do
let_it_be(:project) { create(:project) }
let_it_be(:early_milestone) { create(:milestone, project: project, due_date: 10.days.from_now) }
let_it_be(:late_milestone) { create(:milestone, project: project, due_date: 30.days.from_now) }
let_it_be(:label_1) { create(:label, project: project, priority: 1) }
let_it_be(:label_2) { create(:label, project: project, priority: 5) }
let_it_be(:issue1) { create(:issue, project: project, labels: [label_1], milestone: late_milestone) }
let_it_be(:issue2) { create(:issue, project: project, labels: [label_2]) }
let_it_be(:issue3) { create(:issue, project: project, milestone: early_milestone) }
let_it_be(:issue4) { create(:issue, project: project) }
it 'sorts issues ascending' do
expect(resolve_issues(sort: :priority_asc).items).to eq([issue3, issue1, issue2, issue4])
end
it 'sorts issues descending' do
expect(resolve_issues(sort: :priority_desc).items).to eq([issue1, issue3, issue2, issue4])
end
end
end end
it 'returns issues user can see' do it 'returns issues user can see' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Types::IssuableSortEnum do
it { expect(described_class.graphql_name).to eq('IssuableSort') }
it 'exposes all the existing issuable sort values' do
expect(described_class.values.keys).to include(*%w[PRIORITY_ASC PRIORITY_DESC])
end
end
...@@ -45,8 +45,8 @@ describe 'getting an issue list for a project' do ...@@ -45,8 +45,8 @@ describe 'getting an issue list for a project' do
it 'includes discussion locked' do it 'includes discussion locked' do
post_graphql(query, current_user: current_user) post_graphql(query, current_user: current_user)
expect(issues_data[0]['node']['discussionLocked']).to eq false expect(issues_data[0]['node']['discussionLocked']).to eq(false)
expect(issues_data[1]['node']['discussionLocked']).to eq true expect(issues_data[1]['node']['discussionLocked']).to eq(true)
end end
context 'when limiting the number of results' do context 'when limiting the number of results' do
...@@ -79,7 +79,7 @@ describe 'getting an issue list for a project' do ...@@ -79,7 +79,7 @@ describe 'getting an issue list for a project' do
post_graphql(query) post_graphql(query)
expect(issues_data).to eq [] expect(issues_data).to eq([])
end end
end end
...@@ -122,15 +122,15 @@ describe 'getting an issue list for a project' do ...@@ -122,15 +122,15 @@ describe 'getting an issue list for a project' do
let(:end_cursor) { graphql_data['project']['issues']['pageInfo']['endCursor'] } let(:end_cursor) { graphql_data['project']['issues']['pageInfo']['endCursor'] }
context 'when sorting by due date' do context 'when sorting by due date' do
let(:sort_project) { create(:project, :public) } let_it_be(:sort_project) { create(:project, :public) }
let!(:due_issue1) { create(:issue, project: sort_project, due_date: 3.days.from_now) } let_it_be(:due_issue1) { create(:issue, project: sort_project, due_date: 3.days.from_now) }
let!(:due_issue2) { create(:issue, project: sort_project, due_date: nil) } let_it_be(:due_issue2) { create(:issue, project: sort_project, due_date: nil) }
let!(:due_issue3) { create(:issue, project: sort_project, due_date: 2.days.ago) } let_it_be(:due_issue3) { create(:issue, project: sort_project, due_date: 2.days.ago) }
let!(:due_issue4) { create(:issue, project: sort_project, due_date: nil) } let_it_be(:due_issue4) { create(:issue, project: sort_project, due_date: nil) }
let!(:due_issue5) { create(:issue, project: sort_project, due_date: 1.day.ago) } let_it_be(:due_issue5) { create(:issue, project: sort_project, due_date: 1.day.ago) }
let(:params) { 'sort: DUE_DATE_ASC' } let_it_be(:params) { 'sort: DUE_DATE_ASC' }
def query(issue_params = params) def query(issue_params = params)
graphql_query_for( graphql_query_for(
...@@ -160,20 +160,20 @@ describe 'getting an issue list for a project' do ...@@ -160,20 +160,20 @@ describe 'getting an issue list for a project' do
context 'when ascending' do context 'when ascending' do
it 'sorts issues' do it 'sorts issues' do
expect(grab_iids).to eq [due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid] expect(grab_iids).to eq([due_issue3.iid, due_issue5.iid, due_issue1.iid, due_issue4.iid, due_issue2.iid])
end end
context 'when paginating' do context 'when paginating' do
let(:params) { 'sort: DUE_DATE_ASC, first: 2' } let(:params) { 'sort: DUE_DATE_ASC, first: 2' }
it 'sorts issues' do it 'sorts issues' do
expect(grab_iids).to eq [due_issue3.iid, due_issue5.iid] expect(grab_iids).to eq([due_issue3.iid, due_issue5.iid])
cursored_query = query("sort: DUE_DATE_ASC, after: \"#{end_cursor}\"") cursored_query = query("sort: DUE_DATE_ASC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user) post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges'] response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
expect(grab_iids(response_data)).to eq [due_issue1.iid, due_issue4.iid, due_issue2.iid] expect(grab_iids(response_data)).to eq([due_issue1.iid, due_issue4.iid, due_issue2.iid])
end end
end end
end end
...@@ -182,35 +182,35 @@ describe 'getting an issue list for a project' do ...@@ -182,35 +182,35 @@ describe 'getting an issue list for a project' do
let(:params) { 'sort: DUE_DATE_DESC' } let(:params) { 'sort: DUE_DATE_DESC' }
it 'sorts issues' do it 'sorts issues' do
expect(grab_iids).to eq [due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid] expect(grab_iids).to eq([due_issue1.iid, due_issue5.iid, due_issue3.iid, due_issue4.iid, due_issue2.iid])
end end
context 'when paginating' do context 'when paginating' do
let(:params) { 'sort: DUE_DATE_DESC, first: 2' } let(:params) { 'sort: DUE_DATE_DESC, first: 2' }
it 'sorts issues' do it 'sorts issues' do
expect(grab_iids).to eq [due_issue1.iid, due_issue5.iid] expect(grab_iids).to eq([due_issue1.iid, due_issue5.iid])
cursored_query = query("sort: DUE_DATE_DESC, after: \"#{end_cursor}\"") cursored_query = query("sort: DUE_DATE_DESC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user) post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges'] response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
expect(grab_iids(response_data)).to eq [due_issue3.iid, due_issue4.iid, due_issue2.iid] expect(grab_iids(response_data)).to eq([due_issue3.iid, due_issue4.iid, due_issue2.iid])
end end
end end
end end
end end
context 'when sorting by relative position' do context 'when sorting by relative position' do
let(:sort_project) { create(:project, :public) } let_it_be(:sort_project) { create(:project, :public) }
let!(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) } let_it_be(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) }
let!(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) } let_it_be(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) }
let!(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) } let_it_be(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) }
let!(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) } let_it_be(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) }
let!(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) } let_it_be(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) }
let(:params) { 'sort: RELATIVE_POSITION_ASC' } let_it_be(:params) { 'sort: RELATIVE_POSITION_ASC' }
def query(issue_params = params) def query(issue_params = params)
graphql_query_for( graphql_query_for(
...@@ -228,20 +228,91 @@ describe 'getting an issue list for a project' do ...@@ -228,20 +228,91 @@ describe 'getting an issue list for a project' do
context 'when ascending' do context 'when ascending' do
it 'sorts issues' do it 'sorts issues' do
expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid] expect(grab_iids).to eq([relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid])
end end
context 'when paginating' do context 'when paginating' do
let(:params) { 'sort: RELATIVE_POSITION_ASC, first: 2' } let(:params) { 'sort: RELATIVE_POSITION_ASC, first: 2' }
it 'sorts issues' do it 'sorts issues' do
expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid] expect(grab_iids).to eq([relative_issue5.iid, relative_issue3.iid])
cursored_query = query("sort: RELATIVE_POSITION_ASC, after: \"#{end_cursor}\"") cursored_query = query("sort: RELATIVE_POSITION_ASC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user) post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges'] response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
expect(grab_iids(response_data)).to eq [relative_issue1.iid, relative_issue4.iid, relative_issue2.iid] expect(grab_iids(response_data)).to eq([relative_issue1.iid, relative_issue4.iid, relative_issue2.iid])
end
end
end
end
context 'when sorting by priority' do
let_it_be(:sort_project) { create(:project, :public) }
let_it_be(:early_milestone) { create(:milestone, project: sort_project, due_date: 10.days.from_now) }
let_it_be(:late_milestone) { create(:milestone, project: sort_project, due_date: 30.days.from_now) }
let_it_be(:label_1) { create(:label, project: sort_project, priority: 1) }
let_it_be(:label_2) { create(:label, project: sort_project, priority: 5) }
let_it_be(:issue1) { create(:issue, project: sort_project, labels: [label_1], milestone: late_milestone) }
let_it_be(:issue2) { create(:issue, project: sort_project, labels: [label_2]) }
let_it_be(:issue3) { create(:issue, project: sort_project, milestone: early_milestone) }
let_it_be(:issue4) { create(:issue, project: sort_project) }
let_it_be(:params) { 'sort: PRIORITY_ASC' }
def query(issue_params = params)
graphql_query_for(
'project',
{ 'fullPath' => sort_project.full_path },
"issues(#{issue_params}) { pageInfo { endCursor} edges { node { iid dueDate } } }"
)
end
before do
post_graphql(query, current_user: current_user)
end
it_behaves_like 'a working graphql query'
context 'when ascending' do
it 'sorts issues' do
expect(grab_iids).to eq([issue3.iid, issue1.iid, issue2.iid, issue4.iid])
end
context 'when paginating' do
let(:params) { 'sort: PRIORITY_ASC, first: 2' }
it 'sorts issues' do
expect(grab_iids).to eq([issue3.iid, issue1.iid])
cursored_query = query("sort: PRIORITY_ASC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
expect(grab_iids(response_data)).to eq([issue2.iid, issue4.iid])
end
end
end
context 'when descending' do
let(:params) { 'sort: PRIORITY_DESC' }
it 'sorts issues' do
expect(grab_iids).to eq([issue1.iid, issue3.iid, issue2.iid, issue4.iid])
end
context 'when paginating' do
let(:params) { 'sort: PRIORITY_DESC, first: 2' }
it 'sorts issues' do
expect(grab_iids).to eq([issue1.iid, issue3.iid])
cursored_query = query("sort: PRIORITY_DESC, after: \"#{end_cursor}\"")
post_graphql(cursored_query, current_user: current_user)
response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
expect(grab_iids(response_data)).to eq([issue2.iid, issue4.iid])
end end
end end
end 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