Commit 2bf60538 authored by Florie Guibert's avatar Florie Guibert

Add Epic to new filtered search in issue boards

Allow user to filter issues by epic on boards
parent 1d98c6c6
<script>
import { pickBy } from 'lodash';
import { mapActions } from 'vuex';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
......@@ -35,7 +36,9 @@ export default {
milestoneTitle,
types,
weight,
epicId,
} = this.filterParams;
let notParams = {};
if (Object.prototype.hasOwnProperty.call(this.filterParams, 'not')) {
......@@ -61,6 +64,7 @@ export default {
search,
types,
weight,
epic_id: getIdFromGraphQLId(epicId),
};
},
},
......@@ -86,6 +90,7 @@ export default {
milestoneTitle,
types,
weight,
epicId,
} = this.filterParams;
const filteredSearchValue = [];
......@@ -133,6 +138,13 @@ export default {
});
}
if (epicId) {
filteredSearchValue.push({
type: 'epic_id',
value: { data: epicId },
});
}
if (this.filterParams['not[authorUsername]']) {
filteredSearchValue.push({
type: 'author_username',
......@@ -216,6 +228,9 @@ export default {
case 'weight':
filterParams.weight = filter.value.data;
break;
case 'epic_id':
filterParams.epicId = filter.value.data;
break;
case 'filtered-search-term':
if (filter.value.data) plainText.push(filter.value.data);
break;
......
......@@ -2,12 +2,14 @@
import { GlFilteredSearchToken } from '@gitlab/ui';
import { mapActions } from 'vuex';
import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue';
import { BoardType } from '~/boards/constants';
import issueBoardFilters from '~/boards/issue_board_filters';
import { TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { __ } from '~/locale';
import { DEFAULT_MILESTONES_GRAPHQL } from '~/vue_shared/components/filtered_search_bar/constants';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue';
......@@ -19,6 +21,7 @@ export default {
},
i18n: {
search: __('Search'),
epic: __('Epic'),
label: __('Label'),
author: __('Author'),
assignee: __('Assignee'),
......@@ -31,6 +34,7 @@ export default {
isNot: __('is not'),
},
components: { BoardFilteredSearch },
inject: ['epicFeatureAvailable'],
props: {
fullPath: {
type: String,
......@@ -42,8 +46,17 @@ export default {
},
},
computed: {
isGroupBoard() {
return this.boardType === BoardType.group;
},
epicsGroupPath() {
return this.isGroupBoard
? this.fullPath
: this.fullPath.slice(0, this.fullPath.lastIndexOf('/'));
},
tokens() {
const {
epic,
label,
is,
isNot,
......@@ -90,6 +103,21 @@ export default {
fetchAuthors,
preloadedAuthors: this.preloadedAuthors(),
},
...(this.epicFeatureAvailable
? [
{
type: 'epic_id',
title: epic,
icon: 'epic',
token: EpicToken,
unique: true,
symbol: '&',
idProperty: 'id',
useIdValue: true,
fullPath: this.epicsGroupPath,
},
]
: []),
{
icon: 'labels',
title: label,
......
......@@ -109,7 +109,7 @@ export default () => {
});
if (gon?.features?.issueBoardsFilteredSearch) {
initBoardsFilteredSearch(apolloProvider);
initBoardsFilteredSearch(apolloProvider, parseBoolean($boardApp.dataset.epicFeatureAvailable));
}
mountBoardApp($boardApp);
......
......@@ -4,7 +4,7 @@ import store from '~/boards/stores';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
export default (apolloProvider) => {
export default (apolloProvider, epicFeatureAvailable = false) => {
const el = document.getElementById('js-issue-board-filtered-search');
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
......@@ -20,6 +20,7 @@ export default (apolloProvider) => {
el,
provide: {
initialFilterParams,
epicFeatureAvailable,
},
store, // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/324094
apolloProvider,
......
......@@ -9,28 +9,33 @@ jest.mock('~/boards/issue_board_filters');
describe('IssueBoardFilter', () => {
let wrapper;
const createComponent = () => {
const createComponent = ({ epicFeatureAvailable = false } = {}) => {
wrapper = shallowMount(IssueBoardFilteredSpec, {
props: { fullPath: '', boardType: '' },
propsData: { fullPath: 'gitlab-org', boardType: 'group' },
provide: {
epicFeatureAvailable,
},
});
};
let fetchAuthorsSpy;
let fetchLabelsSpy;
beforeEach(() => {
fetchAuthorsSpy = jest.fn();
fetchLabelsSpy = jest.fn();
issueBoardFilters.mockReturnValue({
fetchAuthors: fetchAuthorsSpy,
fetchLabels: fetchLabelsSpy,
});
});
afterEach(() => {
wrapper.destroy();
});
describe('default', () => {
let fetchAuthorsSpy;
let fetchLabelsSpy;
beforeEach(() => {
fetchAuthorsSpy = jest.fn();
fetchLabelsSpy = jest.fn();
issueBoardFilters.mockReturnValue({
fetchAuthors: fetchAuthorsSpy,
fetchLabels: fetchLabelsSpy,
});
createComponent();
});
......@@ -44,4 +49,16 @@ describe('IssueBoardFilter', () => {
expect(wrapper.find(BoardFilteredSearch).props('tokens')).toEqual(tokens);
});
});
describe('when epics are available', () => {
beforeEach(() => {
createComponent({ epicFeatureAvailable: true });
});
it('passes the correct tokens to BoardFilteredSearch including epics', () => {
const tokens = mockTokens(fetchLabelsSpy, fetchAuthorsSpy, wrapper.vm.fetchMilestones, true);
expect(wrapper.find(BoardFilteredSearch).props('tokens')).toEqual(tokens);
});
});
});
......@@ -4,6 +4,7 @@ import { ListType } from '~/boards/constants';
import { __ } from '~/locale';
import { DEFAULT_MILESTONES_GRAPHQL } from '~/vue_shared/components/filtered_search_bar/constants';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import WeightToken from '~/vue_shared/components/filtered_search_bar/tokens/weight_token.vue';
......@@ -538,7 +539,19 @@ export const mockMoveData = {
...mockMoveIssueParams,
};
export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [
export const mockEpicToken = {
type: 'epic_id',
icon: 'epic',
title: 'Epic',
unique: true,
symbol: '&',
token: EpicToken,
idProperty: 'id',
useIdValue: true,
fullPath: 'gitlab-org',
};
export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones, epics = false) => [
{
icon: 'user',
title: __('Assignee'),
......@@ -566,6 +579,7 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [
fetchAuthors,
preloadedAuthors: [],
},
...(epics ? [mockEpicToken] : []),
{
icon: 'labels',
title: __('Label'),
......
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