Commit e4339f5b authored by Pavel Shutsin's avatar Pavel Shutsin

Add negatve filters for merge requests API

Introduces negative filtering options for
GraphQL API. Can be used in any GraphQL query
parent ef7523d9
......@@ -244,7 +244,7 @@ class IssuableFinder
# These are "helper" params that modify the results, like :in and :search. They usually come in at the top-level
# params, but if they do come in inside the `:not` params, the inner ones should take precedence.
not_helpers = params.slice(*NEGATABLE_PARAMS_HELPER_KEYS).merge(params[:not].slice(*NEGATABLE_PARAMS_HELPER_KEYS))
not_helpers = params.slice(*NEGATABLE_PARAMS_HELPER_KEYS).merge(params[:not].to_h.slice(*NEGATABLE_PARAMS_HELPER_KEYS))
not_helpers.each do |key, value|
not_params[key] = value unless not_params[key].present?
end
......
......@@ -3,6 +3,7 @@
module Resolvers
class MergeRequestsResolver < BaseResolver
include ResolvesMergeRequests
extend ::Gitlab::Graphql::NegatableArguments
type ::Types::MergeRequestType.connection_type, null: true
......@@ -68,6 +69,16 @@ module Resolvers
required: false,
default_value: :created_desc
negated do
argument :labels, [GraphQL::STRING_TYPE],
required: false,
as: :label_name,
description: 'Array of label names. All resolved merge requests will not have these labels.'
argument :milestone_title, GraphQL::STRING_TYPE,
required: false,
description: 'Title of the milestone.'
end
def self.single
::Resolvers::MergeRequestResolver
end
......
---
title: Add negative filters for merge requests API
merge_request: 58021
author:
type: added
# frozen_string_literal: true
module Gitlab
module Graphql
module NegatableArguments
class TypeDefiner
def initialize(resolver_class, type_definition)
@resolver_class = resolver_class
@type_definition = type_definition
end
def define!
negated_params_type.instance_eval(&@type_definition)
end
def negated_params_type
@negated_params_type ||= existing_type || build_type
end
private
def existing_type
::Types.const_get(type_class_name, false) if ::Types.const_defined?(type_class_name)
end
def build_type
klass = Class.new(::Types::BaseInputObject)
::Types.const_set(type_class_name, klass)
klass
end
def type_class_name
@type_class_name ||= begin
base_name = @resolver_class.name.sub('Resolvers::', '')
base_name + 'NegatedParamsType'
end
end
end
def negated(param_key: :not, &block)
definer = ::Gitlab::Graphql::NegatableArguments::TypeDefiner.new(self, block)
definer.define!
argument param_key, definer.negated_params_type,
required: false,
description: <<~MD
List of negated arguments.
Warning: this argument is experimental and a subject to change in future.
MD
end
end
end
end
......@@ -189,6 +189,17 @@ RSpec.describe Resolvers::MergeRequestsResolver do
end
end
context 'with negated label argument' do
let_it_be(:label) { merge_request_6.labels.first }
let_it_be(:with_label) { create(:labeled_merge_request, :closed, labels: [label], **common_attrs) }
it 'excludes merge requests with given label from selection' do
result = resolve_mr(project, not: { labels: [label.title] })
expect(result).not_to include(merge_request_6, with_label)
end
end
context 'with merged_after and merged_before arguments' do
before do
merge_request_1.metrics.update!(merged_at: 10.days.ago)
......@@ -221,6 +232,14 @@ RSpec.describe Resolvers::MergeRequestsResolver do
end
end
context 'with negated milestone argument' do
it 'filters out merge requests with given milestone title' do
result = resolve_mr(project, not: { milestone_title: milestone.title })
expect(result).not_to include(merge_request_with_milestone)
end
end
describe 'combinations' do
it 'requires all filters' do
create(:merge_request, :closed, **common_attrs, source_branch: merge_request_4.source_branch)
......
......@@ -243,6 +243,7 @@ RSpec.describe GitlabSchema.types['Project'] do
:assignee_username,
:reviewer_username,
:milestone_title,
:not,
:sort
)
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::NegatableArguments do
let(:test_resolver) do
Class.new(Resolvers::BaseResolver).tap do |klass|
klass.extend described_class
allow(klass).to receive(:name).and_return('Resolvers::TestResolver')
end
end
describe '#negated' do
it 'defines :not argument' do
test_resolver.negated {}
expect(test_resolver.arguments['not'].type.name).to eq "Types::TestResolverNegatedParamsType"
end
it 'defines any arguments passed as block' do
test_resolver.negated do
argument :foo, GraphQL::STRING_TYPE, required: false
end
expect(test_resolver.arguments['not'].type.arguments.keys).to match_array(['foo'])
end
it 'defines all arguments passed as block even if called multiple times' do
test_resolver.negated do
argument :foo, GraphQL::STRING_TYPE, required: false
end
test_resolver.negated do
argument :bar, GraphQL::STRING_TYPE, required: false
end
expect(test_resolver.arguments['not'].type.arguments.keys).to match_array(%w[foo bar])
end
it 'allows to specify custom argument name' do
test_resolver.negated(param_key: :negative) {}
expect(test_resolver.arguments).to include('negative')
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