Commit e5845760 authored by Mario Celi's avatar Mario Celi

Support negated filters on IssueResolver

Support assignee_usernames in IssuableFinder

Add CE negated params to issue resolver

- Add all negated arguments supported by IssueFinder
- Add resolver specs

Add EE IssueResolver GQL filter arguments

- Adds missing filter EE arguments to resolver
- Adds negated EE filter arguments supported by issue finder

Add EE changelog entry

Refactor EE issue_resolver_spec. Create less DB records

Add FOSS changelog entry
parent 9c00e4f2
......@@ -163,6 +163,8 @@ class IssuableFinder
User.where(id: params[:assignee_id])
elsif assignee_username?
User.where(username: params[:assignee_username])
elsif assignee_usernames?
User.where(username: params[:assignee_usernames])
else
User.none
end
......
......@@ -55,6 +55,10 @@ module IssueResolverArguments
as: :issue_types,
description: 'Filter issues by the given issue types.',
required: false
argument :not, Types::Issues::NegatedIssueFilterInputType,
description: 'List of negated params.',
prepare: ->(negated_args, ctx) { negated_args.to_h },
required: false
end
def resolve_with_lookahead(**args)
......
# frozen_string_literal: true
module Types
module Issues
class NegatedIssueFilterInputType < BaseInputObject
graphql_name 'NegatedIssueFilterInput'
argument :iids, [GraphQL::STRING_TYPE],
required: false,
description: 'List of IIDs of issues to exclude. For example, [1, 2].'
argument :label_name, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Labels not applied to this issue.'
argument :milestone_title, GraphQL::STRING_TYPE.to_list_type,
required: false,
description: 'Milestone not applied to this issue.'
argument :assignee_username, GraphQL::STRING_TYPE,
required: false,
description: 'Username of a user not assigned to the issue.'
argument :assignee_usernames, [GraphQL::STRING_TYPE],
required: false,
description: 'Usernames of users not assigned to the issue.'
argument :assignee_id, GraphQL::STRING_TYPE,
required: false,
description: 'ID of a user not assigned to the issues.'
end
end
end
Types::Issues::NegatedIssueFilterInputType.prepend_if_ee('::EE::Types::Issues::NegatedIssueFilterInputType')
---
title: Support negated filtering by iids, label_name, milestone_title, assignee_username, assignee_usernames and assignee_id in IssueResolvers, GQL API
merge_request: 58154
author:
type: added
......@@ -19,7 +19,7 @@ module EE
end
def weights?
params[:weight].present? && params[:weight] != ::Issue::WEIGHT_ALL
params[:weight].present? && params[:weight].to_s.casecmp(::Issue::WEIGHT_ALL) != 0
end
def filter_by_no_weight?
......
......@@ -10,10 +10,12 @@ module EE
argument :iteration_id, ::GraphQL::ID_TYPE.to_list_type,
required: false,
description: 'Iterations applied to the issue.'
argument :epic_id, GraphQL::STRING_TYPE,
required: false,
description: 'ID of an epic associated with the issues, "none" and "any" values are supported.'
argument :weight, GraphQL::STRING_TYPE,
required: false,
description: 'Weight applied to the issue, "none" and "any" values are supported.'
end
private
......
# frozen_string_literal: true
module EE
module Types
module Issues
module NegatedIssueFilterInputType
extend ActiveSupport::Concern
prepended do
argument :epic_id, GraphQL::STRING_TYPE,
required: false,
description: 'ID of an epic not associated with the issues.'
argument :weight, GraphQL::STRING_TYPE,
required: false,
description: 'Weight not applied to the issue.'
end
end
end
end
end
---
title: Support negated filtering by epic_id and weight in IssueResolvers, GQL API
merge_request: 58154
author:
type: added
......@@ -11,36 +11,39 @@ RSpec.describe Resolvers::IssuesResolver do
context "with a project" do
describe '#resolve' do
let_it_be(:epic1) { create :epic, group: group }
let_it_be(:epic2) { create :epic, group: group }
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, :started, group: group, start_date: Date.today, due_date: 1.day.from_now) }
let_it_be(:issue1) { create :issue, project: project, epic: epic1, iteration: iteration1 }
let_it_be(:issue2) { create :issue, project: project, epic: epic2, weight: 1 }
let_it_be(:issue3) { create :issue, project: project, weight: 3, iteration: current_iteration }
let_it_be(:issue4) { create :issue, :published, project: project }
before do
project.add_developer(current_user)
end
describe 'sorting' do
context 'when sorting by weight' do
let_it_be(:weight_issue1) { create(:issue, project: project, weight: 5) }
let_it_be(:weight_issue2) { create(:issue, project: project, weight: nil) }
let_it_be(:weight_issue3) { create(:issue, project: project, weight: 1) }
let_it_be(:weight_issue4) { create(:issue, project: project, weight: nil) }
it 'sorts issues ascending' do
expect(resolve_issues(sort: :weight_asc).to_a).to eq [weight_issue3, weight_issue1, weight_issue4, weight_issue2]
expect(resolve_issues(sort: :weight_asc).to_a).to eq [issue2, issue3, issue4, issue1]
end
it 'sorts issues descending' do
expect(resolve_issues(sort: :weight_desc).to_a).to eq [weight_issue1, weight_issue3, weight_issue4, weight_issue2]
expect(resolve_issues(sort: :weight_desc).to_a).to eq [issue3, issue2, issue4, issue1]
end
end
context 'when sorting by published' do
let_it_be(:not_published) { create(:issue, project: project) }
let_it_be(:published) { create(:issue, :published, project: project) }
it 'sorts issues ascending' do
expect(resolve_issues(sort: :published_asc).to_a).to eq [not_published, published]
expect(resolve_issues(sort: :published_asc).to_a).to eq [issue3, issue2, issue1, issue4]
end
it 'sorts issues descending' do
expect(resolve_issues(sort: :published_desc).to_a).to eq [published, not_published]
expect(resolve_issues(sort: :published_desc).to_a).to eq [issue4, issue3, issue2, issue1]
end
end
......@@ -64,32 +67,56 @@ RSpec.describe Resolvers::IssuesResolver do
end
describe 'filtering by iteration' do
let_it_be(:iteration1) { create(:iteration, group: group) }
let_it_be(:issue_with_iteration) { create(:issue, project: project, iteration: iteration1) }
let_it_be(:issue_without_iteration) { create(:issue, project: project) }
it 'returns issues with iteration' do
expect(resolve_issues(iteration_id: [iteration1.id])).to contain_exactly(issue_with_iteration)
expect(resolve_issues(iteration_id: [iteration1.id.to_s])).to contain_exactly(issue1)
end
end
describe 'filter by epic' do
let_it_be(:epic) { create :epic, group: group }
let_it_be(:epic2) { create :epic, group: group }
let_it_be(:issue1) { create :issue, project: project, epic: epic }
let_it_be(:issue2) { create :issue, project: project, epic: epic2 }
let_it_be(:issue3) { create :issue, project: project }
it 'returns issues without epic when epic_id is "none"' do
expect(resolve_issues(epic_id: 'none')).to match_array([issue3])
expect(resolve_issues(epic_id: 'none')).to contain_exactly(issue4, issue3)
end
it 'returns issues with any epic when epic_id is "any"' do
expect(resolve_issues(epic_id: 'any')).to match_array([issue1, issue2])
expect(resolve_issues(epic_id: 'any')).to contain_exactly(issue1, issue2)
end
it 'returns issues with any epic when epic_id is specific' do
expect(resolve_issues(epic_id: epic.id)).to match_array([issue1])
expect(resolve_issues(epic_id: epic1.id.to_s)).to contain_exactly(issue1)
end
end
describe 'filter by weight' do
context 'when filtering by any weight' do
it 'only returns issues that have a weight assigned' do
expect(resolve_issues(weight: 'any')).to contain_exactly(issue2, issue3)
end
end
context 'when filtering by no weight' do
it 'only returns issues that have no weight assigned' do
expect(resolve_issues(weight: 'none')).to contain_exactly(issue1, issue4)
end
end
context 'when filtering by specific weight' do
it 'only returns issues that have the specified weight assigned' do
expect(resolve_issues(weight: '3')).to contain_exactly(issue3)
end
end
end
describe 'filtering by negated params' do
describe 'filter by negated epic' do
it 'returns issues without the specified epic_id' do
expect(resolve_issues(not: { epic_id: epic2.id.to_s })).to contain_exactly(issue1, issue3, issue4)
end
end
describe 'filtering by negated weight' do
it 'only returns issues that do not have the specified weight assigned' do
expect(resolve_issues(not: { weight: '3' })).to contain_exactly(issue1, issue2, issue4)
end
end
end
end
......
......@@ -63,6 +63,11 @@ RSpec.describe IssuesFinder do
let(:expected_issuables) { [issue2] }
end
it_behaves_like 'assignee username filter' do
let(:params) { { assignee_usernames: [user2.username] } }
let(:expected_issuables) { [issue2] }
end
it_behaves_like 'assignee NOT username filter' do
before do
issue2.assignees = [user2]
......
......@@ -50,6 +50,10 @@ RSpec.describe Resolvers::IssuesResolver do
expect(resolve_issues(assignee_username: [assignee.username])).to contain_exactly(issue2)
end
it 'filters by assignee_usernames' do
expect(resolve_issues(assignee_usernames: [assignee.username])).to contain_exactly(issue2)
end
it 'filters by two assignees' do
assignee2 = create(:user)
issue2.update!(assignees: [assignee, assignee2])
......@@ -144,6 +148,33 @@ RSpec.describe Resolvers::IssuesResolver do
end
end
describe 'filters by negated params' do
it 'returns issues without the specified iids' do
expect(resolve_issues(not: { iids: [issue1.iid] })).to contain_exactly(issue2)
end
it 'returns issues without the specified label names' do
expect(resolve_issues(not: { label_name: [label1.title] })).to be_empty
expect(resolve_issues(not: { label_name: [label2.title] })).to contain_exactly(issue1)
end
it 'returns issues without the specified milestone' do
expect(resolve_issues(not: { milestone_title: [milestone.title] })).to contain_exactly(issue2)
end
it 'returns issues without the specified assignee_username' do
expect(resolve_issues(not: { assignee_username: assignee.username })).to contain_exactly(issue1)
end
it 'returns issues without the specified assignee_usernames' do
expect(resolve_issues(not: { assignee_usernames: [assignee.username] })).to contain_exactly(issue1)
end
it 'returns issues without the specified assignee_id' do
expect(resolve_issues(not: { assignee_id: [assignee.id] })).to contain_exactly(issue1)
end
end
describe 'sorting' do
context 'when sorting by created' do
it 'sorts issues ascending' 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