Commit 552576ff authored by Eulyeon Ko's avatar Eulyeon Ko

Allow sort by blocking issues via GraphQL

- Allow sorting by blocking issues in ascending order
(adds an AR scope to Issue model).
- Allow sorting issue by blocking issues via GraphQL.
- Expose blocking_issues_count as blockingCount field
of 'issue' type.

Changelog: added
EE: true
parent 7bd32bfe
...@@ -8885,6 +8885,7 @@ Relationship between an epic and an issue. ...@@ -8885,6 +8885,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. |
...@@ -9939,6 +9940,7 @@ Returns [`VulnerabilitySeveritiesCount`](#vulnerabilityseveritiescount). ...@@ -9939,6 +9940,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. |
...@@ -14595,6 +14597,8 @@ Values for sorting issues. ...@@ -14595,6 +14597,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. |
......
...@@ -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 (descending sort only) **(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,9 @@ module EE ...@@ -18,6 +18,9 @@ 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,
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.'
...@@ -45,6 +48,10 @@ module EE ...@@ -45,6 +48,10 @@ module EE
object.weight_available? ? object.weight : nil object.weight_available? ? object.weight : nil
end end
def blocking_count
object.blocking_issues_count
end
def blocked def blocked
::Gitlab::Graphql::Aggregations::Issues::LazyBlockAggregate.new(context, object.id) do |count| ::Gitlab::Graphql::Aggregations::Issues::LazyBlockAggregate.new(context, object.id) do |count|
(count || 0) > 0 (count || 0) > 0
......
...@@ -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,18 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -64,6 +64,18 @@ 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', :aggregate_failures do
expect(issue1.id).to be < (issue2.id)
expect(resolve_issues(sort: :blocking_issues_asc).to_a).to eq [issue4, issue2, issue1, issue3]
end
it 'sorts issues descending', :aggregate_failures do
expect(issue1.id).to be < (issue2.id)
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)
......
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