Commit 87a24bd9 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '118742-iterations-filter-graphql' into 'master'

Support filtering by iterations with GraphQL

See merge request gitlab-org/gitlab!47263
parents 0e8c176d 7487755d
......@@ -1904,6 +1904,16 @@ input BoardIssueInput {
"""
epicWildcardId: EpicWildcardId
"""
Filter by iteration title
"""
iterationTitle: String
"""
Filter by iteration ID wildcard
"""
iterationWildcardId: IterationWildcardId
"""
Filter by label name
"""
......@@ -11371,6 +11381,21 @@ enum IterationState {
upcoming
}
"""
Iteration ID wildcard values
"""
enum IterationWildcardId {
"""
An iteration is assigned
"""
ANY
"""
No iteration is assigned
"""
NONE
}
"""
Represents untyped JSON
"""
......@@ -13883,6 +13908,11 @@ input NegatedBoardIssueInput {
"""
epicId: EpicID
"""
Filter by iteration title
"""
iterationTitle: String
"""
Filter by label name
"""
......
......@@ -5111,6 +5111,16 @@
},
"defaultValue": null
},
{
"name": "iterationTitle",
"description": "Filter by iteration title",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "weight",
"description": "Filter by weight",
......@@ -5150,6 +5160,16 @@
"ofType": null
},
"defaultValue": null
},
{
"name": "iterationWildcardId",
"description": "Filter by iteration ID wildcard",
"type": {
"kind": "ENUM",
"name": "IterationWildcardId",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
......@@ -31135,6 +31155,29 @@
],
"possibleTypes": null
},
{
"kind": "ENUM",
"name": "IterationWildcardId",
"description": "Iteration ID wildcard values",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": [
{
"name": "NONE",
"description": "No iteration is assigned",
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "ANY",
"description": "An iteration is assigned",
"isDeprecated": false,
"deprecationReason": null
}
],
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "JSON",
......@@ -41116,6 +41159,16 @@
},
"defaultValue": null
},
{
"name": "iterationTitle",
"description": "Filter by iteration title",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "weight",
"description": "Filter by weight",
......@@ -3994,6 +3994,15 @@ State of a GitLab iteration.
| `started` | |
| `upcoming` | |
### IterationWildcardId
Iteration ID wildcard values.
| Value | Description |
| ----- | ----------- |
| `ANY` | An iteration is assigned |
| `NONE` | No iteration is assigned |
### ListLimitMetric
List limit metric setting.
......
......@@ -6,6 +6,11 @@ export const EpicFilterType = {
none: 'None',
};
export const IterationFilterType = {
any: 'Any',
none: 'None',
};
export const GroupByParamType = {
epic: 'epic',
};
......
......@@ -7,7 +7,7 @@ import { historyPushState, parseBoolean } from '~/lib/utils/common_utils';
import { mergeUrlParams, removeParams } from '~/lib/utils/url_utility';
import actionsCE from '~/boards/stores/actions';
import { BoardType, ListType } from '~/boards/constants';
import { EpicFilterType, GroupByParamType } from '../constants';
import { EpicFilterType, IterationFilterType, GroupByParamType } from '../constants';
import boardsStoreEE from './boards_store_ee';
import * as types from './mutation_types';
import * as typesCE from '~/boards/stores/mutation_types';
......@@ -79,6 +79,7 @@ export default {
'epicId',
'labelName',
'milestoneTitle',
'iterationTitle',
'releaseTag',
'search',
'weight',
......@@ -94,6 +95,14 @@ export default {
} else if (filterParams.epicId) {
filterParams.epicId = fullEpicId(filterParams.epicId);
}
if (
filters.iterationId === IterationFilterType.any ||
filters.iterationId === IterationFilterType.none
) {
filterParams.iterationWildcardId = filters.iterationId.toUpperCase();
}
commit(types.SET_FILTERS, filterParams);
},
......
......@@ -8,6 +8,13 @@ module EE
override :set_filter_values
def set_filter_values(filters)
filter_by_epic(filters)
filter_by_iteration(filters)
end
private
def filter_by_epic(filters)
epic_id = filters.delete(:epic_id)
epic_wildcard_id = filters.delete(:epic_wildcard_id)
......@@ -21,6 +28,14 @@ module EE
filters[:epic_id] = epic_wildcard_id
end
end
def filter_by_iteration(filters)
iteration_wildcard_id = filters.delete(:iteration_wildcard_id)
if iteration_wildcard_id
filters[:iteration_id] = iteration_wildcard_id
end
end
end
end
end
......@@ -11,6 +11,10 @@ module EE
required: false,
description: 'Filter by epic ID. Incompatible with epicWildcardId'
argument :iteration_title, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by iteration title'
argument :weight, GraphQL::STRING_TYPE,
required: false,
description: 'Filter by weight'
......
......@@ -11,6 +11,10 @@ module EE
argument :epic_wildcard_id, ::Types::Boards::EpicWildcardIdEnum,
required: false,
description: 'Filter by epic ID wildcard. Incompatible with epicId'
argument :iteration_wildcard_id, ::Types::Boards::IterationWildcardIdEnum,
required: false,
description: 'Filter by iteration ID wildcard'
end
end
end
......
# frozen_string_literal: true
module Types
module Boards
class IterationWildcardIdEnum < BaseEnum
graphql_name 'IterationWildcardId'
description 'Iteration ID wildcard values'
value 'NONE', 'No iteration is assigned'
value 'ANY', 'An iteration is assigned'
end
end
end
---
title: Add filtering by iteration in GraphQL board list issues query
merge_request: 47263
author:
type: added
......@@ -104,18 +104,39 @@ RSpec.describe 'Filter issues by iteration', :js do
let(:issue_title_selector) { '.board-card .board-card-title' }
it_behaves_like 'filters by iteration'
context 'when graphql_board_lists is disabled' do
before do
stub_feature_flags(graphql_board_lists: false)
end
it_behaves_like 'filters by iteration'
end
end
context 'group board' do
let_it_be(:board) { create(:board, group: group) }
let_it_be(:user) { create(:user) }
let(:page_path) { group_board_path(group, board) }
let(:issue_title_selector) { '.board-card .board-card-title' }
before_all do
group.add_developer(user)
end
before do
stub_feature_flags(graphql_board_lists: false)
sign_in user
end
it_behaves_like 'filters by iteration'
context 'when graphql_board_lists is disabled' do
before do
stub_feature_flags(graphql_board_lists: false)
end
it_behaves_like 'filters by iteration'
end
end
end
......@@ -74,6 +74,23 @@ describe('setFilters', () => {
);
});
it('should commit mutation SET_FILTERS, updates iterationWildcardId', () => {
const state = {
filters: {},
};
const filters = { labelName: 'label', iterationId: 'None' };
const updatedFilters = { labelName: 'label', iterationWildcardId: 'NONE' };
return testAction(
actions.setFilters,
filters,
state,
[{ type: types.SET_FILTERS, payload: updatedFilters }],
[],
);
});
it('should commit mutation SET_FILTERS, dispatches setEpicSwimlanes action if filters contain groupBy epic', () => {
const state = {
filters: {},
......
......@@ -11,32 +11,56 @@ RSpec.describe Resolvers::BoardListIssuesResolver do
let_it_be(:board) { create(:board, project: project) }
let_it_be(:label) { create(:label, project: project) }
let_it_be(:list) { create(:list, board: board, label: label) }
let_it_be(:issue) { create(:issue, project: project, labels: [label]) }
let_it_be(:epic) { create(:epic, group: group) }
let_it_be(:epic_issue) { create(:epic_issue, epic: epic, issue: issue) }
before_all do
group.add_developer(user)
end
describe '#resolve' do
before do
stub_licensed_features(epics: true)
group.add_developer(user)
end
context 'filtering by epic' do
let_it_be(:issue) { create(:issue, project: project, labels: [label]) }
let_it_be(:epic) { create(:epic, group: group) }
let_it_be(:epic_issue) { create(:epic_issue, epic: epic, issue: issue) }
it 'raises an exception if both epic_id and epic_wildcard_id are present' do
expect do
resolve_board_list_issues({ filters: { epic_id: epic.to_global_id, epic_wildcard_id: 'NONE' } })
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
before do
stub_licensed_features(epics: true)
end
it 'raises an exception if both epic_id and epic_wildcard_id are present' do
expect do
resolve_board_list_issues({ filters: { epic_id: epic.to_global_id, epic_wildcard_id: 'NONE' } })
end.to raise_error(Gitlab::Graphql::Errors::ArgumentError)
end
it 'accepts epic global id' do
result = resolve_board_list_issues({ filters: { epic_id: epic.to_global_id } }).items
it 'accepts epic global id' do
result = resolve_board_list_issues({ filters: { epic_id: epic.to_global_id } }).items
expect(result).to match_array([issue])
expect(result).to match_array([issue])
end
it 'accepts epic wildcard id' do
result = resolve_board_list_issues({ filters: { epic_wildcard_id: 'NONE' } }).items
expect(result).to match_array([])
end
end
it 'accepts epic wildcard id' do
result = resolve_board_list_issues({ filters: { epic_wildcard_id: 'NONE' } }).items
context 'filtering by iteration' do
let_it_be(:iteration) { create(:iteration, group: group) }
let_it_be(:issue_with_iteration) { create(:issue, project: project, labels: [label], iteration: iteration) }
let_it_be(:issue_without_iteration) { create(:issue, project: project, labels: [label]) }
it 'accepts iteration title' do
result = resolve_board_list_issues({ filters: { iteration_title: iteration.title } }).items
expect(result).to contain_exactly(issue_with_iteration)
end
it 'accepts iteration wildcard id' do
result = resolve_board_list_issues({ filters: { iteration_wildcard_id: 'NONE' } }).items
expect(result).to match_array([])
expect(result).to contain_exactly(issue_without_iteration)
end
end
end
......
......@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['BoardIssueInput'] do
it 'has specific fields' do
allowed_args = %w(epicId epicWildcardId weight)
allowed_args = %w(epicId epicWildcardId iterationTitle iterationWildcardId weight)
expect(described_class.arguments.keys).to include(*allowed_args)
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