Commit b429aae0 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '327341-filter-issues-by-release-gql' into 'master'

Filter issues by releaseTag in GraphQL

See merge request gitlab-org/gitlab!71012
parents 137f9b0e df4fad34
# frozen_string_literal: true
module Resolvers
class BaseIssuesResolver < BaseResolver
prepend IssueResolverArguments
argument :state, Types::IssuableStateEnum,
required: false,
description: 'Current state of this issue.'
argument :sort, Types::IssueSortEnum,
description: 'Sort issues by this criteria.',
required: false,
default_value: :created_desc
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
def continue_issue_resolve(parent, finder, **args)
issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all { |q| apply_lookahead(q) }
if non_stable_cursor_sort?(args[:sort])
# Certain complex sorts are not supported by the stable cursor pagination yet.
# In these cases, we use offset pagination, so we return the correct connection.
offset_pagination(issues)
else
issues
end
end
private
def unconditional_includes
[
{
project: [:project_feature]
},
:author
]
end
def preloads
{
alert_management_alert: [:alert_management_alert],
labels: [:labels],
assignees: [:assignees],
timelogs: [:timelogs],
customer_relations_contacts: { customer_relations_contacts: [:group] }
}
end
def non_stable_cursor_sort?(sort)
NON_STABLE_CURSOR_SORTS.include?(sort)
end
end
end
Resolvers::BaseIssuesResolver.prepend_mod_with('Resolvers::BaseIssuesResolver')
...@@ -55,8 +55,8 @@ module IssueResolverArguments ...@@ -55,8 +55,8 @@ module IssueResolverArguments
description: 'Filter issues by the given issue types.', description: 'Filter issues by the given issue types.',
required: false required: false
argument :milestone_wildcard_id, ::Types::MilestoneWildcardIdEnum, argument :milestone_wildcard_id, ::Types::MilestoneWildcardIdEnum,
required: false, required: false,
description: 'Filter issues by milestone ID wildcard.' description: 'Filter issues by milestone ID wildcard.'
argument :my_reaction_emoji, GraphQL::Types::String, argument :my_reaction_emoji, GraphQL::Types::String,
required: false, required: false,
description: 'Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported.' description: 'Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported.'
...@@ -83,6 +83,7 @@ module IssueResolverArguments ...@@ -83,6 +83,7 @@ module IssueResolverArguments
args[:attempt_project_search_optimizations] = true if args[:search].present? args[:attempt_project_search_optimizations] = true if args[:search].present?
prepare_assignee_username_params(args) prepare_assignee_username_params(args)
prepare_release_tag_params(args)
finder = IssuesFinder.new(current_user, args) finder = IssuesFinder.new(current_user, args)
...@@ -93,6 +94,7 @@ module IssueResolverArguments ...@@ -93,6 +94,7 @@ module IssueResolverArguments
params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args) params_not_mutually_exclusive(args, mutually_exclusive_assignee_username_args)
params_not_mutually_exclusive(args, mutually_exclusive_milestone_args) params_not_mutually_exclusive(args, mutually_exclusive_milestone_args)
params_not_mutually_exclusive(args.fetch(:not, {}), mutually_exclusive_milestone_args) params_not_mutually_exclusive(args.fetch(:not, {}), mutually_exclusive_milestone_args)
params_not_mutually_exclusive(args, mutually_exclusive_release_tag_args)
validate_anonymous_search_access! if args[:search].present? validate_anonymous_search_access! if args[:search].present?
super super
...@@ -105,10 +107,30 @@ module IssueResolverArguments ...@@ -105,10 +107,30 @@ module IssueResolverArguments
complexity complexity
end end
def accept_release_tag
argument :release_tag, [GraphQL::Types::String],
required: false,
description: "Release tag associated with the issue's milestone."
argument :release_tag_wildcard_id, Types::ReleaseTagWildcardIdEnum,
required: false,
description: 'Filter issues by release tag ID wildcard.'
end
end end
private private
def prepare_release_tag_params(args)
release_tag_wildcard = args.delete(:release_tag_wildcard_id)
return if release_tag_wildcard.blank?
args[:release_tag] ||= release_tag_wildcard
end
def mutually_exclusive_release_tag_args
[:release_tag, :release_tag_wildcard_id]
end
def prepare_assignee_username_params(args) def prepare_assignee_username_params(args)
args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present? args[:assignee_username] = args.delete(:assignee_usernames) if args[:assignee_usernames].present?
args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) if args.dig(:not, :assignee_usernames).present? args[:not][:assignee_username] = args[:not].delete(:assignee_usernames) if args.dig(:not, :assignee_usernames).present?
......
# frozen_string_literal: true # frozen_string_literal: true
# rubocop:disable Graphql/ResolverType (inherited from IssuesResolver) # rubocop:disable Graphql/ResolverType (inherited from BaseIssuesResolver)
module Resolvers module Resolvers
class GroupIssuesResolver < IssuesResolver class GroupIssuesResolver < BaseIssuesResolver
include GroupIssuableResolver include GroupIssuableResolver
include_subgroups 'issues' include_subgroups 'issues'
def ready?(**args)
if args.dig(:not, :release_tag).present?
raise ::Gitlab::Graphql::Errors::ArgumentError, 'releaseTag filter is not allowed when parent is a group.'
end
super
end
end end
end end
...@@ -5,6 +5,7 @@ module Resolvers ...@@ -5,6 +5,7 @@ module Resolvers
prepend IssueResolverArguments prepend IssueResolverArguments
type Types::IssueStatusCountsType, null: true type Types::IssueStatusCountsType, null: true
accept_release_tag
extras [:lookahead] extras [:lookahead]
......
# frozen_string_literal: true # frozen_string_literal: true
# rubocop:disable Graphql/ResolverType (inherited from BaseIssuesResolver)
module Resolvers module Resolvers
class IssuesResolver < BaseResolver class IssuesResolver < BaseIssuesResolver
prepend IssueResolverArguments accept_release_tag
argument :state, Types::IssuableStateEnum,
required: false,
description: 'Current state of this issue.'
argument :sort, Types::IssueSortEnum,
description: 'Sort issues by this criteria.',
required: false,
default_value: :created_desc
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
def continue_issue_resolve(parent, finder, **args)
issues = Gitlab::Graphql::Loaders::IssuableLoader.new(parent, finder).batching_find_all { |q| apply_lookahead(q) }
if non_stable_cursor_sort?(args[:sort])
# Certain complex sorts are not supported by the stable cursor pagination yet.
# In these cases, we use offset pagination, so we return the correct connection.
offset_pagination(issues)
else
issues
end
end
private
def unconditional_includes
[
{
project: [:project_feature]
},
:author
]
end
def preloads
{
alert_management_alert: [:alert_management_alert],
labels: [:labels],
assignees: [:assignees],
timelogs: [:timelogs],
customer_relations_contacts: { customer_relations_contacts: [:group] }
}
end
def non_stable_cursor_sort?(sort)
NON_STABLE_CURSOR_SORTS.include?(sort)
end
end end
end end
Resolvers::IssuesResolver.prepend_mod_with('Resolvers::IssuesResolver')
...@@ -14,6 +14,9 @@ module Types ...@@ -14,6 +14,9 @@ module Types
argument :milestone_title, [GraphQL::Types::String], argument :milestone_title, [GraphQL::Types::String],
required: false, required: false,
description: 'Milestone not applied to this issue.' description: 'Milestone not applied to this issue.'
argument :release_tag, [GraphQL::Types::String],
required: false,
description: "Release tag not associated with the issue's milestone. Ignored when parent is a group."
argument :author_username, GraphQL::Types::String, argument :author_username, GraphQL::Types::String,
required: false, required: false,
description: "Username of a user who didn't author the issue." description: "Username of a user who didn't author the issue."
......
# frozen_string_literal: true
module Types
class ReleaseTagWildcardIdEnum < BaseEnum
graphql_name 'ReleaseTagWildcardId'
description 'Release tag ID wildcard values'
value 'NONE', 'No release tag is assigned.'
value 'ANY', 'Release tag is assigned.'
end
end
...@@ -20,7 +20,7 @@ module Milestoneable ...@@ -20,7 +20,7 @@ module Milestoneable
scope :without_particular_milestone, ->(title) { left_outer_joins(:milestone).where("milestones.title != ? OR milestone_id IS NULL", title) } scope :without_particular_milestone, ->(title) { left_outer_joins(:milestone).where("milestones.title != ? OR milestone_id IS NULL", title) }
scope :any_release, -> { joins_milestone_releases } scope :any_release, -> { joins_milestone_releases }
scope :with_release, -> (tag, project_id) { joins_milestone_releases.where( milestones: { releases: { tag: tag, project_id: project_id } } ) } scope :with_release, -> (tag, project_id) { joins_milestone_releases.where( milestones: { releases: { tag: tag, project_id: project_id } } ) }
scope :without_particular_release, -> (tag, project_id) { joins_milestone_releases.where.not( milestones: { releases: { tag: tag, project_id: project_id } } ) } scope :without_particular_release, -> (tag, project_id) { joins_milestone_releases.where.not(milestones: { releases: { tag: tag, project_id: project_id } }) }
scope :left_joins_milestones, -> { joins("LEFT OUTER JOIN milestones ON #{table_name}.milestone_id = milestones.id") } scope :left_joins_milestones, -> { joins("LEFT OUTER JOIN milestones ON #{table_name}.milestone_id = milestones.id") }
scope :order_milestone_due_desc, -> { left_joins_milestones.reorder(Arel.sql('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date DESC')) } scope :order_milestone_due_desc, -> { left_joins_milestones.reorder(Arel.sql('milestones.due_date IS NULL, milestones.id IS NULL, milestones.due_date DESC')) }
......
...@@ -12934,6 +12934,8 @@ Returns [`Issue`](#issue). ...@@ -12934,6 +12934,8 @@ Returns [`Issue`](#issue).
| <a id="projectissuemilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="projectissuemilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="projectissuemyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="projectissuemyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="projectissuenot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="projectissuenot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="projectissuereleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag associated with the issue's milestone. |
| <a id="projectissuereleasetagwildcardid"></a>`releaseTagWildcardId` | [`ReleaseTagWildcardId`](#releasetagwildcardid) | Filter issues by release tag ID wildcard. |
| <a id="projectissuesearch"></a>`search` | [`String`](#string) | Search query for title or description. | | <a id="projectissuesearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="projectissuesort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. | | <a id="projectissuesort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. |
| <a id="projectissuestate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. | | <a id="projectissuestate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. |
...@@ -12968,6 +12970,8 @@ Returns [`IssueStatusCountsType`](#issuestatuscountstype). ...@@ -12968,6 +12970,8 @@ Returns [`IssueStatusCountsType`](#issuestatuscountstype).
| <a id="projectissuestatuscountsmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="projectissuestatuscountsmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="projectissuestatuscountsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="projectissuestatuscountsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="projectissuestatuscountsnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="projectissuestatuscountsnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="projectissuestatuscountsreleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag associated with the issue's milestone. |
| <a id="projectissuestatuscountsreleasetagwildcardid"></a>`releaseTagWildcardId` | [`ReleaseTagWildcardId`](#releasetagwildcardid) | Filter issues by release tag ID wildcard. |
| <a id="projectissuestatuscountssearch"></a>`search` | [`String`](#string) | Search query for title or description. | | <a id="projectissuestatuscountssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="projectissuestatuscountstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. | | <a id="projectissuestatuscountstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter issues by the given issue types. |
| <a id="projectissuestatuscountsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Issues updated after this date. | | <a id="projectissuestatuscountsupdatedafter"></a>`updatedAfter` | [`Time`](#time) | Issues updated after this date. |
...@@ -13007,6 +13011,8 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -13007,6 +13011,8 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="projectissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. | | <a id="projectissuesmilestonewildcardid"></a>`milestoneWildcardId` | [`MilestoneWildcardId`](#milestonewildcardid) | Filter issues by milestone ID wildcard. |
| <a id="projectissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. | | <a id="projectissuesmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. Wildcard values "NONE" and "ANY" are supported. |
| <a id="projectissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. | | <a id="projectissuesnot"></a>`not` | [`NegatedIssueFilterInput`](#negatedissuefilterinput) | Negated arguments. |
| <a id="projectissuesreleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag associated with the issue's milestone. |
| <a id="projectissuesreleasetagwildcardid"></a>`releaseTagWildcardId` | [`ReleaseTagWildcardId`](#releasetagwildcardid) | Filter issues by release tag ID wildcard. |
| <a id="projectissuessearch"></a>`search` | [`String`](#string) | Search query for title or description. | | <a id="projectissuessearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="projectissuessort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. | | <a id="projectissuessort"></a>`sort` | [`IssueSort`](#issuesort) | Sort issues by this criteria. |
| <a id="projectissuesstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. | | <a id="projectissuesstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this issue. |
...@@ -16485,6 +16491,15 @@ Values for sorting releases. ...@@ -16485,6 +16491,15 @@ Values for sorting releases.
| <a id="releasesortreleased_at_asc"></a>`RELEASED_AT_ASC` | Released at by ascending order. | | <a id="releasesortreleased_at_asc"></a>`RELEASED_AT_ASC` | Released at by ascending order. |
| <a id="releasesortreleased_at_desc"></a>`RELEASED_AT_DESC` | Released at by descending order. | | <a id="releasesortreleased_at_desc"></a>`RELEASED_AT_DESC` | Released at by descending order. |
### `ReleaseTagWildcardId`
Release tag ID wildcard values.
| Value | Description |
| ----- | ----------- |
| <a id="releasetagwildcardidany"></a>`ANY` | Release tag is assigned. |
| <a id="releasetagwildcardidnone"></a>`NONE` | No release tag is assigned. |
### `RequirementState` ### `RequirementState`
State of a requirement. State of a requirement.
...@@ -18191,6 +18206,7 @@ Represents an escalation rule. ...@@ -18191,6 +18206,7 @@ Represents an escalation rule.
| <a id="negatedissuefilterinputmilestonetitle"></a>`milestoneTitle` | [`[String!]`](#string) | Milestone not applied to this issue. | | <a id="negatedissuefilterinputmilestonetitle"></a>`milestoneTitle` | [`[String!]`](#string) | Milestone not applied to this issue. |
| <a id="negatedissuefilterinputmilestonewildcardid"></a>`milestoneWildcardId` | [`NegatedMilestoneWildcardId`](#negatedmilestonewildcardid) | Filter by negated milestone wildcard values. | | <a id="negatedissuefilterinputmilestonewildcardid"></a>`milestoneWildcardId` | [`NegatedMilestoneWildcardId`](#negatedmilestonewildcardid) | Filter by negated milestone wildcard values. |
| <a id="negatedissuefilterinputmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="negatedissuefilterinputmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="negatedissuefilterinputreleasetag"></a>`releaseTag` | [`[String!]`](#string) | Release tag not associated with the issue's milestone. Ignored when parent is a group. |
| <a id="negatedissuefilterinputtypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filters out issues by the given issue types. | | <a id="negatedissuefilterinputtypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filters out issues by the given issue types. |
| <a id="negatedissuefilterinputweight"></a>`weight` | [`String`](#string) | Weight not applied to the issue. | | <a id="negatedissuefilterinputweight"></a>`weight` | [`String`](#string) | Weight not applied to the issue. |
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module EE module EE
module Resolvers module Resolvers
module IssuesResolver module BaseIssuesResolver
extend ActiveSupport::Concern extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
......
...@@ -29,15 +29,72 @@ RSpec.describe Resolvers::GroupIssuesResolver do ...@@ -29,15 +29,72 @@ RSpec.describe Resolvers::GroupIssuesResolver do
describe '#resolve' do describe '#resolve' do
it 'finds all group issues' do it 'finds all group issues' do
result = resolve(described_class, obj: group, ctx: { current_user: current_user }) expect(resolve_issues).to contain_exactly(issue1, issue2, issue3)
expect(result).to contain_exactly(issue1, issue2, issue3)
end end
it 'finds all group and subgroup issues' do it 'finds all group and subgroup issues' do
result = resolve(described_class, obj: group, args: { include_subgroups: true }, ctx: { current_user: current_user }) result = resolve_issues(include_subgroups: true)
expect(result).to contain_exactly(issue1, issue2, issue3, subissue1, subissue2, subissue3) expect(result).to contain_exactly(issue1, issue2, issue3, subissue1, subissue2, subissue3)
end end
it 'returns issues without the specified issue_type' do
result = resolve_issues(not: { types: ['issue'] })
expect(result).to contain_exactly(issue1)
end
context 'confidential issues' do
let_it_be(:confidential_issue1) { create(:issue, project: project, confidential: true) }
let_it_be(:confidential_issue2) { create(:issue, project: other_project, confidential: true) }
context "when user is allowed to view confidential issues" do
it 'returns all viewable issues by default' do
expect(resolve_issues).to contain_exactly(issue1, issue2, issue3, confidential_issue1, confidential_issue2)
end
context 'filtering for confidential issues' do
it 'returns only the non-confidential issues for the group when filter is set to false' do
expect(resolve_issues({ confidential: false })).to contain_exactly(issue1, issue2, issue3)
end
it "returns only the confidential issues for the group when filter is set to true" do
expect(resolve_issues({ confidential: true })).to contain_exactly(confidential_issue1, confidential_issue2)
end
end
end
context "when user is not allowed to see confidential issues" do
before do
group.add_guest(current_user)
end
it 'returns all viewable issues by default' do
expect(resolve_issues).to contain_exactly(issue1, issue2, issue3)
end
context 'filtering for confidential issues' do
it 'does not return the confidential issues when filter is set to false' do
expect(resolve_issues({ confidential: false })).to contain_exactly(issue1, issue2, issue3)
end
it 'does not return the confidential issues when filter is set to true' do
expect(resolve_issues({ confidential: true })).to be_empty
end
end
end
end
context 'release_tag filter' do
it 'returns an error when trying to filter by negated release_tag' do
expect do
resolve_issues(not: { release_tag: ['v1.0'] })
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'releaseTag filter is not allowed when parent is a group.')
end
end
end
def resolve_issues(args = {}, context = { current_user: current_user })
resolve(described_class, obj: group, args: args, ctx: context)
end end
end end
...@@ -26,14 +26,7 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -26,14 +26,7 @@ RSpec.describe Resolvers::IssuesResolver do
expect(described_class).to have_nullable_graphql_type(Types::IssueType.connection_type) expect(described_class).to have_nullable_graphql_type(Types::IssueType.connection_type)
end end
shared_context 'filtering for confidential issues' do
let_it_be(:confidential_issue1) { create(:issue, project: project, confidential: true) }
let_it_be(:confidential_issue2) { create(:issue, project: other_project, confidential: true) }
end
context "with a project" do context "with a project" do
let(:obj) { project }
before_all do before_all do
project.add_developer(current_user) project.add_developer(current_user)
project.add_reporter(reporter) project.add_reporter(reporter)
...@@ -112,6 +105,54 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -112,6 +105,54 @@ RSpec.describe Resolvers::IssuesResolver do
end end
end end
describe 'filter by release' do
let_it_be(:milestone1) { create(:milestone, project: project, start_date: 1.day.from_now, title: 'Version 1') }
let_it_be(:milestone2) { create(:milestone, project: project, start_date: 1.day.from_now, title: 'Version 2') }
let_it_be(:milestone3) { create(:milestone, project: project, start_date: 1.day.from_now, title: 'Version 3') }
let_it_be(:release1) { create(:release, tag: 'v1.0', milestones: [milestone1], project: project) }
let_it_be(:release2) { create(:release, tag: 'v2.0', milestones: [milestone2], project: project) }
let_it_be(:release3) { create(:release, tag: 'v3.0', milestones: [milestone3], project: project) }
let_it_be(:release_issue1) { create(:issue, project: project, milestone: milestone1) }
let_it_be(:release_issue2) { create(:issue, project: project, milestone: milestone2) }
let_it_be(:release_issue3) { create(:issue, project: project, milestone: milestone3) }
describe 'filter by release_tag' do
it 'returns all issues associated with the specified tags' do
expect(resolve_issues(release_tag: [release1.tag, release3.tag])).to contain_exactly(release_issue1, release_issue3)
end
context 'when release_tag_wildcard_id is also provided' do
it 'raises a mutually eclusive argument error' do
expect do
resolve_issues(release_tag: [release1.tag], release_tag_wildcard_id: 'ANY')
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError, 'only one of [releaseTag, releaseTagWildcardId] arguments is allowed at the same time.')
end
end
end
describe 'filter by negated release_tag' do
it 'returns all issues not associated with the specified tags' do
expect(resolve_issues(not: { release_tag: [release1.tag, release3.tag] })).to contain_exactly(release_issue2)
end
end
describe 'filter by release_tag_wildcard_id' do
subject { resolve_issues(release_tag_wildcard_id: wildcard_id) }
context 'when filtering by ANY' do
let(:wildcard_id) { 'ANY' }
it { is_expected.to contain_exactly(release_issue1, release_issue2, release_issue3) }
end
context 'when filtering by NONE' do
let(:wildcard_id) { 'NONE' }
it { is_expected.to contain_exactly(issue1, issue2) }
end
end
end
it 'filters by two assignees' do it 'filters by two assignees' do
assignee2 = create(:user) assignee2 = create(:user)
issue2.update!(assignees: [assignee, assignee2]) issue2.update!(assignees: [assignee, assignee2])
...@@ -230,7 +271,8 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -230,7 +271,8 @@ RSpec.describe Resolvers::IssuesResolver do
end end
context 'confidential issues' do context 'confidential issues' do
include_context 'filtering for confidential issues' let_it_be(:confidential_issue1) { create(:issue, project: project, confidential: true) }
let_it_be(:confidential_issue2) { create(:issue, project: other_project, confidential: true) }
context "when user is allowed to view confidential issues" do context "when user is allowed to view confidential issues" do
it 'returns all viewable issues by default' do it 'returns all viewable issues by default' do
...@@ -561,64 +603,6 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -561,64 +603,6 @@ RSpec.describe Resolvers::IssuesResolver do
end end
end end
context "with a group" do
let(:obj) { group }
before do
group.add_developer(current_user)
end
describe '#resolve' do
it 'finds all group issues' do
expect(resolve_issues).to contain_exactly(issue1, issue2, issue3)
end
it 'returns issues without the specified issue_type' do
expect(resolve_issues({ not: { types: ['issue'] } })).to contain_exactly(issue1)
end
context "confidential issues" do
include_context 'filtering for confidential issues'
context "when user is allowed to view confidential issues" do
it 'returns all viewable issues by default' do
expect(resolve_issues).to contain_exactly(issue1, issue2, issue3, confidential_issue1, confidential_issue2)
end
context 'filtering for confidential issues' do
it 'returns only the non-confidential issues for the group when filter is set to false' do
expect(resolve_issues({ confidential: false })).to contain_exactly(issue1, issue2, issue3)
end
it "returns only the confidential issues for the group when filter is set to true" do
expect(resolve_issues({ confidential: true })).to contain_exactly(confidential_issue1, confidential_issue2)
end
end
end
context "when user is not allowed to see confidential issues" do
before do
group.add_guest(current_user)
end
it 'returns all viewable issues by default' do
expect(resolve_issues).to contain_exactly(issue1, issue2, issue3)
end
context 'filtering for confidential issues' do
it 'does not return the confidential issues when filter is set to false' do
expect(resolve_issues({ confidential: false })).to contain_exactly(issue1, issue2, issue3)
end
it 'does not return the confidential issues when filter is set to true' do
expect(resolve_issues({ confidential: true })).to be_empty
end
end
end
end
end
end
context "when passing a non existent, batch loaded project" do context "when passing a non existent, batch loaded project" do
let!(:project) do let!(:project) do
BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _| BatchLoader::GraphQL.for("non-existent-path").batch do |_fake_paths, loader, _|
...@@ -626,8 +610,6 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -626,8 +610,6 @@ RSpec.describe Resolvers::IssuesResolver do
end end
end end
let(:obj) { project }
it "returns nil without breaking" do it "returns nil without breaking" do
expect(resolve_issues(iids: ["don't", "break"])).to be_empty expect(resolve_issues(iids: ["don't", "break"])).to be_empty
end end
...@@ -648,6 +630,6 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -648,6 +630,6 @@ RSpec.describe Resolvers::IssuesResolver do
end end
def resolve_issues(args = {}, context = { current_user: current_user }) def resolve_issues(args = {}, context = { current_user: current_user })
resolve(described_class, obj: obj, args: args, ctx: context) resolve(described_class, obj: project, args: args, ctx: context)
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