Commit 93c3f5f8 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '337685-add-epic-to-new-filtered-search-in-issue-boards' into 'master'

Add Epic to new filtered search in issue boards

See merge request gitlab-org/gitlab!70476
parents 533bb51e 272936fa
<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')) {
......@@ -47,6 +50,7 @@ export default {
'not[types]': this.filterParams.not.types,
'not[milestone_title]': this.filterParams.not.milestoneTitle,
'not[weight]': this.filterParams.not.weight,
'not[epic_id]': this.filterParams.not.epicId,
},
undefined,
);
......@@ -61,6 +65,7 @@ export default {
search,
types,
weight,
epic_id: getIdFromGraphQLId(epicId),
};
},
},
......@@ -86,6 +91,7 @@ export default {
milestoneTitle,
types,
weight,
epicId,
} = this.filterParams;
const filteredSearchValue = [];
......@@ -133,6 +139,13 @@ export default {
});
}
if (epicId) {
filteredSearchValue.push({
type: 'epic_id',
value: { data: epicId, operator: '=' },
});
}
if (this.filterParams['not[authorUsername]']) {
filteredSearchValue.push({
type: 'author_username',
......@@ -177,6 +190,13 @@ export default {
});
}
if (this.filterParams['not[epicId]']) {
filteredSearchValue.push({
type: 'epic_id',
value: { data: this.filterParams['not[epicId]'], operator: '!=' },
});
}
if (search) {
filteredSearchValue.push(search);
}
......@@ -216,6 +236,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,6 +2,7 @@
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';
......@@ -19,6 +20,7 @@ export default {
},
i18n: {
search: __('Search'),
epic: __('Epic'),
label: __('Label'),
author: __('Author'),
assignee: __('Assignee'),
......@@ -42,7 +44,15 @@ export default {
},
},
computed: {
tokens() {
isGroupBoard() {
return this.boardType === BoardType.group;
},
epicsGroupPath() {
return this.isGroupBoard
? this.fullPath
: this.fullPath.slice(0, this.fullPath.lastIndexOf('/'));
},
tokensCE() {
const {
label,
is,
......@@ -134,6 +144,9 @@ export default {
},
];
},
tokens() {
return this.tokensCE;
},
},
methods: {
...mapActions(['fetchMilestones']),
......
......@@ -109,7 +109,7 @@ export default () => {
});
if (gon?.features?.issueBoardsFilteredSearch) {
initBoardsFilteredSearch(apolloProvider);
initBoardsFilteredSearch(apolloProvider, parseBoolean($boardApp.dataset.epicFeatureAvailable));
}
mountBoardApp($boardApp);
......
import Vue from 'vue';
import IssueBoardFilteredSearch from '~/boards/components/issue_board_filtered_search.vue';
import IssueBoardFilteredSearch from 'ee_else_ce/boards/components/issue_board_filtered_search.vue';
import store from '~/boards/stores';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { queryToObject } from '~/lib/utils/url_utility';
......
<script>
// This is a false violation of @gitlab/no-runtime-template-compiler, since it
// extends a valid Vue single file component.
/* eslint-disable @gitlab/no-runtime-template-compiler */
import IssueBoardFilteredSearchFoss from '~/boards/components/issue_board_filtered_search.vue';
import { BoardType } from '~/boards/constants';
import { __ } from '~/locale';
import EpicToken from '~/vue_shared/components/filtered_search_bar/tokens/epic_token.vue';
export default {
extends: IssueBoardFilteredSearchFoss,
i18n: {
...IssueBoardFilteredSearchFoss.i18n,
epic: __('Epic'),
},
computed: {
isGroupBoard() {
return this.boardType === BoardType.group;
},
epicsGroupPath() {
return this.isGroupBoard
? this.fullPath
: this.fullPath.slice(0, this.fullPath.lastIndexOf('/'));
},
tokens() {
const { epic } = this.$options.i18n;
return [
...this.tokensCE,
{
type: 'epic_id',
title: epic,
icon: 'epic',
token: EpicToken,
unique: true,
symbol: '&',
idProperty: 'id',
useIdValue: true,
fullPath: this.epicsGroupPath,
},
];
},
},
};
</script>
import { shallowMount } from '@vue/test-utils';
import BoardFilteredSearch from '~/boards/components/board_filtered_search.vue';
import IssueBoardFilteredSpec from 'ee/boards/components/issue_board_filtered_search.vue';
import issueBoardFilters from '~/boards/issue_board_filters';
import { mockTokens } from '../mock_data';
jest.mock('~/boards/issue_board_filters');
describe('IssueBoardFilter', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMount(IssueBoardFilteredSpec, {
propsData: { fullPath: 'gitlab-org', boardType: 'group' },
});
};
let fetchAuthorsSpy;
let fetchLabelsSpy;
beforeEach(() => {
fetchAuthorsSpy = jest.fn();
fetchLabelsSpy = jest.fn();
issueBoardFilters.mockReturnValue({
fetchAuthors: fetchAuthorsSpy,
fetchLabels: fetchLabelsSpy,
});
});
afterEach(() => {
wrapper.destroy();
});
describe('default', () => {
beforeEach(() => {
createComponent();
});
it('finds BoardFilteredSearch', () => {
expect(wrapper.find(BoardFilteredSearch).exists()).toBe(true);
});
it('passes the correct tokens to BoardFilteredSearch including epics', () => {
const tokens = mockTokens(fetchLabelsSpy, fetchAuthorsSpy, wrapper.vm.fetchMilestones);
expect(wrapper.find(BoardFilteredSearch).props('tokens')).toEqual(tokens);
});
});
});
import { GlFilteredSearchToken } from '@gitlab/ui';
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';
export const mockLabel = {
id: 'gid://gitlab/GroupLabel/121',
title: 'To Do',
......@@ -365,3 +374,86 @@ export const mockGroup2 = {
};
export const mockSubGroups = [mockGroup0, mockGroup1, mockGroup2];
export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [
{
icon: 'user',
title: __('Assignee'),
type: 'assignee_username',
operators: [
{ value: '=', description: 'is' },
{ value: '!=', description: 'is not' },
],
token: AuthorToken,
unique: true,
fetchAuthors,
preloadedAuthors: [],
},
{
icon: 'pencil',
title: __('Author'),
type: 'author_username',
operators: [
{ value: '=', description: 'is' },
{ value: '!=', description: 'is not' },
],
symbol: '@',
token: AuthorToken,
unique: true,
fetchAuthors,
preloadedAuthors: [],
},
{
icon: 'labels',
title: __('Label'),
type: 'label_name',
operators: [
{ value: '=', description: 'is' },
{ value: '!=', description: 'is not' },
],
token: LabelToken,
unique: false,
symbol: '~',
fetchLabels,
},
{
icon: 'clock',
title: __('Milestone'),
symbol: '%',
type: 'milestone_title',
token: MilestoneToken,
unique: true,
defaultMilestones: DEFAULT_MILESTONES_GRAPHQL,
fetchMilestones,
},
{
icon: 'issues',
title: __('Type'),
type: 'types',
operators: [{ value: '=', description: 'is' }],
token: GlFilteredSearchToken,
unique: true,
options: [
{ icon: 'issue-type-issue', value: 'ISSUE', title: 'Issue' },
{ icon: 'issue-type-incident', value: 'INCIDENT', title: 'Incident' },
],
},
{
icon: 'weight',
title: __('Weight'),
type: 'weight',
token: WeightToken,
unique: true,
},
{
type: 'epic_id',
icon: 'epic',
title: 'Epic',
unique: true,
symbol: '&',
token: EpicToken,
idProperty: 'id',
useIdValue: true,
fullPath: 'gitlab-org',
},
];
......@@ -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();
});
......
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