Commit 2e10229b authored by Felipe Artur's avatar Felipe Artur

GraphQL - Allow to filter epics on epic lis lists

Allow to filter epics from epic lists on GraphQL
parent 4eceeb13
# frozen_string_literal: true
module Types
module Boards
# Common arguments that we can be used to filter boards epics and issues
class BoardIssuableInputBaseType < BaseInputObject
argument :label_name, GraphQL::STRING_TYPE.to_list_type,
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
...@@ -2,11 +2,8 @@ ...@@ -2,11 +2,8 @@
module Types module Types
module Boards module Boards
class BoardIssueInputBaseType < BaseInputObject # rubocop: disable Graphql/AuthorizeTypes
argument :label_name, GraphQL::STRING_TYPE.to_list_type, class BoardIssueInputBaseType < BoardIssuableInputBaseType
required: false,
description: 'Filter by label name.'
argument :milestone_title, GraphQL::STRING_TYPE, argument :milestone_title, GraphQL::STRING_TYPE,
required: false, required: false,
description: 'Filter by milestone title.' description: 'Filter by milestone title.'
...@@ -15,17 +12,9 @@ module Types ...@@ -15,17 +12,9 @@ module Types
required: false, required: false,
description: 'Filter by assignee username.' description: 'Filter by assignee username.'
argument :author_username, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by author username.'
argument :release_tag, GraphQL::STRING_TYPE, argument :release_tag, GraphQL::STRING_TYPE,
required: false, required: false,
description: 'Filter by release tag.' description: 'Filter by release tag.'
argument :my_reaction_emoji, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by reaction emoji.'
end end
end end
end end
......
...@@ -7,8 +7,13 @@ module Resolvers ...@@ -7,8 +7,13 @@ module Resolvers
alias_method :list, :object alias_method :list, :object
def resolve(**args) argument :filters, Types::Boards::BoardEpicInputType,
filter_params = { board_id: list.epic_board.id, id: list.id } required: false,
description: 'Filters applied when selecting epics in the board list.'
def resolve(filters: {}, **args)
filter_params = { board_id: list.epic_board.id, id: list.id }.merge(filters)
service = ::Boards::Epics::ListService.new(list.epic_board.group, context[:current_user], filter_params) service = ::Boards::Epics::ListService.new(list.epic_board.group, context[:current_user], filter_params)
offset_pagination(service.execute) offset_pagination(service.execute)
......
# frozen_string_literal: true
module Types
module Boards
class BoardEpicInputType < BoardIssuableInputBaseType
graphql_name 'EpicFilters'
argument :search, GraphQL::STRING_TYPE,
required: false,
description: 'Search query for epic title or description.'
end
end
end
---
title: GraphQL - Allow to filter epics on epic lists
merge_request: 56026
author:
type: added
...@@ -25,10 +25,12 @@ RSpec.describe Resolvers::Boards::BoardListEpicsResolver do ...@@ -25,10 +25,12 @@ RSpec.describe Resolvers::Boards::BoardListEpicsResolver do
expect(described_class).to have_nullable_graphql_type(Types::EpicType.connection_type) expect(described_class).to have_nullable_graphql_type(Types::EpicType.connection_type)
end end
describe '#resolve' do def resolve_board_list_epics(args: {})
let(:args) { {} } resolve(described_class, ctx: { current_user: user }, obj: list1, args: args)
end
subject(:result) { resolve(described_class, ctx: { current_user: user }, obj: list1, args: args) } describe '#resolve' do
subject(:result) { resolve_board_list_epics }
before do before do
stub_licensed_features(epics: true) stub_licensed_features(epics: true)
...@@ -38,5 +40,45 @@ RSpec.describe Resolvers::Boards::BoardListEpicsResolver do ...@@ -38,5 +40,45 @@ RSpec.describe Resolvers::Boards::BoardListEpicsResolver do
it 'returns epics on the board list ordered by position on the board' do it 'returns epics on the board list ordered by position on the board' do
expect(result.to_a).to eq([list1_epic2, list1_epic1]) expect(result.to_a).to eq([list1_epic2, list1_epic1])
end end
context 'with filters' do
let_it_be(:production_label) { create(:group_label, group: group, name: 'production') }
let_it_be(:list1_epic3) { create(:labeled_epic, group: group, labels: [development, production_label], title: 'filter_this 1') }
let_it_be(:list1_epic4) { create(:labeled_epic, group: group, labels: [development], description: 'filter_this 2') }
it 'filters epics by label' do
args = { filters: { label_name: [production_label.title] } }
result = resolve_board_list_epics(args: args)
expect(result).to contain_exactly(list1_epic3)
end
it 'filters epics by author' do
args = { filters: { author_username: list1_epic4.author.username } }
result = resolve_board_list_epics(args: args)
expect(result).to contain_exactly(list1_epic4)
end
it 'filters epics by reaction emoji' do
emoji_name = 'thumbsup'
create(:award_emoji, name: emoji_name, awardable: list1_epic1, user: user)
args = { filters: { my_reaction_emoji: emoji_name } }
result = resolve_board_list_epics(args: args)
expect(result).to contain_exactly(list1_epic1)
end
it 'filters epics by title and description' do
args = { filters: { search: 'filter_this' } }
result = resolve_board_list_epics(args: args)
expect(result).to contain_exactly(list1_epic3, list1_epic4)
end
end
end end
end end
...@@ -8,16 +8,18 @@ RSpec.describe 'get list of epics for an epic board list' do ...@@ -8,16 +8,18 @@ RSpec.describe 'get list of epics for an epic board list' do
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:group) { create(:group, :private) } let_it_be(:group) { create(:group, :private) }
let_it_be(:development) { create(:group_label, group: group, name: 'Development') } let_it_be(:development) { create(:group_label, group: group, name: 'Development') }
let_it_be(:staging) { create(:group_label, group: group, name: 'Staging') }
let_it_be(:board) { create(:epic_board, group: group) } let_it_be(:board) { create(:epic_board, group: group) }
let_it_be(:list) { create(:epic_list, epic_board: board, label: development) } let_it_be(:list) { create(:epic_list, epic_board: board, label: development) }
let_it_be(:epic1) { create(:labeled_epic, group: group, labels: [development]) } let_it_be(:epic1) { create(:labeled_epic, group: group, labels: [development]) }
let_it_be(:epic2) { create(:labeled_epic, group: group, labels: [development]) } let_it_be(:epic2) { create(:labeled_epic, group: group, labels: [development]) }
let_it_be(:epic3) { create(:labeled_epic, group: group, labels: [development]) } let_it_be(:epic3) { create(:labeled_epic, group: group, labels: [development, staging], author: current_user) }
let_it_be(:epic4) { create(:labeled_epic, group: group) } let_it_be(:epic4) { create(:labeled_epic, group: group) }
let_it_be(:epic_pos1) { create(:epic_board_position, epic: epic1, epic_board: board, relative_position: 20) } let_it_be(:epic_pos1) { create(:epic_board_position, epic: epic1, epic_board: board, relative_position: 20) }
let_it_be(:epic_pos2) { create(:epic_board_position, epic: epic2, epic_board: board, relative_position: 10) } let_it_be(:epic_pos2) { create(:epic_board_position, epic: epic2, epic_board: board, relative_position: 10) }
let(:data_path) { [:group, :epicBoard, :lists, :nodes, 0, :epics] }
def pagination_query(params = {}) def pagination_query(params = {})
graphql_query_for(:group, { full_path: group.full_path }, graphql_query_for(:group, { full_path: group.full_path },
...@@ -25,7 +27,7 @@ RSpec.describe 'get list of epics for an epic board list' do ...@@ -25,7 +27,7 @@ RSpec.describe 'get list of epics for an epic board list' do
epicBoard(id: "#{board.to_global_id}") { epicBoard(id: "#{board.to_global_id}") {
lists(id: "#{list.to_global_id}") { lists(id: "#{list.to_global_id}") {
nodes { nodes {
#{query_nodes(:epics, all_graphql_fields_for('epics'.classify), include_pagination_info: true, args: params)} #{query_nodes(:epics, epic_fields, include_pagination_info: true, args: params)}
} }
} }
} }
...@@ -39,7 +41,7 @@ RSpec.describe 'get list of epics for an epic board list' do ...@@ -39,7 +41,7 @@ RSpec.describe 'get list of epics for an epic board list' do
end end
describe 'sorting and pagination' do describe 'sorting and pagination' do
let(:data_path) { [:group, :epicBoard, :lists, :nodes, 0, :epics] } let(:epic_fields) { all_graphql_fields_for('epics'.classify) }
let(:expected_results) { [epic2.to_global_id.to_s, epic1.to_global_id.to_s, epic3.to_global_id.to_s] } let(:expected_results) { [epic2.to_global_id.to_s, epic1.to_global_id.to_s, epic3.to_global_id.to_s] }
def pagination_results_data(nodes) def pagination_results_data(nodes)
...@@ -53,4 +55,18 @@ RSpec.describe 'get list of epics for an epic board list' do ...@@ -53,4 +55,18 @@ RSpec.describe 'get list of epics for an epic board list' do
let(:first_param) { 2 } let(:first_param) { 2 }
end end
end end
context 'filters' do
let(:epic_fields) { 'id' }
it 'finds only epics matching the filter' do
filter_params = { filters: { author_username: current_user.username, label_name: [staging.title] } }
query = pagination_query(filter_params)
post_graphql(query, current_user: current_user)
boards = graphql_data_at(*data_path, :nodes, :id)
expect(boards).to contain_exactly(global_id_of(epic3))
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