Commit 93afbe07 authored by charlie ablett's avatar charlie ablett

Merge branch 'issue_273459' into 'master'

GraphQl Allow filtering epics by negated values

See merge request gitlab-org/gitlab!61823
parents e87924f1 b44e246b
...@@ -7228,6 +7228,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -7228,6 +7228,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="boardepicchildrenlabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. | | <a id="boardepicchildrenlabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. |
| <a id="boardepicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="boardepicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="boardepicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="boardepicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="boardepicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="boardepicchildrensearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="boardepicchildrensearch"></a>`search` | [`String`](#string) | Search query for epic title or description. |
| <a id="boardepicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="boardepicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="boardepicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="boardepicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
...@@ -8383,6 +8384,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -8383,6 +8384,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="epicchildrenlabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. | | <a id="epicchildrenlabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. |
| <a id="epicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="epicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="epicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="epicchildrenmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="epicchildrennot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="epicchildrensearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="epicchildrensearch"></a>`search` | [`String`](#string) | Search query for epic title or description. |
| <a id="epicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="epicchildrensort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="epicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="epicchildrenstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
...@@ -8983,6 +8985,7 @@ Returns [`Epic`](#epic). ...@@ -8983,6 +8985,7 @@ Returns [`Epic`](#epic).
| <a id="groupepiclabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. | | <a id="groupepiclabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. |
| <a id="groupepicmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="groupepicmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="groupepicmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="groupepicmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="groupepicnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="groupepicsearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="groupepicsearch"></a>`search` | [`String`](#string) | Search query for epic title or description. |
| <a id="groupepicsort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="groupepicsort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="groupepicstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="groupepicstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
...@@ -9025,6 +9028,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ...@@ -9025,6 +9028,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupepicslabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. | | <a id="groupepicslabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. |
| <a id="groupepicsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. | | <a id="groupepicsmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
| <a id="groupepicsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="groupepicsmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
| <a id="groupepicsnot"></a>`not` | [`NegatedEpicFilterInput`](#negatedepicfilterinput) | Negated epic arguments. |
| <a id="groupepicssearch"></a>`search` | [`String`](#string) | Search query for epic title or description. | | <a id="groupepicssearch"></a>`search` | [`String`](#string) | Search query for epic title or description. |
| <a id="groupepicssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. | | <a id="groupepicssort"></a>`sort` | [`EpicSort`](#epicsort) | List epics by sort order. |
| <a id="groupepicsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. | | <a id="groupepicsstartdate"></a>`startDate` **{warning-solid}** | [`Time`](#time) | **Deprecated** in 13.5. Use timeframe.start. |
...@@ -15970,6 +15974,16 @@ A node of an epic tree. ...@@ -15970,6 +15974,16 @@ A node of an epic tree.
| <a id="negatedepicboardissueinputlabelname"></a>`labelName` | [`[String]`](#string) | Filter by label name. | | <a id="negatedepicboardissueinputlabelname"></a>`labelName` | [`[String]`](#string) | Filter by label name. |
| <a id="negatedepicboardissueinputmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. | | <a id="negatedepicboardissueinputmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
### `NegatedEpicFilterInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="negatedepicfilterinputauthorusername"></a>`authorUsername` | [`String`](#string) | Filter by author username. |
| <a id="negatedepicfilterinputlabelname"></a>`labelName` | [`[String]`](#string) | Filter by label name. |
| <a id="negatedepicfilterinputmyreactionemoji"></a>`myReactionEmoji` | [`String`](#string) | Filter by reaction emoji applied by the current user. |
### `NegatedIssueFilterInput` ### `NegatedIssueFilterInput`
#### Arguments #### Arguments
......
...@@ -54,6 +54,10 @@ module Resolvers ...@@ -54,6 +54,10 @@ module Resolvers
required: false, required: false,
description: 'Filter by reaction emoji applied by the current user.' description: 'Filter by reaction emoji applied by the current user.'
argument :not, ::Types::Epics::NegatedEpicFilterInputType,
required: false,
description: 'Negated epic arguments.'
type Types::EpicType, null: true type Types::EpicType, null: true
def ready?(**args) def ready?(**args)
......
...@@ -177,7 +177,7 @@ module Types ...@@ -177,7 +177,7 @@ module Types
end end
def health_status def health_status
Epics::DescendantCountService.new(object, context[:current_user]) ::Epics::DescendantCountService.new(object, context[:current_user])
end end
end end
end end
# frozen_string_literal: true
module Types
module Epics
class NegatedEpicFilterInputType < BaseInputObject
argument :label_name, [GraphQL::STRING_TYPE, null: true],
required: false,
description: 'Filter by label name.'
argument :author_username, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by author username.'
argument :my_reaction_emoji, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by reaction emoji applied by the current user.'
end
end
end
...@@ -263,6 +263,36 @@ RSpec.describe Resolvers::EpicsResolver do ...@@ -263,6 +263,36 @@ RSpec.describe Resolvers::EpicsResolver do
end end
end end
end end
context 'with negated filters' do
let_it_be(:group) { create(:group) }
let_it_be(:author) { create(:user) }
let_it_be(:label) { create(:label) }
let_it_be(:epic_1) { create(:labeled_epic, group: group, labels: [label]) }
let_it_be(:epic_2) { create(:epic, group: group, author: author) }
let_it_be(:epic_3) { create(:epic, group: group) }
let_it_be(:awarded_emoji) { create(:award_emoji, name: 'thumbsup', awardable: epic_3, user: current_user) }
subject(:results) { resolve_epics(args) }
context 'for label' do
let(:args) { { not: { label_name: [label.title] } } }
it { is_expected.to contain_exactly(epic_2, epic_3) }
end
context 'for author' do
let(:args) { { not: { author_username: author.username } } }
it { is_expected.to contain_exactly(epic_1, epic_3) }
end
context 'for emoji' do
let(:args) { { not: { my_reaction_emoji: awarded_emoji.name } } }
it { is_expected.to contain_exactly(epic_1, epic_2) }
end
end
end end
context "when passing a non existent, batch loaded group" do context "when passing a non existent, batch loaded group" do
......
...@@ -200,6 +200,17 @@ RSpec.describe 'Epics through GroupQuery' do ...@@ -200,6 +200,17 @@ RSpec.describe 'Epics through GroupQuery' do
end.not_to exceed_all_query_limit(control_count + 5) end.not_to exceed_all_query_limit(control_count + 5)
end end
end end
context 'with negated filters' do
it 'returns only matching epics' do
filter_params = { not: { author_username: user2.username } }
graphql_query = query(filter_params)
post_graphql(graphql_query, current_user: user)
expect_array_response([epic.to_global_id.to_s])
end
end
end end
context 'when error requests' do context 'when error requests' 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