Commit 15d4fd73 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'ss/fix-board-scope-issue' into 'master'

Fix board scope issue when issue_boards_filtered_search is enabled

See merge request gitlab-org/gitlab!73008
parents a70561c0 77772099
<script>
import { pickBy } from 'lodash';
import { pickBy, isEmpty } from 'lodash';
import { mapActions } from 'vuex';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
......@@ -20,6 +20,11 @@ export default {
type: Array,
required: true,
},
eeFilters: {
required: false,
type: Object,
default: () => ({}),
},
},
data() {
return {
......@@ -27,61 +32,6 @@ export default {
};
},
computed: {
urlParams() {
const {
authorUsername,
labelName,
assigneeUsername,
search,
milestoneTitle,
types,
weight,
epicId,
} = 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,
'not[assignee_username]': this.filterParams.not.assigneeUsername,
'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,
);
}
return {
...notParams,
author_username: authorUsername,
'label_name[]': labelName,
assignee_username: assigneeUsername,
milestone_title: milestoneTitle,
search,
types,
weight,
epic_id: getIdFromGraphQLId(epicId),
};
},
},
methods: {
...mapActions(['performSearch']),
handleFilter(filters) {
this.filterParams = this.getFilterParams(filters);
updateHistory({
url: setUrlParams(this.urlParams, window.location.href, true, false, true),
title: document.title,
replace: true,
});
this.performSearch();
},
getFilteredSearchValue() {
const {
authorUsername,
......@@ -203,6 +153,66 @@ export default {
return filteredSearchValue;
},
urlParams() {
const {
authorUsername,
labelName,
assigneeUsername,
search,
milestoneTitle,
types,
weight,
epicId,
} = 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,
'not[assignee_username]': this.filterParams.not.assigneeUsername,
'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,
);
}
return {
...notParams,
author_username: authorUsername,
'label_name[]': labelName,
assignee_username: assigneeUsername,
milestone_title: milestoneTitle,
search,
types,
weight,
epic_id: getIdFromGraphQLId(epicId),
};
},
},
created() {
if (!isEmpty(this.eeFilters)) {
this.filterParams = this.eeFilters;
}
},
methods: {
...mapActions(['performSearch']),
handleFilter(filters) {
this.filterParams = this.getFilterParams(filters);
updateHistory({
url: setUrlParams(this.urlParams, window.location.href, true, false, true),
title: document.title,
replace: true,
});
this.performSearch();
},
getFilterParams(filters = []) {
const notFilters = filters.filter((item) => item.value.operator === '!=');
const equalsFilters = filters.filter(
......@@ -266,7 +276,7 @@ export default {
namespace=""
:tokens="tokens"
:search-input-placeholder="$options.i18n.search"
:initial-filter-value="getFilteredSearchValue()"
:initial-filter-value="getFilteredSearchValue"
@onFilter="handleFilter"
/>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { isEmpty } from 'lodash';
import BoardFilteredSearchCe from '~/boards/components/board_filtered_search.vue';
import { transformBoardConfig } from 'ee/boards/boards_util';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { updateHistory, queryToObject } from '~/lib/utils/url_utility';
export default {
components: { BoardFilteredSearchCe },
......@@ -9,9 +14,51 @@ export default {
type: Array,
},
},
data() {
return {
filterParams: {},
resetFilters: false,
};
},
computed: {
...mapState({ boardScopeConfig: ({ boardConfig }) => boardConfig }),
shouldRenderComponent() {
return this.resetFilters || !isEmpty(this.boardScopeConfig);
},
},
watch: {
boardScopeConfig(newVal) {
if (!isEmpty(newVal)) {
const boardConfigPath = transformBoardConfig(newVal);
if (boardConfigPath !== '') {
const filterPath = window.location.search ? `${window.location.search}&` : '?';
updateHistory({
url: `${filterPath}${transformBoardConfig(newVal)}`,
});
this.performSearch();
const rawFilterParams = queryToObject(window.location.search, { gatherArrays: true });
this.filterParams = {
...convertObjectPropsToCamelCase(rawFilterParams, {}),
};
this.resetFilters = true;
}
}
},
},
methods: {
...mapActions(['performSearch']),
},
};
</script>
<template>
<board-filtered-search-ce v-bind="{ ...$props, ...$attrs }" />
<board-filtered-search-ce
v-if="shouldRenderComponent"
:ee-filters="filterParams"
v-bind="{ ...$props, ...$attrs }"
/>
</template>
......@@ -3,7 +3,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import initFilteredSearch from 'ee/boards/epic_filtered_search';
import { fullEpicBoardId, transformBoardConfig } from 'ee_component/boards/boards_util';
import { fullEpicBoardId } from 'ee_component/boards/boards_util';
import toggleLabels from 'ee_component/boards/toggle_labels';
import BoardAddNewColumnTrigger from '~/boards/components/board_add_new_column_trigger.vue';
......@@ -16,7 +16,6 @@ import createDefaultClient from '~/lib/graphql';
import '~/boards/filters/due_date_filters';
import { NavigationType, parseBoolean } from '~/lib/utils/common_utils';
import { updateHistory } from '~/lib/utils/url_utility';
import introspectionQueryResultData from '~/sidebar/fragmentTypes.json';
Vue.use(VueApollo);
......@@ -60,15 +59,6 @@ function mountBoardApp(el) {
},
});
const boardConfigPath = transformBoardConfig(store.state.boardConfig);
if (boardConfigPath !== '') {
const filterPath = window.location.search ? `${window.location.search}&` : '?';
updateHistory({
url: `${filterPath}${transformBoardConfig(store.state.boardConfig)}`,
});
}
// eslint-disable-next-line no-new
new Vue({
el,
......
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import BoardFilteredSearch from 'ee/boards/components/board_filtered_search.vue';
import BoardFilteredSearchCe from '~/boards/components/board_filtered_search.vue';
import { createStore } from '~/boards/stores';
import * as urlUtility from '~/lib/utils/url_utility';
Vue.use(Vuex);
describe('ee/BoardFilteredSearch', () => {
let wrapper;
let store;
const createComponent = () => {
wrapper = shallowMount(BoardFilteredSearch, { propsData: { tokens: [] } });
wrapper = shallowMount(BoardFilteredSearch, {
store,
propsData: { tokens: [] },
});
};
const findFilteredSearch = () => wrapper.findComponent(BoardFilteredSearchCe);
......@@ -15,12 +25,55 @@ describe('ee/BoardFilteredSearch', () => {
wrapper.destroy();
});
describe('default', () => {
describe('when boardScopeConfig watcher is triggered', () => {
beforeEach(async () => {
store = createStore();
createComponent();
jest.spyOn(store, 'dispatch').mockImplementation();
jest.spyOn(urlUtility, 'updateHistory');
store.state.boardConfig = { labels: [{ title: 'test', color: 'black', id: '1' }] };
await wrapper.vm.$nextTick();
});
it('calls performSearch', () => {
expect(store.dispatch).toHaveBeenCalledWith('performSearch');
});
it('calls historyPushState', () => {
expect(urlUtility.updateHistory).toHaveBeenCalledWith({
url: '?label_name[]=test',
});
});
it('passes the correct props to BoardFilteredSearchCe', () => {
expect(findFilteredSearch().props()).toEqual(
expect.objectContaining({ eeFilters: { labelName: ['test'] } }),
);
});
});
describe('when resetFilters is true and boardConfig is not empty', () => {
beforeEach(() => {
store = createStore();
createComponent();
});
it('renders FilteredSearch', () => {
it('renders BoardFilteredSearchCe', async () => {
store.state.boardConfig = {};
await wrapper.vm.$nextTick();
expect(findFilteredSearch().exists()).toEqual(false);
store.state.boardConfig = { labels: [] };
await wrapper.vm.$nextTick();
expect(findFilteredSearch().exists()).toBe(true);
});
});
......
......@@ -7,6 +7,7 @@ import { __ } from '~/locale';
import FilteredSearchBarRoot from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import AuthorToken from '~/vue_shared/components/filtered_search_bar/tokens/author_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
import { createStore } from '~/boards/stores';
Vue.use(Vuex);
......@@ -42,17 +43,13 @@ describe('BoardFilteredSearch', () => {
},
];
const createComponent = ({ initialFilterParams = {} } = {}) => {
store = new Vuex.Store({
actions: {
performSearch: jest.fn(),
},
});
const createComponent = ({ initialFilterParams = {}, props = {} } = {}) => {
store = createStore();
wrapper = shallowMount(BoardFilteredSearch, {
provide: { initialFilterParams, fullPath: '' },
store,
propsData: {
...props,
tokens,
},
});
......@@ -68,11 +65,7 @@ describe('BoardFilteredSearch', () => {
beforeEach(() => {
createComponent();
jest.spyOn(store, 'dispatch');
});
it('renders FilteredSearch', () => {
expect(findFilteredSearch().exists()).toBe(true);
jest.spyOn(store, 'dispatch').mockImplementation();
});
it('passes the correct tokens to FilteredSearch', () => {
......@@ -99,6 +92,22 @@ describe('BoardFilteredSearch', () => {
});
});
describe('when eeFilters is not empty', () => {
it('passes the correct initialFilterValue to FitleredSearchBarRoot', () => {
createComponent({ props: { eeFilters: { labelName: ['label'] } } });
expect(findFilteredSearch().props('initialFilterValue')).toEqual([
{ type: 'label_name', value: { data: 'label', operator: '=' } },
]);
});
});
it('renders FilteredSearch', () => {
createComponent();
expect(findFilteredSearch().exists()).toBe(true);
});
describe('when searching', () => {
beforeEach(() => {
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