Commit 97a7e283 authored by Scott Stern's avatar Scott Stern Committed by Simon Knox

Add not support for epic boards filtered search

parent bafa3cbe
<script>
import { pickBy } from 'lodash';
import { mapActions, mapState } from 'vuex';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
......@@ -13,6 +14,8 @@ export default {
search: __('Search'),
label: __('Label'),
author: __('Author'),
is: __('is'),
isNot: __('is not'),
},
components: { FilteredSearch },
inject: ['initialFilterParams'],
......@@ -24,12 +27,16 @@ export default {
computed: {
...mapState(['fullPath']),
tokens() {
const { label, is, isNot, author } = this.$options.i18n;
return [
{
icon: 'labels',
title: this.$options.i18n.label,
title: label,
type: 'label_name',
operators: [{ value: '=', description: 'is' }],
operators: [
{ value: '=', description: is },
{ value: '!=', description: isNot },
],
token: LabelToken,
unique: false,
symbol: '~',
......@@ -37,9 +44,12 @@ export default {
},
{
icon: 'pencil',
title: this.$options.i18n.author,
title: author,
type: 'author_username',
operators: [{ value: '=', description: 'is' }],
operators: [
{ value: '=', description: is },
{ value: '!=', description: isNot },
],
symbol: '@',
token: AuthorToken,
unique: true,
......@@ -49,8 +59,20 @@ export default {
},
urlParams() {
const { authorUsername, labelName, search } = this.filterParams;
let notParams = {};
if (Object.prototype.hasOwnProperty.call(this.filterParams, 'not')) {
notParams = pickBy(
{
'not[label_name][]': this.filterParams.not.labelName,
'not[author_username]': this.filterParams.not.authorUsername,
},
undefined,
);
}
return {
...notParams,
author_username: authorUsername,
'label_name[]': labelName,
search,
......@@ -66,7 +88,7 @@ export default {
if (authorUsername) {
filteredSearchValue.push({
type: 'author_username',
value: { data: authorUsername },
value: { data: authorUsername, operator: '=' },
});
}
......@@ -74,7 +96,23 @@ export default {
filteredSearchValue.push(
...labelName.map((label) => ({
type: 'label_name',
value: { data: label },
value: { data: label, operator: '=' },
})),
);
}
if (this.filterParams['not[authorUsername]']) {
filteredSearchValue.push({
type: 'author_username',
value: { data: this.filterParams['not[authorUsername]'], operator: '!=' },
});
}
if (this.filterParams['not[labelName]']) {
filteredSearchValue.push(
...this.filterParams['not[labelName]'].map((label) => ({
type: 'label_name',
value: { data: label, operator: '!=' },
})),
);
}
......@@ -86,6 +124,12 @@ export default {
return filteredSearchValue;
},
getFilterParams(filters = []) {
const notFilters = filters.filter((item) => item.value.operator === '!=');
const equalsFilters = filters.filter((item) => item.value.operator === '=');
return { ...this.generateParams(equalsFilters), not: { ...this.generateParams(notFilters) } };
},
generateParams(filters = []) {
const filterParams = {};
const labels = [];
const plainText = [];
......
......@@ -124,10 +124,7 @@ export default {
const supportedFilters = [...SupportedFilters, ...SupportedFiltersEE];
const filterParams = getSupportedParams(filters, supportedFilters);
// Temporarily disabled until negated filters are supported for epic boards
if (!getters.isEpicBoard) {
filterParams.not = transformNotFilters(filters);
}
filterParams.not = transformNotFilters(filters);
if (filters.groupBy === GroupByParamType.epic) {
dispatch('setEpicSwimlanes');
......
......@@ -224,9 +224,26 @@ RSpec.describe 'epic boards', :js do
find_field('Search').click
end
it 'can select a Label in order to filter the board' do
it 'can select a Label in order to filter the board by not equals' do
page.within('[data-testid="epic-filtered-search"]') do
click_link 'Label'
click_link '!='
click_link label.title
find('input').native.send_keys(:return)
end
wait_for_requests
expect(page).not_to have_content('Epic1')
expect(page).to have_content('Epic2')
expect(page).to have_content('Epic3')
end
it 'can select a Label in order to filter the board by equals' do
page.within('[data-testid="epic-filtered-search"]') do
click_link 'Label'
click_token_equals
click_link label.title
find('input').native.send_keys(:return)
......@@ -239,9 +256,10 @@ RSpec.describe 'epic boards', :js do
expect(page).not_to have_content('Epic3')
end
it 'can select an Author in order to filter the board' do
it 'can select an Author in order to filter the board by equals' do
page.within('[data-testid="epic-filtered-search"]') do
click_link 'Author'
click_token_equals
click_link user.name
find('input').native.send_keys(:return)
......@@ -253,6 +271,22 @@ RSpec.describe 'epic boards', :js do
expect(page).not_to have_content('Epic2')
expect(page).not_to have_content('Epic3')
end
it 'can select an Author in order to filter the board by not equals' do
page.within('[data-testid="epic-filtered-search"]') do
click_link 'Author'
click_link '!='
click_link user.name
find('input').native.send_keys(:return)
end
wait_for_requests
expect(page).not_to have_content('Epic1')
expect(page).to have_content('Epic2')
expect(page).to have_content('Epic3')
end
end
def visit_epic_boards_page
......@@ -343,6 +377,12 @@ RSpec.describe 'epic boards', :js do
find('.board-config-modal .modal-content').click
end
# This isnt the "best" matcher but because we have opts
# != and = the find function returns both links when finding by =
def click_token_equals
first('a', text: '=').click
end
def find_board_list(board_number)
find(".board:nth-child(#{board_number})")
end
......
......@@ -53,7 +53,10 @@ describe('EpicFilteredSearch', () => {
icon: 'labels',
title: __('Label'),
type: 'label_name',
operators: [{ value: '=', description: 'is' }],
operators: [
{ value: '=', description: 'is' },
{ value: '!=', description: 'is not' },
],
token: LabelToken,
unique: false,
symbol: '~',
......@@ -63,7 +66,10 @@ describe('EpicFilteredSearch', () => {
icon: 'pencil',
title: __('Author'),
type: 'author_username',
operators: [{ value: '=', description: 'is' }],
operators: [
{ value: '=', description: 'is' },
{ value: '!=', description: 'is not' },
],
symbol: '@',
token: AuthorToken,
unique: true,
......@@ -105,9 +111,9 @@ describe('EpicFilteredSearch', () => {
it('sets the url params to the correct results', async () => {
const mockFilters = [
{ type: 'author_username', value: { data: 'root' } },
{ type: 'label_name', value: { data: 'label' } },
{ type: 'label_name', value: { data: 'label2' } },
{ type: 'author_username', value: { data: 'root', operator: '=' } },
{ type: 'label_name', value: { data: 'label', operator: '=' } },
{ type: 'label_name', value: { data: 'label2', operator: '!=' } },
];
jest.spyOn(urlUtility, 'updateHistory');
findFilteredSearch().vm.$emit('onFilter', mockFilters);
......@@ -115,7 +121,7 @@ describe('EpicFilteredSearch', () => {
expect(urlUtility.updateHistory).toHaveBeenCalledWith({
title: '',
replace: true,
url: 'http://test.host/?author_username=root&label_name[]=label&label_name[]=label2',
url: 'http://test.host/?not[label_name][]=label2&author_username=root&label_name[]=label',
});
});
});
......@@ -131,8 +137,8 @@ describe('EpicFilteredSearch', () => {
it('passes the correct props to FitlerSearchBar', async () => {
expect(findFilteredSearch().props('initialFilterValue')).toEqual([
{ type: 'author_username', value: { data: 'root' } },
{ type: 'label_name', value: { data: 'label' } },
{ type: 'author_username', value: { data: 'root', operator: '=' } },
{ type: 'label_name', value: { data: 'label', operator: '=' } },
]);
});
});
......
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