Commit b3211781 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '341426-update-global-search-apply-button' into 'master'

Global Search - Update Apply Button

See merge request gitlab-org/gitlab!72531
parents b647ffc5 4241fb40
...@@ -13,7 +13,7 @@ export default { ...@@ -13,7 +13,7 @@ export default {
ConfidentialityFilter, ConfidentialityFilter,
}, },
computed: { computed: {
...mapState(['query']), ...mapState(['query', 'sidebarDirty']),
showReset() { showReset() {
return this.query.state || this.query.confidential; return this.query.state || this.query.confidential;
}, },
...@@ -32,7 +32,7 @@ export default { ...@@ -32,7 +32,7 @@ export default {
<status-filter /> <status-filter />
<confidentiality-filter /> <confidentiality-filter />
<div class="gl-display-flex gl-align-items-center gl-mt-3"> <div class="gl-display-flex gl-align-items-center gl-mt-3">
<gl-button category="primary" variant="confirm" size="small" type="submit"> <gl-button category="primary" variant="confirm" type="submit" :disabled="!sidebarDirty">
{{ __('Apply') }} {{ __('Apply') }}
</gl-button> </gl-button>
<gl-link v-if="showReset" class="gl-ml-auto" @click="resetQuery">{{ <gl-link v-if="showReset" class="gl-ml-auto" @click="resetQuery">{{
......
...@@ -5,7 +5,7 @@ const header = __('Status'); ...@@ -5,7 +5,7 @@ const header = __('Status');
const filters = { const filters = {
ANY: { ANY: {
label: __('Any'), label: __('Any'),
value: 'all', value: null,
}, },
OPEN: { OPEN: {
label: __('Open'), label: __('Open'),
......
...@@ -2,9 +2,9 @@ import Api from '~/api'; ...@@ -2,9 +2,9 @@ import Api from '~/api';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants'; import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY, SIDEBAR_PARAMS } from './constants';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { loadDataFromLS, setFrequentItemToLS, mergeById } from './utils'; import { loadDataFromLS, setFrequentItemToLS, mergeById, isSidebarDirty } from './utils';
export const fetchGroups = ({ commit }, search) => { export const fetchGroups = ({ commit }, search) => {
commit(types.REQUEST_GROUPS); commit(types.REQUEST_GROUPS);
...@@ -86,8 +86,12 @@ export const setFrequentProject = ({ state, commit }, item) => { ...@@ -86,8 +86,12 @@ export const setFrequentProject = ({ state, commit }, item) => {
commit(types.LOAD_FREQUENT_ITEMS, { key: PROJECTS_LOCAL_STORAGE_KEY, data: frequentItems }); commit(types.LOAD_FREQUENT_ITEMS, { key: PROJECTS_LOCAL_STORAGE_KEY, data: frequentItems });
}; };
export const setQuery = ({ commit }, { key, value }) => { export const setQuery = ({ state, commit }, { key, value }) => {
commit(types.SET_QUERY, { key, value }); commit(types.SET_QUERY, { key, value });
if (SIDEBAR_PARAMS.includes(key)) {
commit(types.SET_SIDEBAR_DIRTY, isSidebarDirty(state.query, state.urlQuery));
}
}; };
export const applyQuery = ({ state }) => { export const applyQuery = ({ state }) => {
......
import { stateFilterData } from '~/search/sidebar/constants/state_filter_data';
import { confidentialFilterData } from '~/search/sidebar/constants/confidential_filter_data';
export const MAX_FREQUENT_ITEMS = 5; export const MAX_FREQUENT_ITEMS = 5;
export const MAX_FREQUENCY = 5; export const MAX_FREQUENCY = 5;
...@@ -5,3 +8,5 @@ export const MAX_FREQUENCY = 5; ...@@ -5,3 +8,5 @@ export const MAX_FREQUENCY = 5;
export const GROUPS_LOCAL_STORAGE_KEY = 'global-search-frequent-groups'; export const GROUPS_LOCAL_STORAGE_KEY = 'global-search-frequent-groups';
export const PROJECTS_LOCAL_STORAGE_KEY = 'global-search-frequent-projects'; export const PROJECTS_LOCAL_STORAGE_KEY = 'global-search-frequent-projects';
export const SIDEBAR_PARAMS = [stateFilterData.filterParam, confidentialFilterData.filterParam];
...@@ -7,5 +7,6 @@ export const RECEIVE_PROJECTS_SUCCESS = 'RECEIVE_PROJECTS_SUCCESS'; ...@@ -7,5 +7,6 @@ export const RECEIVE_PROJECTS_SUCCESS = 'RECEIVE_PROJECTS_SUCCESS';
export const RECEIVE_PROJECTS_ERROR = 'RECEIVE_PROJECTS_ERROR'; export const RECEIVE_PROJECTS_ERROR = 'RECEIVE_PROJECTS_ERROR';
export const SET_QUERY = 'SET_QUERY'; export const SET_QUERY = 'SET_QUERY';
export const SET_SIDEBAR_DIRTY = 'SET_SIDEBAR_DIRTY';
export const LOAD_FREQUENT_ITEMS = 'LOAD_FREQUENT_ITEMS'; export const LOAD_FREQUENT_ITEMS = 'LOAD_FREQUENT_ITEMS';
...@@ -26,6 +26,9 @@ export default { ...@@ -26,6 +26,9 @@ export default {
[types.SET_QUERY](state, { key, value }) { [types.SET_QUERY](state, { key, value }) {
state.query[key] = value; state.query[key] = value;
}, },
[types.SET_SIDEBAR_DIRTY](state, value) {
state.sidebarDirty = value;
},
[types.LOAD_FREQUENT_ITEMS](state, { key, data }) { [types.LOAD_FREQUENT_ITEMS](state, { key, data }) {
state.frequentItems[key] = data; state.frequentItems[key] = data;
}, },
......
import { cloneDeep } from 'lodash';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants'; import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants';
const createState = ({ query }) => ({ const createState = ({ query }) => ({
urlQuery: cloneDeep(query),
query, query,
groups: [], groups: [],
fetchingGroups: false, fetchingGroups: false,
...@@ -10,5 +12,6 @@ const createState = ({ query }) => ({ ...@@ -10,5 +12,6 @@ const createState = ({ query }) => ({
[GROUPS_LOCAL_STORAGE_KEY]: [], [GROUPS_LOCAL_STORAGE_KEY]: [],
[PROJECTS_LOCAL_STORAGE_KEY]: [], [PROJECTS_LOCAL_STORAGE_KEY]: [],
}, },
sidebarDirty: false,
}); });
export default createState; export default createState;
import AccessorUtilities from '../../lib/utils/accessor'; import AccessorUtilities from '../../lib/utils/accessor';
import { MAX_FREQUENT_ITEMS, MAX_FREQUENCY } from './constants'; import { MAX_FREQUENT_ITEMS, MAX_FREQUENCY, SIDEBAR_PARAMS } from './constants';
function extractKeys(object, keyList) { function extractKeys(object, keyList) {
return Object.fromEntries(keyList.map((key) => [key, object[key]])); return Object.fromEntries(keyList.map((key) => [key, object[key]]));
...@@ -80,3 +80,13 @@ export const mergeById = (inflatedData, storedData) => { ...@@ -80,3 +80,13 @@ export const mergeById = (inflatedData, storedData) => {
return { ...stored, ...data }; return { ...stored, ...data };
}); });
}; };
export const isSidebarDirty = (currentQuery, urlQuery) => {
return SIDEBAR_PARAMS.some((param) => {
// userAddParam ensures we don't get a false dirty from null !== undefined
const userAddedParam = !urlQuery[param] && currentQuery[param];
const userChangedExistingParam = urlQuery[param] && urlQuery[param] !== currentQuery[param];
return userAddedParam || userChangedExistingParam;
});
};
import { GlButton, GlLink } from '@gitlab/ui'; import { GlButton, GlLink } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { MOCK_QUERY } from 'jest/search/mock_data'; import { MOCK_QUERY } from 'jest/search/mock_data';
import GlobalSearchSidebar from '~/search/sidebar/components/app.vue'; import GlobalSearchSidebar from '~/search/sidebar/components/app.vue';
import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue'; import ConfidentialityFilter from '~/search/sidebar/components/confidentiality_filter.vue';
import StatusFilter from '~/search/sidebar/components/status_filter.vue'; import StatusFilter from '~/search/sidebar/components/status_filter.vue';
const localVue = createLocalVue(); Vue.use(Vuex);
localVue.use(Vuex);
describe('GlobalSearchSidebar', () => { describe('GlobalSearchSidebar', () => {
let wrapper; let wrapper;
...@@ -27,21 +27,19 @@ describe('GlobalSearchSidebar', () => { ...@@ -27,21 +27,19 @@ describe('GlobalSearchSidebar', () => {
}); });
wrapper = shallowMount(GlobalSearchSidebar, { wrapper = shallowMount(GlobalSearchSidebar, {
localVue,
store, store,
}); });
}; };
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
const findSidebarForm = () => wrapper.find('form'); const findSidebarForm = () => wrapper.find('form');
const findStatusFilter = () => wrapper.find(StatusFilter); const findStatusFilter = () => wrapper.findComponent(StatusFilter);
const findConfidentialityFilter = () => wrapper.find(ConfidentialityFilter); const findConfidentialityFilter = () => wrapper.findComponent(ConfidentialityFilter);
const findApplyButton = () => wrapper.find(GlButton); const findApplyButton = () => wrapper.findComponent(GlButton);
const findResetLinkButton = () => wrapper.find(GlLink); const findResetLinkButton = () => wrapper.findComponent(GlLink);
describe('template', () => { describe('template', () => {
beforeEach(() => { beforeEach(() => {
...@@ -61,6 +59,28 @@ describe('GlobalSearchSidebar', () => { ...@@ -61,6 +59,28 @@ describe('GlobalSearchSidebar', () => {
}); });
}); });
describe('ApplyButton', () => {
describe('when sidebarDirty is false', () => {
beforeEach(() => {
createComponent({ sidebarDirty: false });
});
it('disables the button', () => {
expect(findApplyButton().attributes('disabled')).toBe('true');
});
});
describe('when sidebarDirty is true', () => {
beforeEach(() => {
createComponent({ sidebarDirty: true });
});
it('enables the button', () => {
expect(findApplyButton().attributes('disabled')).toBe(undefined);
});
});
});
describe('ResetLinkButton', () => { describe('ResetLinkButton', () => {
describe('with no filter selected', () => { describe('with no filter selected', () => {
beforeEach(() => { beforeEach(() => {
......
...@@ -5,7 +5,11 @@ import createFlash from '~/flash'; ...@@ -5,7 +5,11 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import * as urlUtils from '~/lib/utils/url_utility'; import * as urlUtils from '~/lib/utils/url_utility';
import * as actions from '~/search/store/actions'; import * as actions from '~/search/store/actions';
import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from '~/search/store/constants'; import {
GROUPS_LOCAL_STORAGE_KEY,
PROJECTS_LOCAL_STORAGE_KEY,
SIDEBAR_PARAMS,
} from '~/search/store/constants';
import * as types from '~/search/store/mutation_types'; import * as types from '~/search/store/mutation_types';
import createState from '~/search/store/state'; import createState from '~/search/store/state';
import * as storeUtils from '~/search/store/utils'; import * as storeUtils from '~/search/store/utils';
...@@ -153,15 +157,24 @@ describe('Global Search Store Actions', () => { ...@@ -153,15 +157,24 @@ describe('Global Search Store Actions', () => {
}); });
}); });
describe('setQuery', () => { describe.each`
const payload = { key: 'key1', value: 'value1' }; payload | isDirty | isDirtyMutation
${{ key: SIDEBAR_PARAMS[0], value: 'test' }} | ${false} | ${[{ type: types.SET_SIDEBAR_DIRTY, payload: false }]}
${{ key: SIDEBAR_PARAMS[0], value: 'test' }} | ${true} | ${[{ type: types.SET_SIDEBAR_DIRTY, payload: true }]}
${{ key: SIDEBAR_PARAMS[1], value: 'test' }} | ${false} | ${[{ type: types.SET_SIDEBAR_DIRTY, payload: false }]}
${{ key: SIDEBAR_PARAMS[1], value: 'test' }} | ${true} | ${[{ type: types.SET_SIDEBAR_DIRTY, payload: true }]}
${{ key: 'non-sidebar', value: 'test' }} | ${false} | ${[]}
${{ key: 'non-sidebar', value: 'test' }} | ${true} | ${[]}
`('setQuery', ({ payload, isDirty, isDirtyMutation }) => {
describe(`when filter param is ${payload.key} and utils.isSidebarDirty returns ${isDirty}`, () => {
const expectedMutations = [{ type: types.SET_QUERY, payload }].concat(isDirtyMutation);
it('calls the SET_QUERY mutation', () => { beforeEach(() => {
return testAction({ storeUtils.isSidebarDirty = jest.fn().mockReturnValue(isDirty);
action: actions.setQuery, });
payload,
state, it(`should dispatch the correct mutations`, () => {
expectedMutations: [{ type: types.SET_QUERY, payload }], return testAction({ action: actions.setQuery, payload, state, expectedMutations });
}); });
}); });
}); });
......
...@@ -72,6 +72,16 @@ describe('Global Search Store Mutations', () => { ...@@ -72,6 +72,16 @@ describe('Global Search Store Mutations', () => {
}); });
}); });
describe('SET_SIDEBAR_DIRTY', () => {
const value = true;
it('sets sidebarDirty to the value', () => {
mutations[types.SET_SIDEBAR_DIRTY](state, value);
expect(state.sidebarDirty).toBe(value);
});
});
describe('LOAD_FREQUENT_ITEMS', () => { describe('LOAD_FREQUENT_ITEMS', () => {
it('sets frequentItems[key] to data', () => { it('sets frequentItems[key] to data', () => {
const payload = { key: 'test-key', data: [1, 2, 3] }; const payload = { key: 'test-key', data: [1, 2, 3] };
......
import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { MAX_FREQUENCY } from '~/search/store/constants'; import { MAX_FREQUENCY, SIDEBAR_PARAMS } from '~/search/store/constants';
import { loadDataFromLS, setFrequentItemToLS, mergeById } from '~/search/store/utils'; import {
loadDataFromLS,
setFrequentItemToLS,
mergeById,
isSidebarDirty,
} from '~/search/store/utils';
import { import {
MOCK_LS_KEY, MOCK_LS_KEY,
MOCK_GROUPS, MOCK_GROUPS,
...@@ -216,4 +221,24 @@ describe('Global Search Store Utils', () => { ...@@ -216,4 +221,24 @@ describe('Global Search Store Utils', () => {
}); });
}); });
}); });
describe.each`
description | currentQuery | urlQuery | isDirty
${'identical'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default' }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default' }} | ${false}
${'different'} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'new' }} | ${{ [SIDEBAR_PARAMS[0]]: 'default', [SIDEBAR_PARAMS[1]]: 'default' }} | ${true}
${'null/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: null, [SIDEBAR_PARAMS[1]]: null }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined }} | ${false}
${'updated/undefined'} | ${{ [SIDEBAR_PARAMS[0]]: 'new', [SIDEBAR_PARAMS[1]]: 'new' }} | ${{ [SIDEBAR_PARAMS[0]]: undefined, [SIDEBAR_PARAMS[1]]: undefined }} | ${true}
`('isSidebarDirty', ({ description, currentQuery, urlQuery, isDirty }) => {
describe(`with ${description} sidebar query data`, () => {
let res;
beforeEach(() => {
res = isSidebarDirty(currentQuery, urlQuery);
});
it(`returns ${isDirty}`, () => {
expect(res).toStrictEqual(isDirty);
});
});
});
}); });
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