Commit bd76a7ba authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre

Merge branch '327243-allow-sorting-issues-by-blocking-issues-in-graphql-api' into 'master'

Allow sorting by blocking issues and popularity via GraphQL API

See merge request gitlab-org/gitlab!65323
parents 5ae32778 46e37c85
......@@ -15,6 +15,7 @@ module Resolvers
type Types::IssueType.connection_type, null: true
NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc
popularity_asc popularity_desc
label_priority_asc label_priority_desc
milestone_due_asc milestone_due_desc].freeze
......
......@@ -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 '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
end
......
......@@ -8891,6 +8891,7 @@ Relationship between an epic and an issue.
| <a id="epicissueblocked"></a>`blocked` | [`Boolean!`](#boolean) | Indicates the issue is blocked. |
| <a id="epicissueblockedbycount"></a>`blockedByCount` | [`Int`](#int) | Count of issues blocking this issue. |
| <a id="epicissueblockedbyissues"></a>`blockedByIssues` | [`IssueConnection`](#issueconnection) | Issues blocking this issue. (see [Connections](#connections)) |
| <a id="epicissueblockingcount"></a>`blockingCount` | [`Int!`](#int) | Count of issues this issue is blocking. |
| <a id="epicissueclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the issue was closed. |
| <a id="epicissueconfidential"></a>`confidential` | [`Boolean!`](#boolean) | Indicates the issue is confidential. |
| <a id="epicissuecreatenoteemail"></a>`createNoteEmail` | [`String`](#string) | User specific email address for the issue. |
......@@ -9945,6 +9946,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="issueblocked"></a>`blocked` | [`Boolean!`](#boolean) | Indicates the issue is blocked. |
| <a id="issueblockedbycount"></a>`blockedByCount` | [`Int`](#int) | Count of issues blocking this issue. |
| <a id="issueblockedbyissues"></a>`blockedByIssues` | [`IssueConnection`](#issueconnection) | Issues blocking this issue. (see [Connections](#connections)) |
| <a id="issueblockingcount"></a>`blockingCount` | [`Int!`](#int) | Count of issues this issue is blocking. |
| <a id="issueclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the issue was closed. |
| <a id="issueconfidential"></a>`confidential` | [`Boolean!`](#boolean) | Indicates the issue is confidential. |
| <a id="issuecreatenoteemail"></a>`createNoteEmail` | [`String`](#string) | User specific email address for the issue. |
......@@ -14611,6 +14613,8 @@ Values for sorting issues.
| Value | Description |
| ----- | ----------- |
| <a id="issuesortblocking_issues_asc"></a>`BLOCKING_ISSUES_ASC` | Blocking issues count by ascending order. |
| <a id="issuesortblocking_issues_desc"></a>`BLOCKING_ISSUES_DESC` | Blocking issues count by descending order. |
| <a id="issuesortcreated_asc"></a>`CREATED_ASC` | Created at ascending order. |
| <a id="issuesortcreated_desc"></a>`CREATED_DESC` | Created at descending order. |
| <a id="issuesortdue_date_asc"></a>`DUE_DATE_ASC` | Due date by ascending order. |
......@@ -14619,6 +14623,8 @@ Values for sorting issues.
| <a id="issuesortlabel_priority_desc"></a>`LABEL_PRIORITY_DESC` | Label priority by descending order. |
| <a id="issuesortmilestone_due_asc"></a>`MILESTONE_DUE_ASC` | Milestone due date by ascending order. |
| <a id="issuesortmilestone_due_desc"></a>`MILESTONE_DUE_DESC` | Milestone due date by descending order. |
| <a id="issuesortpopularity_asc"></a>`POPULARITY_ASC` | Number of upvotes (awarded "thumbs up" emoji) by ascending order. |
| <a id="issuesortpopularity_desc"></a>`POPULARITY_DESC` | Number of upvotes (awarded "thumbs up" emoji) by descending order. |
| <a id="issuesortpriority_asc"></a>`PRIORITY_ASC` | Priority by ascending order. |
| <a id="issuesortpriority_desc"></a>`PRIORITY_DESC` | Priority by descending order. |
| <a id="issuesortpublished_asc"></a>`PUBLISHED_ASC` | Published issues shown last. |
......
......@@ -426,7 +426,7 @@ To set a WIP limit for a list:
1. Enter the maximum number of issues.
1. Press <kbd>Enter</kbd> to save.
## Blocked issues
## Blocked issues **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34723) in GitLab 12.8.
> - [View blocking issues when hovering over blocked icon](https://gitlab.com/gitlab-org/gitlab/-/issues/210452) in GitLab 13.10.
......
......@@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
You can sort a list of issues several ways, including by:
- Blocking
- Blocking **(PREMIUM)**
- Created date
- Due date
- Label priority
......@@ -51,7 +51,7 @@ This ordering also affects [issue boards](../issue_board.md#how-gitlab-orders-is
Changing the order in an issue list changes the ordering in an issue board,
and vice versa.
## Sorting by blocking issues
## Sorting by blocking issues **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34247/) in GitLab 13.7.
......
......@@ -12,6 +12,8 @@ module EE
value 'PUBLISHED_DESC', 'Published issues shown first.', value: :published_desc
value 'SLA_DUE_AT_ASC', 'Issues with earliest SLA due time shown first.', value: :sla_due_at_asc
value 'SLA_DUE_AT_DESC', 'Issues with latest SLA due time shown first.', value: :sla_due_at_desc
value 'BLOCKING_ISSUES_ASC', 'Blocking issues count by ascending order.', value: :blocking_issues_asc
value 'BLOCKING_ISSUES_DESC', 'Blocking issues count by descending order.', value: :blocking_issues_desc
end
end
end
......
......@@ -18,6 +18,10 @@ module EE
field :blocked, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates the issue is blocked.'
field :blocking_count, GraphQL::INT_TYPE, null: false,
method: :blocking_issues_count,
description: 'Count of issues this issue is blocking.'
field :blocked_by_count, GraphQL::INT_TYPE, null: true,
description: 'Count of issues blocking this issue.'
......
......@@ -21,6 +21,7 @@ module EE
# widget supporting custom issue types - see https://gitlab.com/gitlab-org/gitlab/-/issues/292035
include IssueWidgets::ActsLikeRequirement
scope :order_blocking_issues_asc, -> { reorder(blocking_issues_count: :asc) }
scope :order_blocking_issues_desc, -> { reorder(blocking_issues_count: :desc) }
scope :order_weight_desc, -> { reorder ::Gitlab::Database.nulls_last_order('weight', 'DESC') }
scope :order_weight_asc, -> { reorder ::Gitlab::Database.nulls_last_order('weight') }
......@@ -186,6 +187,7 @@ module EE
override :sort_by_attribute
def sort_by_attribute(method, excluded_labels: [])
case method.to_s
when 'blocking_issues_asc' then order_blocking_issues_asc.with_order_id_desc
when 'blocking_issues_desc' then order_blocking_issues_desc.with_order_id_desc
when 'weight', 'weight_asc' then order_weight_asc.with_order_id_desc
when 'weight_desc' then order_weight_desc.with_order_id_desc
......
......@@ -17,9 +17,9 @@ RSpec.describe Resolvers::IssuesResolver do
let_it_be(:iteration1) { create(:iteration, group: group, start_date: 2.weeks.ago, due_date: 1.week.ago) }
let_it_be(:current_iteration) { create(:iteration, group: group, start_date: Date.yesterday, due_date: 1.day.from_now) }
let_it_be(:issue1) { create :issue, project: project, epic: epic1, iteration: iteration1 }
let_it_be(:issue2) { create :issue, project: project, epic: epic2, weight: 1 }
let_it_be(:issue3) { create :issue, project: project, weight: 3, iteration: current_iteration }
let_it_be(:issue1) { create :issue, project: project, epic: epic1, iteration: iteration1, blocking_issues_count: 2 }
let_it_be(:issue2) { create :issue, project: project, epic: epic2, weight: 1, blocking_issues_count: 2 }
let_it_be(:issue3) { create :issue, project: project, weight: 3, iteration: current_iteration, blocking_issues_count: 4 }
let_it_be(:issue4) { create :issue, :published, project: project }
before do
......@@ -64,6 +64,16 @@ RSpec.describe Resolvers::IssuesResolver do
expect(resolve_issues(sort: :sla_due_at_desc).to_a).to eq [sla_due_last, sla_due_first]
end
end
context 'when sorting by blocking issues count (ties broken by id in desc order)' do
it 'sorts issues ascending' do
expect(resolve_issues(sort: :blocking_issues_asc).to_a).to eq [issue4, issue2, issue1, issue3]
end
it 'sorts issues descending' do
expect(resolve_issues(sort: :blocking_issues_desc).to_a).to eq [issue3, issue2, issue1, issue4]
end
end
end
describe 'filtering by iteration' do
......
......@@ -7,6 +7,7 @@ RSpec.describe GitlabSchema.types['Issue'] do
it { expect(described_class).to have_graphql_field(:iteration) }
it { expect(described_class).to have_graphql_field(:weight) }
it { expect(described_class).to have_graphql_field(:health_status) }
it { expect(described_class).to have_graphql_field(:blocking_count) }
it { expect(described_class).to have_graphql_field(:blocked) }
it { expect(described_class).to have_graphql_field(:blocked_by_count) }
it { expect(described_class).to have_graphql_field(:blocked_by_issues) }
......
......@@ -434,10 +434,17 @@ RSpec.describe Issue do
end
context 'by blocking issues' do
it 'orders by descending blocking issues count' do
issue_1 = create(:issue, blocking_issues_count: 3)
issue_2 = create(:issue, blocking_issues_count: 2)
let_it_be(:issue_1) { create(:issue, blocking_issues_count: 3) }
let_it_be(:issue_2) { create(:issue, blocking_issues_count: 1) }
it 'orders by ascending blocking issues count', :aggregate_failures do
results = described_class.sort_by_attribute('blocking_issues_asc')
expect(results.first).to eq(issue_2)
expect(results.second).to eq(issue_1)
end
it 'orders by descending blocking issues count', :aggregate_failures do
results = described_class.sort_by_attribute('blocking_issues_desc')
expect(results.first).to eq(issue_1)
......
......@@ -290,6 +290,42 @@ RSpec.describe Resolvers::IssuesResolver do
expect(resolve_issues(sort: :severity_desc).to_a).to eq([issue_high_severity, issue_low_severity, issue_no_severity])
end
end
context 'when sorting by popularity' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue1) { create(:issue, project: project) } # has one upvote
let_it_be(:issue2) { create(:issue, project: project) } # has two upvote
let_it_be(:issue3) { create(:issue, project: project) }
let_it_be(:issue4) { create(:issue, project: project) } # has one upvote
before do
create(:award_emoji, :upvote, awardable: issue1)
create(:award_emoji, :upvote, awardable: issue2)
create(:award_emoji, :upvote, awardable: issue2)
create(:award_emoji, :upvote, awardable: issue4)
end
it 'sorts issues ascending (ties broken by id in desc order)' do
expect(resolve_issues(sort: :popularity_asc).to_a).to eq([issue3, issue4, issue1, issue2])
end
it 'sorts issues descending (ties broken by id in desc order)' do
expect(resolve_issues(sort: :popularity_desc).to_a).to eq([issue2, issue4, issue1, issue3])
end
end
context 'when sorting with non-stable cursors' do
%i[priority_asc priority_desc
popularity_asc popularity_desc
label_priority_asc label_priority_desc
milestone_due_asc milestone_due_desc].each do |sort_by|
it "uses offset-pagination when sorting by #{sort_by}" do
resolved = resolve_issues(sort: sort_by)
expect(resolved).to be_a(::Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection)
end
end
end
end
it 'returns issues user can see' do
......
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