Commit f6334237 authored by Simon Knox's avatar Simon Knox

Merge branch 'ss/add-not-support-epic-filtered-search' into 'master'

Add not support for epic boards filtered search

See merge request gitlab-org/gitlab!60310
parents 9ed55eca 97a7e283
<script> <script>
import { pickBy } from 'lodash';
import { mapActions, mapState } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility'; import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
...@@ -13,6 +14,8 @@ export default { ...@@ -13,6 +14,8 @@ export default {
search: __('Search'), search: __('Search'),
label: __('Label'), label: __('Label'),
author: __('Author'), author: __('Author'),
is: __('is'),
isNot: __('is not'),
}, },
components: { FilteredSearch }, components: { FilteredSearch },
inject: ['initialFilterParams'], inject: ['initialFilterParams'],
...@@ -24,12 +27,16 @@ export default { ...@@ -24,12 +27,16 @@ export default {
computed: { computed: {
...mapState(['fullPath']), ...mapState(['fullPath']),
tokens() { tokens() {
const { label, is, isNot, author } = this.$options.i18n;
return [ return [
{ {
icon: 'labels', icon: 'labels',
title: this.$options.i18n.label, title: label,
type: 'label_name', type: 'label_name',
operators: [{ value: '=', description: 'is' }], operators: [
{ value: '=', description: is },
{ value: '!=', description: isNot },
],
token: LabelToken, token: LabelToken,
unique: false, unique: false,
symbol: '~', symbol: '~',
...@@ -37,9 +44,12 @@ export default { ...@@ -37,9 +44,12 @@ export default {
}, },
{ {
icon: 'pencil', icon: 'pencil',
title: this.$options.i18n.author, title: author,
type: 'author_username', type: 'author_username',
operators: [{ value: '=', description: 'is' }], operators: [
{ value: '=', description: is },
{ value: '!=', description: isNot },
],
symbol: '@', symbol: '@',
token: AuthorToken, token: AuthorToken,
unique: true, unique: true,
...@@ -49,8 +59,20 @@ export default { ...@@ -49,8 +59,20 @@ export default {
}, },
urlParams() { urlParams() {
const { authorUsername, labelName, search } = this.filterParams; 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 { return {
...notParams,
author_username: authorUsername, author_username: authorUsername,
'label_name[]': labelName, 'label_name[]': labelName,
search, search,
...@@ -66,7 +88,7 @@ export default { ...@@ -66,7 +88,7 @@ export default {
if (authorUsername) { if (authorUsername) {
filteredSearchValue.push({ filteredSearchValue.push({
type: 'author_username', type: 'author_username',
value: { data: authorUsername }, value: { data: authorUsername, operator: '=' },
}); });
} }
...@@ -74,7 +96,23 @@ export default { ...@@ -74,7 +96,23 @@ export default {
filteredSearchValue.push( filteredSearchValue.push(
...labelName.map((label) => ({ ...labelName.map((label) => ({
type: 'label_name', 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 { ...@@ -86,6 +124,12 @@ export default {
return filteredSearchValue; return filteredSearchValue;
}, },
getFilterParams(filters = []) { 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 filterParams = {};
const labels = []; const labels = [];
const plainText = []; const plainText = [];
......
...@@ -124,10 +124,7 @@ export default { ...@@ -124,10 +124,7 @@ export default {
const supportedFilters = [...SupportedFilters, ...SupportedFiltersEE]; const supportedFilters = [...SupportedFilters, ...SupportedFiltersEE];
const filterParams = getSupportedParams(filters, supportedFilters); const filterParams = getSupportedParams(filters, supportedFilters);
// Temporarily disabled until negated filters are supported for epic boards filterParams.not = transformNotFilters(filters);
if (!getters.isEpicBoard) {
filterParams.not = transformNotFilters(filters);
}
if (filters.groupBy === GroupByParamType.epic) { if (filters.groupBy === GroupByParamType.epic) {
dispatch('setEpicSwimlanes'); dispatch('setEpicSwimlanes');
......
...@@ -224,9 +224,26 @@ RSpec.describe 'epic boards', :js do ...@@ -224,9 +224,26 @@ RSpec.describe 'epic boards', :js do
find_field('Search').click find_field('Search').click
end 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 page.within('[data-testid="epic-filtered-search"]') do
click_link 'Label' 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 click_link label.title
find('input').native.send_keys(:return) find('input').native.send_keys(:return)
...@@ -239,9 +256,10 @@ RSpec.describe 'epic boards', :js do ...@@ -239,9 +256,10 @@ RSpec.describe 'epic boards', :js do
expect(page).not_to have_content('Epic3') expect(page).not_to have_content('Epic3')
end 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 page.within('[data-testid="epic-filtered-search"]') do
click_link 'Author' click_link 'Author'
click_token_equals
click_link user.name click_link user.name
find('input').native.send_keys(:return) find('input').native.send_keys(:return)
...@@ -253,6 +271,22 @@ RSpec.describe 'epic boards', :js do ...@@ -253,6 +271,22 @@ RSpec.describe 'epic boards', :js do
expect(page).not_to have_content('Epic2') expect(page).not_to have_content('Epic2')
expect(page).not_to have_content('Epic3') expect(page).not_to have_content('Epic3')
end 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 end
def visit_epic_boards_page def visit_epic_boards_page
...@@ -343,6 +377,12 @@ RSpec.describe 'epic boards', :js do ...@@ -343,6 +377,12 @@ RSpec.describe 'epic boards', :js do
find('.board-config-modal .modal-content').click find('.board-config-modal .modal-content').click
end 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) def find_board_list(board_number)
find(".board:nth-child(#{board_number})") find(".board:nth-child(#{board_number})")
end end
......
...@@ -53,7 +53,10 @@ describe('EpicFilteredSearch', () => { ...@@ -53,7 +53,10 @@ describe('EpicFilteredSearch', () => {
icon: 'labels', icon: 'labels',
title: __('Label'), title: __('Label'),
type: 'label_name', type: 'label_name',
operators: [{ value: '=', description: 'is' }], operators: [
{ value: '=', description: 'is' },
{ value: '!=', description: 'is not' },
],
token: LabelToken, token: LabelToken,
unique: false, unique: false,
symbol: '~', symbol: '~',
...@@ -63,7 +66,10 @@ describe('EpicFilteredSearch', () => { ...@@ -63,7 +66,10 @@ describe('EpicFilteredSearch', () => {
icon: 'pencil', icon: 'pencil',
title: __('Author'), title: __('Author'),
type: 'author_username', type: 'author_username',
operators: [{ value: '=', description: 'is' }], operators: [
{ value: '=', description: 'is' },
{ value: '!=', description: 'is not' },
],
symbol: '@', symbol: '@',
token: AuthorToken, token: AuthorToken,
unique: true, unique: true,
...@@ -105,9 +111,9 @@ describe('EpicFilteredSearch', () => { ...@@ -105,9 +111,9 @@ describe('EpicFilteredSearch', () => {
it('sets the url params to the correct results', async () => { it('sets the url params to the correct results', async () => {
const mockFilters = [ const mockFilters = [
{ type: 'author_username', value: { data: 'root' } }, { type: 'author_username', value: { data: 'root', operator: '=' } },
{ type: 'label_name', value: { data: 'label' } }, { type: 'label_name', value: { data: 'label', operator: '=' } },
{ type: 'label_name', value: { data: 'label2' } }, { type: 'label_name', value: { data: 'label2', operator: '!=' } },
]; ];
jest.spyOn(urlUtility, 'updateHistory'); jest.spyOn(urlUtility, 'updateHistory');
findFilteredSearch().vm.$emit('onFilter', mockFilters); findFilteredSearch().vm.$emit('onFilter', mockFilters);
...@@ -115,7 +121,7 @@ describe('EpicFilteredSearch', () => { ...@@ -115,7 +121,7 @@ describe('EpicFilteredSearch', () => {
expect(urlUtility.updateHistory).toHaveBeenCalledWith({ expect(urlUtility.updateHistory).toHaveBeenCalledWith({
title: '', title: '',
replace: true, 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', () => { ...@@ -131,8 +137,8 @@ describe('EpicFilteredSearch', () => {
it('passes the correct props to FitlerSearchBar', async () => { it('passes the correct props to FitlerSearchBar', async () => {
expect(findFilteredSearch().props('initialFilterValue')).toEqual([ expect(findFilteredSearch().props('initialFilterValue')).toEqual([
{ type: 'author_username', value: { data: 'root' } }, { type: 'author_username', value: { data: 'root', operator: '=' } },
{ type: 'label_name', value: { data: 'label' } }, { 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