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 ...@@ -15,6 +15,7 @@ module Resolvers
type Types::IssueType.connection_type, null: true type Types::IssueType.connection_type, null: true
NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc NON_STABLE_CURSOR_SORTS = %i[priority_asc priority_desc
popularity_asc popularity_desc
label_priority_asc label_priority_desc label_priority_asc label_priority_desc
milestone_due_asc milestone_due_desc].freeze milestone_due_asc milestone_due_desc].freeze
......
...@@ -10,6 +10,8 @@ module Types ...@@ -10,6 +10,8 @@ module Types
value 'RELATIVE_POSITION_ASC', 'Relative position by ascending order.', value: :relative_position_asc 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_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 '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
end end
......
...@@ -8891,6 +8891,7 @@ Relationship between an epic and an issue. ...@@ -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="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="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="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="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="epicissueconfidential"></a>`confidential` | [`Boolean!`](#boolean) | Indicates the issue is confidential. |
| <a id="epicissuecreatenoteemail"></a>`createNoteEmail` | [`String`](#string) | User specific email address for the issue. | | <a id="epicissuecreatenoteemail"></a>`createNoteEmail` | [`String`](#string) | User specific email address for the issue. |
...@@ -9945,6 +9946,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount). ...@@ -9945,6 +9946,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount).
| <a id="issueblocked"></a>`blocked` | [`Boolean!`](#boolean) | Indicates the issue is blocked. | | <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="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="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="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="issueconfidential"></a>`confidential` | [`Boolean!`](#boolean) | Indicates the issue is confidential. |
| <a id="issuecreatenoteemail"></a>`createNoteEmail` | [`String`](#string) | User specific email address for the issue. | | <a id="issuecreatenoteemail"></a>`createNoteEmail` | [`String`](#string) | User specific email address for the issue. |
...@@ -14611,6 +14613,8 @@ Values for sorting issues. ...@@ -14611,6 +14613,8 @@ Values for sorting issues.
| Value | Description | | 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_asc"></a>`CREATED_ASC` | Created at ascending order. |
| <a id="issuesortcreated_desc"></a>`CREATED_DESC` | Created at descending 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. | | <a id="issuesortdue_date_asc"></a>`DUE_DATE_ASC` | Due date by ascending order. |
...@@ -14619,6 +14623,8 @@ Values for sorting issues. ...@@ -14619,6 +14623,8 @@ Values for sorting issues.
| <a id="issuesortlabel_priority_desc"></a>`LABEL_PRIORITY_DESC` | Label priority by descending order. | | <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_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="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_asc"></a>`PRIORITY_ASC` | Priority by ascending order. |
| <a id="issuesortpriority_desc"></a>`PRIORITY_DESC` | Priority by descending order. | | <a id="issuesortpriority_desc"></a>`PRIORITY_DESC` | Priority by descending order. |
| <a id="issuesortpublished_asc"></a>`PUBLISHED_ASC` | Published issues shown last. | | <a id="issuesortpublished_asc"></a>`PUBLISHED_ASC` | Published issues shown last. |
......
...@@ -426,7 +426,7 @@ To set a WIP limit for a list: ...@@ -426,7 +426,7 @@ To set a WIP limit for a list:
1. Enter the maximum number of issues. 1. Enter the maximum number of issues.
1. Press <kbd>Enter</kbd> to save. 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. > - [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. > - [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 ...@@ -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: You can sort a list of issues several ways, including by:
- Blocking - Blocking **(PREMIUM)**
- Created date - Created date
- Due date - Due date
- Label priority - Label priority
...@@ -51,7 +51,7 @@ This ordering also affects [issue boards](../issue_board.md#how-gitlab-orders-is ...@@ -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, Changing the order in an issue list changes the ordering in an issue board,
and vice versa. 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. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34247/) in GitLab 13.7.
......
...@@ -12,6 +12,8 @@ module EE ...@@ -12,6 +12,8 @@ module EE
value 'PUBLISHED_DESC', 'Published issues shown first.', value: :published_desc 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_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 '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 end
end end
......
...@@ -18,6 +18,10 @@ module EE ...@@ -18,6 +18,10 @@ module EE
field :blocked, GraphQL::BOOLEAN_TYPE, null: false, field :blocked, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates the issue is blocked.' 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, field :blocked_by_count, GraphQL::INT_TYPE, null: true,
description: 'Count of issues blocking this issue.' description: 'Count of issues blocking this issue.'
......
...@@ -21,6 +21,7 @@ module EE ...@@ -21,6 +21,7 @@ module EE
# widget supporting custom issue types - see https://gitlab.com/gitlab-org/gitlab/-/issues/292035 # widget supporting custom issue types - see https://gitlab.com/gitlab-org/gitlab/-/issues/292035
include IssueWidgets::ActsLikeRequirement 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_blocking_issues_desc, -> { reorder(blocking_issues_count: :desc) }
scope :order_weight_desc, -> { reorder ::Gitlab::Database.nulls_last_order('weight', 'DESC') } scope :order_weight_desc, -> { reorder ::Gitlab::Database.nulls_last_order('weight', 'DESC') }
scope :order_weight_asc, -> { reorder ::Gitlab::Database.nulls_last_order('weight') } scope :order_weight_asc, -> { reorder ::Gitlab::Database.nulls_last_order('weight') }
...@@ -186,6 +187,7 @@ module EE ...@@ -186,6 +187,7 @@ module EE
override :sort_by_attribute override :sort_by_attribute
def sort_by_attribute(method, excluded_labels: []) def sort_by_attribute(method, excluded_labels: [])
case method.to_s 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 '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', 'weight_asc' then order_weight_asc.with_order_id_desc
when 'weight_desc' then order_weight_desc.with_order_id_desc when 'weight_desc' then order_weight_desc.with_order_id_desc
......
...@@ -17,9 +17,9 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -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(: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(: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(: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 } 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 } 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 } let_it_be(:issue4) { create :issue, :published, project: project }
before do before do
...@@ -64,6 +64,16 @@ RSpec.describe Resolvers::IssuesResolver 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] expect(resolve_issues(sort: :sla_due_at_desc).to_a).to eq [sla_due_last, sla_due_first]
end end
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 end
describe 'filtering by iteration' do describe 'filtering by iteration' do
......
...@@ -7,6 +7,7 @@ RSpec.describe GitlabSchema.types['Issue'] 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(:iteration) }
it { expect(described_class).to have_graphql_field(:weight) } 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(: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) }
it { expect(described_class).to have_graphql_field(:blocked_by_count) } it { expect(described_class).to have_graphql_field(:blocked_by_count) }
it { expect(described_class).to have_graphql_field(:blocked_by_issues) } it { expect(described_class).to have_graphql_field(:blocked_by_issues) }
......
...@@ -434,10 +434,17 @@ RSpec.describe Issue do ...@@ -434,10 +434,17 @@ RSpec.describe Issue do
end end
context 'by blocking issues' do context 'by blocking issues' do
it 'orders by descending blocking issues count' do let_it_be(:issue_1) { create(:issue, blocking_issues_count: 3) }
issue_1 = create(:issue, blocking_issues_count: 3) let_it_be(:issue_2) { create(:issue, blocking_issues_count: 1) }
issue_2 = create(:issue, blocking_issues_count: 2)
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') results = described_class.sort_by_attribute('blocking_issues_desc')
expect(results.first).to eq(issue_1) expect(results.first).to eq(issue_1)
......
...@@ -290,6 +290,42 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -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]) expect(resolve_issues(sort: :severity_desc).to_a).to eq([issue_high_severity, issue_low_severity, issue_no_severity])
end end
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 end
it 'returns issues user can see' do 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