Commit ce051400 authored by Heinrich Lee Yu's avatar Heinrich Lee Yu

Merge branch '300115-negated-current-iteration-board-filters' into 'master'

Graphql Issue resolvers implement iteration filters

See merge request gitlab-org/gitlab!59325
parents 44fb7c26 c009da7e
...@@ -7,10 +7,10 @@ module BoardIssueFilterable ...@@ -7,10 +7,10 @@ module BoardIssueFilterable
def issue_filters(args) def issue_filters(args)
filters = args.to_h filters = args.to_h
set_filter_values(filters) set_filter_values(filters)
if filters[:not] if filters[:not]
filters[:not] = filters[:not].to_h
set_filter_values(filters[:not]) set_filter_values(filters[:not])
end end
......
...@@ -2,14 +2,12 @@ ...@@ -2,14 +2,12 @@
module Types module Types
module Boards module Boards
class NegatedBoardIssueInputType < BoardIssueInputBaseType
end
class BoardIssueInputType < BoardIssueInputBaseType class BoardIssueInputType < BoardIssueInputBaseType
graphql_name 'BoardIssueInput' graphql_name 'BoardIssueInput'
argument :not, NegatedBoardIssueInputType, argument :not, NegatedBoardIssueInputType,
required: false, required: false,
prepare: ->(negated_args, ctx) { negated_args.to_h },
description: <<~MD description: <<~MD
List of negated arguments. List of negated arguments.
Warning: this argument is experimental and a subject to change in future. Warning: this argument is experimental and a subject to change in future.
......
# frozen_string_literal: true
module Types
module Boards
class NegatedBoardIssueInputType < BoardIssueInputBaseType
end
end
end
Types::Boards::NegatedBoardIssueInputType.prepend_if_ee('::EE::Types::Boards::NegatedBoardIssueInputType')
...@@ -8287,6 +8287,14 @@ Values for sorting projects. ...@@ -8287,6 +8287,14 @@ Values for sorting projects.
| `SIMILARITY` | Most similar to the search query. | | `SIMILARITY` | Most similar to the search query. |
| `STORAGE` | Sort by storage size. | | `STORAGE` | Sort by storage size. |
### `NegatedIterationWildcardId`
Negated Iteration ID wildcard values.
| Value | Description |
| ----- | ----------- |
| `CURRENT` | Current iteration. |
### `OncallRotationUnitEnum` ### `OncallRotationUnitEnum`
Rotation length unit of an on-call rotation. Rotation length unit of an on-call rotation.
......
...@@ -110,8 +110,10 @@ module EE ...@@ -110,8 +110,10 @@ module EE
if not_params.filter_by_current_iteration? if not_params.filter_by_current_iteration?
items.not_in_iterations(get_current_iteration) items.not_in_iterations(get_current_iteration)
else elsif not_params.filter_by_iteration_title?
items.without_iteration_title(not_params[:iteration_title]) items.without_iteration_title(not_params[:iteration_title])
else
items.not_in_iterations(not_params[:iteration_id])
end end
end end
......
...@@ -10,6 +10,9 @@ module EE ...@@ -10,6 +10,9 @@ module EE
argument :iteration_id, [::GraphQL::ID_TYPE, null: true], argument :iteration_id, [::GraphQL::ID_TYPE, null: true],
required: false, required: false,
description: 'List of iteration Global IDs applied to the issue.' description: 'List of iteration Global IDs applied to the issue.'
argument :iteration_wildcard_id, ::Types::IterationWildcardIdEnum,
required: false,
description: 'Filter by iteration ID wildcard.'
argument :epic_id, GraphQL::STRING_TYPE, argument :epic_id, GraphQL::STRING_TYPE,
required: false, required: false,
description: 'ID of an epic associated with the issues, "none" and "any" values are supported.' description: 'ID of an epic associated with the issues, "none" and "any" values are supported.'
...@@ -21,6 +24,17 @@ module EE ...@@ -21,6 +24,17 @@ module EE
override :resolve_with_lookahead override :resolve_with_lookahead
def resolve_with_lookahead(**args) def resolve_with_lookahead(**args)
args[:iteration_id] = iteration_ids_from_args(args) if args[:iteration_id].present? args[:iteration_id] = iteration_ids_from_args(args) if args[:iteration_id].present?
args[:not][:iteration_id] = iteration_ids_from_args(args[:not]) if args.dig(:not, :iteration_id).present?
prepare_iteration_wildcard_params(args)
super
end
def ready?(**args)
if iteration_params_not_mutually_exclusive?(args) || iteration_params_not_mutually_exclusive?(args.fetch(:not, {}))
arg_str = mutually_exclusive_iteration_args.map { |x| x.to_s.camelize(:lower) }.join(', ')
raise ::Gitlab::Graphql::Errors::ArgumentError, "only one of [#{arg_str}] arguments is allowed at the same time."
end
super super
end end
...@@ -37,6 +51,19 @@ module EE ...@@ -37,6 +51,19 @@ module EE
end end
end end
def prepare_iteration_wildcard_params(args)
args[:iteration_id] = args.delete(:iteration_wildcard_id) if args[:iteration_wildcard_id].present?
args[:not][:iteration_id] = args[:not].delete(:iteration_wildcard_id) if args.dig(:not, :iteration_wildcard_id).present?
end
def iteration_params_not_mutually_exclusive?(args)
args.slice(*mutually_exclusive_iteration_args).compact.size > 1
end
def mutually_exclusive_iteration_args
[:iteration_id, :iteration_wildcard_id]
end
override :preloads override :preloads
def preloads def preloads
super.merge( super.merge(
......
...@@ -12,7 +12,7 @@ module EE ...@@ -12,7 +12,7 @@ module EE
required: false, required: false,
description: 'Filter by epic ID wildcard. Incompatible with epicId.' description: 'Filter by epic ID wildcard. Incompatible with epicId.'
argument :iteration_wildcard_id, ::Types::Boards::IterationWildcardIdEnum, argument :iteration_wildcard_id, ::Types::IterationWildcardIdEnum,
required: false, required: false,
description: 'Filter by iteration ID wildcard.' description: 'Filter by iteration ID wildcard.'
end end
......
# frozen_string_literal: true
module EE
module Types
module Boards
module NegatedBoardIssueInputType
extend ActiveSupport::Concern
prepended do
argument :iteration_wildcard_id, ::Types::NegatedIterationWildcardIdEnum,
required: false,
description: 'Filter by iteration ID wildcard.'
end
end
end
end
end
...@@ -13,6 +13,12 @@ module EE ...@@ -13,6 +13,12 @@ module EE
argument :weight, GraphQL::STRING_TYPE, argument :weight, GraphQL::STRING_TYPE,
required: false, required: false,
description: 'Weight not applied to the issue.' description: 'Weight not applied to the issue.'
argument :iteration_id, [::GraphQL::ID_TYPE],
required: false,
description: 'List of iteration Global IDs not applied to the issue.'
argument :iteration_wildcard_id, ::Types::IterationWildcardIdEnum,
required: false,
description: 'Filter by negated iteration ID wildcard.'
end end
end end
end end
......
# frozen_string_literal: true
module Types
module Boards
class IterationWildcardIdEnum < BaseEnum
graphql_name 'IterationWildcardId'
description 'Iteration ID wildcard values'
value 'NONE', 'No iteration is assigned.'
value 'ANY', 'An iteration is assigned.'
value 'CURRENT', 'Current iteration.'
end
end
end
# frozen_string_literal: true
module Types
class IterationWildcardIdEnum < BaseEnum
graphql_name 'IterationWildcardId'
description 'Iteration ID wildcard values'
value 'NONE', 'No iteration is assigned.'
value 'ANY', 'An iteration is assigned.'
value 'CURRENT', 'Current iteration.'
end
end
# frozen_string_literal: true
module Types
class NegatedIterationWildcardIdEnum < BaseEnum
graphql_name 'NegatedIterationWildcardId'
description 'Negated Iteration ID wildcard values'
value 'CURRENT', 'Current iteration.'
end
end
---
title: GraphQL issue filters support 'current' iteration.
Board issue filters support negated 'current' iteration
merge_request: 59325
author:
type: added
...@@ -46,9 +46,11 @@ RSpec.describe Resolvers::BoardListIssuesResolver do ...@@ -46,9 +46,11 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
end end
context 'filtering by iteration' do context 'filtering by iteration' do
let_it_be(:iteration) { create(:iteration, group: group) } let_it_be(:iteration) { create(:iteration, group: group, start_date: 1.week.ago, due_date: 2.days.ago) }
let_it_be(:current_iteration) { create(:iteration, :started, group: group, start_date: Date.today, due_date: 1.day.from_now) }
let_it_be(:issue_with_iteration) { create(:issue, project: project, labels: [label], iteration: iteration) } let_it_be(:issue_with_iteration) { create(:issue, project: project, labels: [label], iteration: iteration) }
let_it_be(:issue_without_iteration) { create(:issue, project: project, labels: [label]) } let_it_be(:issue_without_iteration) { create(:issue, project: project, labels: [label]) }
let_it_be(:issue_with_current_iteration) { create(:issue, project: project, labels: [label], iteration: current_iteration) }
it 'accepts iteration title' do it 'accepts iteration title' do
result = resolve_board_list_issues({ filters: { iteration_title: iteration.title } }) result = resolve_board_list_issues({ filters: { iteration_title: iteration.title } })
...@@ -61,6 +63,14 @@ RSpec.describe Resolvers::BoardListIssuesResolver do ...@@ -61,6 +63,14 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
expect(result).to contain_exactly(issue_without_iteration) expect(result).to contain_exactly(issue_without_iteration)
end end
context 'filterning by negated iteration' do
it 'accepts iteration wildcard id' do
result = resolve_board_list_issues({ filters: { not: { iteration_wildcard_id: 'CURRENT' } } })
expect(result).to contain_exactly(issue_without_iteration, issue_with_iteration)
end
end
end end
end end
......
...@@ -92,6 +92,26 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -92,6 +92,26 @@ RSpec.describe Resolvers::IssuesResolver do
end end
end end
describe 'filtering by iteration wildcard' do
it 'returns issues with current iteration' do
expect(resolve_issues(iteration_wildcard_id: 'CURRENT')).to contain_exactly(issue3)
end
it 'returns issues with any iteration' do
expect(resolve_issues(iteration_wildcard_id: 'ANY')).to contain_exactly(issue1, issue3)
end
it 'returns issues with no iteration' do
expect(resolve_issues(iteration_wildcard_id: 'NONE')).to contain_exactly(issue2, issue4)
end
it 'raises a mutually exclusive filter error when wildcard and list are provided' do
expect do
resolve_issues(iteration_id: [iteration1.to_global_id], iteration_wildcard_id: 'CURRENT')
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [iterationId, iterationWildcardId] arguments is allowed at the same time.')
end
end
describe 'filter by epic' do describe 'filter by epic' do
it 'returns issues without epic when epic_id is "none"' do it 'returns issues without epic when epic_id is "none"' do
expect(resolve_issues(epic_id: 'none')).to contain_exactly(issue4, issue3) expect(resolve_issues(epic_id: 'none')).to contain_exactly(issue4, issue3)
...@@ -138,6 +158,28 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -138,6 +158,28 @@ RSpec.describe Resolvers::IssuesResolver do
expect(resolve_issues(not: { weight: '3' })).to contain_exactly(issue1, issue2, issue4) expect(resolve_issues(not: { weight: '3' })).to contain_exactly(issue1, issue2, issue4)
end end
end end
describe 'filtering by negated iteration' do
it 'returns issues without iteration using RAW id' do
expect(resolve_issues(not: { iteration_id: [iteration1.id.to_s] })).to contain_exactly(issue2, issue3, issue4)
end
it 'works with global IDs' do
expect(resolve_issues(not: { iteration_id: [iteration1.to_global_id] })).to contain_exactly(issue2, issue3, issue4)
end
it 'raises a mutually exclusive filter error when wildcard and list are provided' do
expect do
resolve_issues(not: { iteration_id: [iteration1.to_global_id], iteration_wildcard_id: 'CURRENT' })
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [iterationId, iterationWildcardId] arguments is allowed at the same time.')
end
end
describe 'filtering by negated iteration wildcard' do
it 'returns issues not in the current iteration' do
expect(resolve_issues(not: { iteration_wildcard_id: 'CURRENT' })).to contain_exactly(issue1, issue2, issue4)
end
end
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