Commit 47d801b1 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch '235699-graphql-board-issue-filters' into 'master'

GraphQL: Board issue filters

See merge request gitlab-org/gitlab!40602
parents ab089c5e cce68968
...@@ -2,12 +2,20 @@ ...@@ -2,12 +2,20 @@
module Resolvers module Resolvers
class BoardListIssuesResolver < BaseResolver class BoardListIssuesResolver < BaseResolver
include BoardIssueFilterable
argument :filters, Types::Boards::BoardIssueInputType,
required: false,
description: 'Filters applied when selecting issues in the board list'
type Types::IssueType, null: true type Types::IssueType, null: true
alias_method :list, :object alias_method :list, :object
def resolve(**args) def resolve(**args)
service = Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], { board_id: list.board.id, id: list.id }) filter_params = issue_filters(args[:filters]).merge(board_id: list.board.id, id: list.id)
service = Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], filter_params)
Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(service.execute) Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(service.execute)
end end
......
# frozen_string_literal: true
module BoardIssueFilterable
extend ActiveSupport::Concern
private
def issue_filters(args)
filters = args.to_h
set_filter_values(filters)
if filters[:not]
filters[:not] = filters[:not].to_h
set_filter_values(filters[:not])
end
filters
end
def set_filter_values(filters)
end
end
::BoardIssueFilterable.prepend_if_ee('::EE::Resolvers::BoardIssueFilterable')
...@@ -41,7 +41,7 @@ module Types ...@@ -41,7 +41,7 @@ module Types
list = self.object list = self.object
user = context[:current_user] user = context[:current_user]
Boards::Issues::ListService ::Boards::Issues::ListService
.new(list.board.resource_parent, user, board_id: list.board_id, id: list.id) .new(list.board.resource_parent, user, board_id: list.board_id, id: list.id)
.metadata .metadata
end end
......
# frozen_string_literal: true
module Types
module Boards
# rubocop: disable Graphql/AuthorizeTypes
class BoardIssueInputBaseType < BaseInputObject
argument :label_name, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Filter by label name'
argument :milestone_title, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by milestone title'
argument :assignee_username, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Filter by assignee username'
argument :author_username, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by author username'
argument :release_tag, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by release tag'
argument :my_reaction_emoji, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by reaction emoji'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
Types::Boards::BoardIssueInputBaseType.prepend_if_ee('::EE::Types::Boards::BoardIssueInputBaseType')
# frozen_string_literal: true
module Types
module Boards
# rubocop: disable Graphql/AuthorizeTypes
class NegatedBoardIssueInputType < BoardIssueInputBaseType
end
class BoardIssueInputType < BoardIssueInputBaseType
graphql_name 'BoardIssueInput'
argument :not, NegatedBoardIssueInputType,
required: false,
description: 'List of negated params. Warning: this argument is experimental and a subject to change in future'
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query for issue title or description'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
Types::Boards::BoardIssueInputType.prepend_if_ee('::EE::Types::Boards::BoardIssueInputType')
---
title: Add issue filters when listing board issues in GraphQL
merge_request: 40602
author:
type: added
...@@ -1029,7 +1029,7 @@ type Board { ...@@ -1029,7 +1029,7 @@ type Board {
""" """
Filters applied when selecting issues on the board Filters applied when selecting issues on the board
""" """
issueFilters: BoardEpicIssueInput issueFilters: BoardIssueInput
""" """
Returns the last _n_ elements from the list. Returns the last _n_ elements from the list.
...@@ -1133,7 +1133,12 @@ type BoardEdge { ...@@ -1133,7 +1133,12 @@ type BoardEdge {
node: Board node: Board
} }
input BoardEpicIssueInput { """
Identifier of Board
"""
scalar BoardID
input BoardIssueInput {
""" """
Filter by assignee username Filter by assignee username
""" """
...@@ -1145,9 +1150,14 @@ input BoardEpicIssueInput { ...@@ -1145,9 +1150,14 @@ input BoardEpicIssueInput {
authorUsername: String authorUsername: String
""" """
Filter by epic ID Filter by epic ID. Incompatible with epicWildcardId
"""
epicId: ID
"""
Filter by epic ID wildcard. Incompatible with epicId
""" """
epicId: String epicWildcardId: EpicWildcardId
""" """
Filter by label name Filter by label name
...@@ -1167,7 +1177,7 @@ input BoardEpicIssueInput { ...@@ -1167,7 +1177,7 @@ input BoardEpicIssueInput {
""" """
List of negated params. Warning: this argument is experimental and a subject to change in future List of negated params. Warning: this argument is experimental and a subject to change in future
""" """
not: NegatedBoardEpicIssueInput not: NegatedBoardIssueInput
""" """
Filter by release tag Filter by release tag
...@@ -1185,11 +1195,6 @@ input BoardEpicIssueInput { ...@@ -1185,11 +1195,6 @@ input BoardEpicIssueInput {
weight: String weight: String
} }
"""
Identifier of Board
"""
scalar BoardID
""" """
Represents a list for an issue board Represents a list for an issue board
""" """
...@@ -1223,6 +1228,11 @@ type BoardList { ...@@ -1223,6 +1228,11 @@ type BoardList {
""" """
before: String before: String
"""
Filters applied when selecting issues in the board list
"""
filters: BoardIssueInput
""" """
Returns the first _n_ elements from the list. Returns the first _n_ elements from the list.
""" """
...@@ -5884,6 +5894,21 @@ type EpicTreeReorderPayload { ...@@ -5884,6 +5894,21 @@ type EpicTreeReorderPayload {
errors: [String!]! errors: [String!]!
} }
"""
Epic ID wildcard values
"""
enum EpicWildcardId {
"""
Any epic is assigned
"""
ANY
"""
No epic is assigned
"""
NONE
}
type GeoNode { type GeoNode {
""" """
The maximum concurrency of container repository sync for this secondary node The maximum concurrency of container repository sync for this secondary node
...@@ -10296,7 +10321,7 @@ type NamespaceIncreaseStorageTemporarilyPayload { ...@@ -10296,7 +10321,7 @@ type NamespaceIncreaseStorageTemporarilyPayload {
namespace: Namespace namespace: Namespace
} }
input NegatedBoardEpicIssueInput { input NegatedBoardIssueInput {
""" """
Filter by assignee username Filter by assignee username
""" """
...@@ -10308,9 +10333,9 @@ input NegatedBoardEpicIssueInput { ...@@ -10308,9 +10333,9 @@ input NegatedBoardEpicIssueInput {
authorUsername: String authorUsername: String
""" """
Filter by epic ID Filter by epic ID. Incompatible with epicWildcardId
""" """
epicId: String epicId: ID
""" """
Filter by label name Filter by label name
......
...@@ -2716,7 +2716,7 @@ ...@@ -2716,7 +2716,7 @@
"description": "Filters applied when selecting issues on the board", "description": "Filters applied when selecting issues on the board",
"type": { "type": {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "BoardEpicIssueInput", "name": "BoardIssueInput",
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
...@@ -3041,9 +3041,19 @@ ...@@ -3041,9 +3041,19 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "SCALAR",
"name": "BoardID",
"description": "Identifier of Board",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "BoardEpicIssueInput", "name": "BoardIssueInput",
"description": null, "description": null,
"fields": null, "fields": null,
"inputFields": [ "inputFields": [
...@@ -3106,8 +3116,8 @@ ...@@ -3106,8 +3116,8 @@
"defaultValue": null "defaultValue": null
}, },
{ {
"name": "epicId", "name": "myReactionEmoji",
"description": "Filter by epic ID", "description": "Filter by reaction emoji",
"type": { "type": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "String", "name": "String",
...@@ -3116,11 +3126,11 @@ ...@@ -3116,11 +3126,11 @@
"defaultValue": null "defaultValue": null
}, },
{ {
"name": "myReactionEmoji", "name": "epicId",
"description": "Filter by reaction emoji", "description": "Filter by epic ID. Incompatible with epicWildcardId",
"type": { "type": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "String", "name": "ID",
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
...@@ -3140,7 +3150,7 @@ ...@@ -3140,7 +3150,7 @@
"description": "List of negated params. Warning: this argument is experimental and a subject to change in future", "description": "List of negated params. Warning: this argument is experimental and a subject to change in future",
"type": { "type": {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "NegatedBoardEpicIssueInput", "name": "NegatedBoardIssueInput",
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
...@@ -3154,22 +3164,22 @@ ...@@ -3154,22 +3164,22 @@
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
},
{
"name": "epicWildcardId",
"description": "Filter by epic ID wildcard. Incompatible with epicId",
"type": {
"kind": "ENUM",
"name": "EpicWildcardId",
"ofType": null
},
"defaultValue": null
} }
], ],
"interfaces": null, "interfaces": null,
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "SCALAR",
"name": "BoardID",
"description": "Identifier of Board",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "BoardList", "name": "BoardList",
...@@ -3225,6 +3235,16 @@ ...@@ -3225,6 +3235,16 @@
"name": "issues", "name": "issues",
"description": "Board issues", "description": "Board issues",
"args": [ "args": [
{
"name": "filters",
"description": "Filters applied when selecting issues in the board list",
"type": {
"kind": "INPUT_OBJECT",
"name": "BoardIssueInput",
"ofType": null
},
"defaultValue": null
},
{ {
"name": "after", "name": "after",
"description": "Returns the elements in the list that come after the specified cursor.", "description": "Returns the elements in the list that come after the specified cursor.",
...@@ -16457,6 +16477,29 @@ ...@@ -16457,6 +16477,29 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "ENUM",
"name": "EpicWildcardId",
"description": "Epic ID wildcard values",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "NONE",
"description": "No epic is assigned",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ANY",
"description": "Any epic is assigned",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{ {
"kind": "SCALAR", "kind": "SCALAR",
"name": "Float", "name": "Float",
...@@ -30834,7 +30877,7 @@ ...@@ -30834,7 +30877,7 @@
}, },
{ {
"kind": "INPUT_OBJECT", "kind": "INPUT_OBJECT",
"name": "NegatedBoardEpicIssueInput", "name": "NegatedBoardIssueInput",
"description": null, "description": null,
"fields": null, "fields": null,
"inputFields": [ "inputFields": [
...@@ -30897,8 +30940,8 @@ ...@@ -30897,8 +30940,8 @@
"defaultValue": null "defaultValue": null
}, },
{ {
"name": "epicId", "name": "myReactionEmoji",
"description": "Filter by epic ID", "description": "Filter by reaction emoji",
"type": { "type": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "String", "name": "String",
...@@ -30907,11 +30950,11 @@ ...@@ -30907,11 +30950,11 @@
"defaultValue": null "defaultValue": null
}, },
{ {
"name": "myReactionEmoji", "name": "epicId",
"description": "Filter by reaction emoji", "description": "Filter by epic ID. Incompatible with epicWildcardId",
"type": { "type": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "String", "name": "ID",
"ofType": null "ofType": null
}, },
"defaultValue": null "defaultValue": null
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
query BoardEE( query BoardEE(
$fullPath: ID! $fullPath: ID!
$boardId: ID! $boardId: ID!
$issueFilters: BoardEpicIssueInput $issueFilters: BoardIssueInput
$withLists: Boolean = true $withLists: Boolean = true
$isGroup: Boolean = false $isGroup: Boolean = false
$isProject: Boolean = false $isProject: Boolean = false
......
# frozen_string_literal: true
module EE
module Resolvers
module BoardIssueFilterable
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
override :set_filter_values
def set_filter_values(filters)
epic_id = filters.delete(:epic_id)
epic_wildcard_id = filters.delete(:epic_wildcard_id)
if epic_id && epic_wildcard_id
raise ::Gitlab::Graphql::Errors::ArgumentError, 'Incompatible arguments: epicId, epicWildcardId.'
end
if epic_id
filters[:epic_id] = ::GitlabSchema.parse_gid(epic_id, expected_type: ::Epic).model_id
elsif epic_wildcard_id
filters[:epic_id] = epic_wildcard_id
end
end
end
end
end
# frozen_string_literal: true
module EE
module Types
module Boards
module BoardIssueInputBaseType
extend ActiveSupport::Concern
prepended do
argument :epic_id, GraphQL::ID_TYPE,
required: false,
description: 'Filter by epic ID. Incompatible with epicWildcardId'
argument :weight, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by weight'
end
end
end
end
end
# frozen_string_literal: true
module EE
module Types
module Boards
module BoardIssueInputType
extend ActiveSupport::Concern
prepended do
# NONE/ANY epic filter can not be negated
argument :epic_wildcard_id, ::Types::Boards::EpicWildcardIdEnum,
required: false,
description: 'Filter by epic ID wildcard. Incompatible with epicId'
end
end
end
end
end
...@@ -3,9 +3,11 @@ ...@@ -3,9 +3,11 @@
module Resolvers module Resolvers
module BoardGroupings module BoardGroupings
class EpicsResolver < BaseResolver class EpicsResolver < BaseResolver
include ::BoardIssueFilterable
alias_method :board, :synchronized_object alias_method :board, :synchronized_object
argument :issue_filters, Types::BoardEpicIssueInputType, argument :issue_filters, Types::Boards::BoardIssueInputType,
required: false, required: false,
description: 'Filters applied when selecting issues on the board' description: 'Filters applied when selecting issues on the board'
...@@ -16,16 +18,15 @@ module Resolvers ...@@ -16,16 +18,15 @@ module Resolvers
return Epic.none unless group.present? return Epic.none unless group.present?
return unless ::Feature.enabled?(:boards_with_swimlanes, group) return unless ::Feature.enabled?(:boards_with_swimlanes, group)
Epic.for_ids(board_epic_ids(args[:issue_filters].to_h)) Epic.for_ids(board_epic_ids(args[:issue_filters]))
end end
private private
def board_epic_ids(issue_params) def board_epic_ids(issue_params)
params = issue_params.to_h.merge(all_lists: true, board_id: board.id) params = issue_filters(issue_params).merge(all_lists: true, board_id: board.id)
params[:not] = params[:not].to_h if params[:not].present?
list_service = Boards::Issues::ListService.new( list_service = ::Boards::Issues::ListService.new(
board.resource_parent, board.resource_parent,
current_user, current_user,
params params
......
# frozen_string_literal: true
module Types
# rubocop: disable Graphql/AuthorizeTypes
class BoardEpicIssueInputBaseType < BaseInputObject
argument :label_name, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Filter by label name'
argument :milestone_title, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by milestone title'
argument :assignee_username, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Filter by assignee username'
argument :author_username, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by author username'
argument :release_tag, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by release tag'
argument :epic_id, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by epic ID'
argument :my_reaction_emoji, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by reaction emoji'
argument :weight, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by weight'
end
class NegatedBoardEpicIssueInputType < BoardEpicIssueInputBaseType
end
class BoardEpicIssueInputType < BoardEpicIssueInputBaseType
graphql_name 'BoardEpicIssueInput'
argument :not, Types::NegatedBoardEpicIssueInputType,
required: false,
description: 'List of negated params. Warning: this argument is experimental and a subject to change in future'
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query for issue title or description'
end
# rubocop: enable Graphql/AuthorizeTypes
end
# frozen_string_literal: true
module Types
module Boards
class EpicWildcardIdEnum < BaseEnum
graphql_name 'EpicWildcardId'
description 'Epic ID wildcard values'
value 'NONE', 'No epic is assigned'
value 'ANY', 'Any epic is assigned'
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::BoardListIssuesResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:board) { create(:board, project: project) }
let_it_be(:label) { create(:label, project: project) }
let_it_be(:list) { create(:list, board: board, label: label) }
let_it_be(:issue) { create(:issue, project: project, labels: [label]) }
let_it_be(:epic) { create(:epic, group: group) }
let_it_be(:epic_issue) { create(:epic_issue, epic: epic, issue: issue) }
describe '#resolve' do
before do
stub_licensed_features(epics: true)
group.add_developer(user)
end
it 'raises an exception if both epic_id and epic_wildcard_id are present' do
expect do
resolve_board_list_issues({ filters: { epic_id: epic.to_global_id, epic_wildcard_id: 'NONE' } })
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
it 'accepts epic global id' do
result = resolve_board_list_issues({ filters: { epic_id: epic.to_global_id } }).items
expect(result).to match_array([issue])
end
it 'accepts epic wildcard id' do
result = resolve_board_list_issues({ filters: { epic_wildcard_id: 'NONE' } }).items
expect(result).to match_array([])
end
end
def resolve_board_list_issues(args)
resolve(described_class, obj: list, args: args, ctx: { current_user: user })
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['BoardIssueInput'] do
it 'has specific fields' do
allowed_args = %w(epicId epicWildcardId weight)
expect(described_class.arguments.keys).to include(*allowed_args)
end
end
...@@ -94,6 +94,26 @@ RSpec.describe Resolvers::BoardGroupings::EpicsResolver do ...@@ -94,6 +94,26 @@ RSpec.describe Resolvers::BoardGroupings::EpicsResolver do
resolve_board_epics(group_board, { issue_filters: { label_name: 'foo', not: { label_name: %w(foo bar) } } }) resolve_board_epics(group_board, { issue_filters: { label_name: 'foo', not: { label_name: %w(foo bar) } } })
end end
it 'raises an exception if both epic_id and epic_wildcard_id are present' do
expect do
resolve_board_epics(group_board, { issue_filters: { epic_id: epic1.to_global_id, epic_wildcard_id: 'NONE' } })
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
it 'accepts epic global id' do
result = resolve_board_epics(
group_board, { issue_filters: { epic_id: epic1.to_global_id } })
expect(result).to match_array([epic1])
end
it 'accepts epic wildcard id' do
result = resolve_board_epics(
group_board, { issue_filters: { epic_wildcard_id: 'NONE' } })
expect(result).to match_array([])
end
end end
end end
......
...@@ -11,41 +11,59 @@ RSpec.describe Resolvers::BoardListIssuesResolver do ...@@ -11,41 +11,59 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
let_it_be(:group) { create(:group, :private) } let_it_be(:group) { create(:group, :private) }
shared_examples_for 'group and project board list issues resolver' do shared_examples_for 'group and project board list issues resolver' do
let!(:board) { create(:board, resource_parent: board_parent) }
before do before do
board_parent.add_developer(user) board_parent.add_developer(user)
end end
# auth is handled by the parent object # auth is handled by the parent object
context 'when authorized' do context 'when authorized' do
let!(:list) { create(:list, board: board, label: label) } let!(:issue1) { create(:issue, project: project, labels: [label], relative_position: 10) }
let!(:issue2) { create(:issue, project: project, labels: [label, label2], relative_position: 12) }
let!(:issue3) { create(:issue, project: project, labels: [label, label3], relative_position: 10) }
it 'returns the issues in the correct order' do it 'returns the issues in the correct order' do
issue1 = create(:issue, project: project, labels: [label], relative_position: 10)
issue2 = create(:issue, project: project, labels: [label], relative_position: 12)
issue3 = create(:issue, project: project, labels: [label], relative_position: 10)
# by relative_position and then ID # by relative_position and then ID
issues = resolve_board_list_issues.items issues = resolve_board_list_issues.items
expect(issues.map(&:id)).to eq [issue3.id, issue1.id, issue2.id] expect(issues.map(&:id)).to eq [issue3.id, issue1.id, issue2.id]
end end
it 'finds only issues matching filters' do
result = resolve_board_list_issues(args: { filters: { label_name: label.title, not: { label_name: label2.title } } }).items
expect(result).to match_array([issue1, issue3])
end
it 'finds only issues matching search param' do
result = resolve_board_list_issues(args: { filters: { search: issue1.title } }).items
expect(result).to match_array([issue1])
end
end end
end end
describe '#resolve' do describe '#resolve' do
context 'when project boards' do context 'when project boards' do
let_it_be(:label) { create(:label, project: user_project) }
let_it_be(:label2) { create(:label, project: user_project) }
let_it_be(:label3) { create(:label, project: user_project) }
let_it_be(:board) { create(:board, resource_parent: user_project) }
let_it_be(:list) { create(:list, board: board, label: label) }
let(:board_parent) { user_project } let(:board_parent) { user_project }
let!(:label) { create(:label, project: project, name: 'project label') }
let(:project) { user_project } let(:project) { user_project }
it_behaves_like 'group and project board list issues resolver' it_behaves_like 'group and project board list issues resolver'
end end
context 'when group boards' do context 'when group boards' do
let_it_be(:label) { create(:group_label, group: group) }
let_it_be(:label2) { create(:group_label, group: group) }
let_it_be(:label3) { create(:group_label, group: group) }
let_it_be(:board) { create(:board, resource_parent: group) }
let_it_be(:list) { create(:list, board: board, label: label) }
let(:board_parent) { group } let(:board_parent) { group }
let!(:label) { create(:group_label, group: group, name: 'group label') }
let!(:project) { create(:project, :private, group: group) } let!(:project) { create(:project, :private, group: group) }
it_behaves_like 'group and project board list issues resolver' it_behaves_like 'group and project board list issues resolver'
......
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GitlabSchema.types['BoardEpicIssueInput'] do RSpec.describe GitlabSchema.types['BoardIssueInput'] do
it { expect(described_class.graphql_name).to eq('BoardEpicIssueInput') } it { expect(described_class.graphql_name).to eq('BoardIssueInput') }
it 'exposes negated issue arguments' do it 'exposes negated issue arguments' do
allowed_args = %w(labelName milestoneTitle assigneeUsername authorUsername allowed_args = %w(labelName milestoneTitle assigneeUsername authorUsername
releaseTag epicId myReactionEmoji weight not search) releaseTag myReactionEmoji not search)
expect(described_class.arguments.keys).to match_array(allowed_args) expect(described_class.arguments.keys).to include(*allowed_args)
expect(described_class.arguments['not'].type).to eq(Types::NegatedBoardEpicIssueInputType) expect(described_class.arguments['not'].type).to eq(Types::Boards::NegatedBoardIssueInputType)
end end
end end
...@@ -30,7 +30,7 @@ RSpec.describe 'get board lists' do ...@@ -30,7 +30,7 @@ RSpec.describe 'get board lists' do
nodes { nodes {
lists { lists {
nodes { nodes {
issues { issues(filters: {labelName: "#{label2.title}"}) {
count count
nodes { nodes {
#{all_graphql_fields_for('issues'.classify)} #{all_graphql_fields_for('issues'.classify)}
...@@ -51,8 +51,8 @@ RSpec.describe 'get board lists' do ...@@ -51,8 +51,8 @@ RSpec.describe 'get board lists' do
shared_examples 'group and project board list issues query' do shared_examples 'group and project board list issues query' do
let!(:board) { create(:board, resource_parent: board_parent) } let!(:board) { create(:board, resource_parent: board_parent) }
let!(:label_list) { create(:list, board: board, label: label, position: 10) } let!(:label_list) { create(:list, board: board, label: label, position: 10) }
let!(:issue1) { create(:issue, project: issue_project, labels: [label], relative_position: 9) } let!(:issue1) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 9) }
let!(:issue2) { create(:issue, project: issue_project, labels: [label], relative_position: 2) } let!(:issue2) { create(:issue, project: issue_project, labels: [label, label2], relative_position: 2) }
let!(:issue3) { create(:issue, project: issue_project, labels: [label], relative_position: 9) } let!(:issue3) { create(:issue, project: issue_project, labels: [label], relative_position: 9) }
let!(:issue4) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) } let!(:issue4) { create(:issue, project: issue_project, labels: [label2], relative_position: 432) }
...@@ -72,7 +72,7 @@ RSpec.describe 'get board lists' do ...@@ -72,7 +72,7 @@ RSpec.describe 'get board lists' do
it 'can access the issues' do it 'can access the issues' do
post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user) post_graphql(query("id: \"#{global_id_of(label_list)}\""), current_user: user)
expect(issue_titles).to eq([issue2.title, issue3.title, issue1.title]) expect(issue_titles).to eq([issue2.title, issue1.title])
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