Commit 1cc19f7a authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Updated specs for filter bar

Updates the filter bar to add
the additional search tokens and updates
related tests
parent f1f5371a
<script> <script>
import { mapState } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { GlFilteredSearch } from '@gitlab/ui'; import { GlFilteredSearch } from '@gitlab/ui';
import { __ } from '~/locale';
import MilestoneToken from '../../shared/components/tokens/milestone_token.vue';
import LabelToken from '../../shared/components/tokens/label_token.vue';
import UserToken from '../../shared/components/tokens/user_token.vue';
export default { export default {
name: 'FilterBar', name: 'FilterBar',
...@@ -16,20 +20,114 @@ export default { ...@@ -16,20 +20,114 @@ export default {
}, },
data() { data() {
return { return {
searchTerms: [], value: [],
}; };
}, },
computed: { computed: {
...mapState('filters', ['milestonesPath', 'labelsPath']), ...mapState('filters', {
milestones: state => state.milestones.data,
milestonesLoading: state => state.milestones.isLoading,
labels: state => state.labels.data,
labelsLoading: state => state.labels.isLoading,
authors: state => state.authors.data,
authorsLoading: state => state.authors.isLoading,
assignees: state => state.assignees.data,
assigneesLoading: state => state.assignees.isLoading,
}),
availableTokens() {
return [
{
icon: 'clock',
title: __('Milestone'),
type: 'milestone',
token: MilestoneToken,
milestones: this.milestones,
unique: true,
symbol: '%',
isLoading: this.milestonesLoading,
operators: [{ value: '=', description: 'is', default: 'true' }],
},
{
icon: 'labels',
title: __('Label'),
type: 'labels',
token: LabelToken,
labels: this.labels,
unique: false,
symbol: '~',
isLoading: this.labelsLoading,
operators: [{ value: '=', description: 'is', default: 'true' }],
},
{
icon: 'pencil',
title: __('Author'),
type: 'author',
token: UserToken,
users: this.authors,
unique: true,
isLoading: this.authorsLoading,
operators: [{ value: '=', description: 'is', default: 'true' }],
},
{
icon: 'user',
title: __('Assignees'),
type: 'assignees',
token: UserToken,
users: this.assignees,
unique: false,
isLoading: this.assigneesLoading,
operators: [{ value: '=', description: 'is', default: 'true' }],
},
];
},
},
methods: {
...mapActions('filters', ['setFilters']),
processFilters(filters) {
return filters.reduce((acc, token) => {
const { type, value } = token;
const { operator } = value;
let tokenValue = value.data;
// remove wrapping double quotes which were added for token values that include spaces
if (
(tokenValue[0] === "'" && tokenValue[tokenValue.length - 1] === "'") ||
(tokenValue[0] === '"' && tokenValue[tokenValue.length - 1] === '"')
) {
tokenValue = tokenValue.slice(1, -1);
}
if (!acc[type]) {
acc[type] = [];
}
acc[type].push({ value: tokenValue, operator });
return acc;
}, {});
},
filteredSearchSubmit(filters) {
const { labels, milestone, author, assignees } = this.processFilters(filters);
this.setFilters({
selectedAuthor: author ? author[0].value : null,
selectedMilestone: milestone ? milestone[0].value : null,
selectedAssignees: assignees ? assignees.map(a => a.value) : [],
selectedLabels: labels ? labels.map(l => l.value) : [],
});
},
}, },
}; };
</script> </script>
<template> <template>
<gl-filtered-search <gl-filtered-search
v-model="value"
:disabled="disabled" :disabled="disabled"
:v-model="searchTerms"
:placeholder="__('Filter results')" :placeholder="__('Filter results')"
:clear-button-title="__('Clear')" :clear-button-title="__('Clear')"
:close-button-title="__('Close')" :close-button-title="__('Close')"
:available-tokens="availableTokens"
@submit="filteredSearchSubmit"
/> />
</template> </template>
...@@ -234,6 +234,9 @@ export const removeStage = ({ dispatch, state }, stageId) => { ...@@ -234,6 +234,9 @@ export const removeStage = ({ dispatch, state }, stageId) => {
.catch(error => dispatch('receiveRemoveStageError', error)); .catch(error => dispatch('receiveRemoveStageError', error));
}; };
export const setSelectedFilters = ({ commit }, filters) =>
commit(types.SET_SELECTED_FILTERS, filters);
export const initializeCycleAnalyticsSuccess = ({ commit }) => export const initializeCycleAnalyticsSuccess = ({ commit }) =>
commit(types.INITIALIZE_CYCLE_ANALYTICS_SUCCESS); commit(types.INITIALIZE_CYCLE_ANALYTICS_SUCCESS);
......
import * as types from './mutation_types'; import * as types from './mutation_types';
// eslint-disable-next-line import/prefer-default-export const appendExtension = path => (path.indexOf('.') > -1 ? path : `${path}.json`);
export const setPaths = ({ commit }, { milestonesPath = '', labelsPath = '' }) => { export const setPaths = ({ commit }, { milestonesPath = '', labelsPath = '' }) => {
commit(types.SET_MILESTONES_PATH, milestonesPath); commit(types.SET_MILESTONES_PATH, appendExtension(milestonesPath));
commit(types.SET_LABELS_PATH, labelsPath); commit(types.SET_LABELS_PATH, appendExtension(labelsPath));
};
export const setFilters = ({ dispatch, state }, params) => {
const { selectedLabels: labelNames = [], ...rest } = params;
const {
labels: { data: labelsList = [] },
} = state;
const selectedLabels = labelsList.filter(({ title }) => labelNames.includes(title));
const nextFilters = {
...rest,
selectedLabels,
};
return dispatch('setSelectedFilters', nextFilters, { root: true });
}; };
export default () => ({ export default () => ({
milestonesPath: '', milestonesPath: '',
labelsPath: '', labelsPath: '',
milestones: {
isLoading: false,
data: [],
},
labels: {
isLoading: false,
data: [],
},
authors: {
isLoading: false,
data: [],
},
assignees: {
isLoading: false,
data: [],
},
}); });
...@@ -4,6 +4,7 @@ export const SET_SELECTED_GROUP = 'SET_SELECTED_GROUP'; ...@@ -4,6 +4,7 @@ export const SET_SELECTED_GROUP = 'SET_SELECTED_GROUP';
export const SET_SELECTED_PROJECTS = 'SET_SELECTED_PROJECTS'; export const SET_SELECTED_PROJECTS = 'SET_SELECTED_PROJECTS';
export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE'; export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
export const SET_DATE_RANGE = 'SET_DATE_RANGE'; export const SET_DATE_RANGE = 'SET_DATE_RANGE';
export const SET_SELECTED_FILTERS = 'SET_SELECTED_FILTERS';
export const REQUEST_CYCLE_ANALYTICS_DATA = 'REQUEST_CYCLE_ANALYTICS_DATA'; export const REQUEST_CYCLE_ANALYTICS_DATA = 'REQUEST_CYCLE_ANALYTICS_DATA';
export const RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS = 'RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS'; export const RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS = 'RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS';
......
...@@ -115,4 +115,5 @@ export default { ...@@ -115,4 +115,5 @@ export default {
state.isSavingStageOrder = false; state.isSavingStageOrder = false;
state.errorSavingStageOrder = true; state.errorSavingStageOrder = true;
}, },
[types.SET_SELECTED_FILTERS]() {},
}; };
...@@ -37,9 +37,6 @@ export default { ...@@ -37,9 +37,6 @@ export default {
value: this.getEscapedText(label.title), value: this.getEscapedText(label.title),
})); }));
}, },
hideDefault() {
return this.config?.hideDefaultOptions;
},
}, },
methods: { methods: {
getEscapedText(text) { getEscapedText(text) {
...@@ -80,15 +77,13 @@ export default { ...@@ -80,15 +77,13 @@ export default {
<template #suggestions> <template #suggestions>
<gl-loading-icon v-if="config.isLoading" /> <gl-loading-icon v-if="config.isLoading" />
<template v-else> <template v-else>
<div v-if="!hideDefault"> <gl-filtered-search-suggestion
<gl-filtered-search-suggestion v-for="suggestion in $options.defaultSuggestions"
v-for="suggestion in $options.defaultSuggestions" :key="suggestion.value"
:key="suggestion.value" :value="suggestion.value"
:value="suggestion.value" >{{ suggestion.text }}</gl-filtered-search-suggestion
>{{ suggestion.text }}</gl-filtered-search-suggestion >
> <gl-dropdown-divider v-if="config.isLoading || filteredLabels.length" />
<gl-dropdown-divider v-if="config.isLoading || filteredLabels.length" />
</div>
<gl-filtered-search-suggestion <gl-filtered-search-suggestion
v-for="label in filteredLabels" v-for="label in filteredLabels"
ref="labelItem" ref="labelItem"
......
...@@ -39,7 +39,7 @@ export default { ...@@ -39,7 +39,7 @@ export default {
}, },
}, },
methods: { methods: {
getEscapedText(text = null) { getEscapedText(text) {
let escapedText = text; let escapedText = text;
const hasSpace = text.indexOf(' ') !== -1; const hasSpace = text.indexOf(' ') !== -1;
const hasDoubleQuote = text.indexOf('"') !== -1; const hasDoubleQuote = text.indexOf('"') !== -1;
......
...@@ -4,8 +4,7 @@ import testAction from 'helpers/vuex_action_helper'; ...@@ -4,8 +4,7 @@ import testAction from 'helpers/vuex_action_helper';
import * as actions from 'ee/analytics/cycle_analytics/store/modules/filters/actions'; import * as actions from 'ee/analytics/cycle_analytics/store/modules/filters/actions';
import * as types from 'ee/analytics/cycle_analytics/store/modules/filters/mutation_types'; import * as types from 'ee/analytics/cycle_analytics/store/modules/filters/mutation_types';
import initialState from 'ee/analytics/cycle_analytics/store/modules/filters/state'; import initialState from 'ee/analytics/cycle_analytics/store/modules/filters/state';
import createFlash from '~/flash'; import { filterLabels } from '../../../mock_data';
import { filterMilestones, filterUsers, filterLabels } from '../../../mock_data';
const milestonesPath = 'fake_milestones_path'; const milestonesPath = 'fake_milestones_path';
const labelsPath = 'fake_labels_path'; const labelsPath = 'fake_labels_path';
......
import mutations from 'ee/analytics/cycle_analytics/store/modules/filters/mutations'; import mutations from 'ee/analytics/cycle_analytics/store/modules/filters/mutations';
import * as types from 'ee/analytics/cycle_analytics/store/modules/filters/mutation_types'; import * as types from 'ee/analytics/cycle_analytics/store/modules/filters/mutation_types';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { filterMilestones, filterUsers, filterLabels } from '../../../mock_data';
let state = null; let state = null;
const milestones = filterMilestones.map(convertObjectPropsToCamelCase);
const users = filterUsers.map(convertObjectPropsToCamelCase);
const labels = filterLabels.map(convertObjectPropsToCamelCase);
describe('Filters mutations', () => { describe('Filters mutations', () => {
beforeEach(() => { beforeEach(() => {
state = { initialTokens: {}, milestones: {}, authors: {}, labels: {}, assignees: {} }; state = { initialTokens: {}, milestones: {}, authors: {}, labels: {}, assignees: {} };
......
...@@ -56,23 +56,6 @@ describe('LabelToken', () => { ...@@ -56,23 +56,6 @@ describe('LabelToken', () => {
}); });
}); });
describe('hideDefaultOptions = true', () => {
it('does not render defualt suggestions', () => {
createComponent(
{
config: {
...defaultConfig,
hideDefaultOptions: true,
},
},
{ stubs },
);
const html = wrapper.html();
expect(html).not.toContain('None');
expect(html).not.toContain('Any');
});
});
describe('when no search term is given', () => { describe('when no search term is given', () => {
it('renders two label suggestions', () => { it('renders two label suggestions', () => {
createComponent(null, { stubs }); createComponent(null, { stubs });
......
...@@ -35,37 +35,37 @@ export const mockLabels = [ ...@@ -35,37 +35,37 @@ export const mockLabels = [
]; ];
export const mockUsers = [ export const mockUsers = [
{ {
id: 31, id: 31,
name: 'VSM User2', name: 'VSM User2',
username: 'vsm-user-2-1589776313', username: 'vsm-user-2-1589776313',
state: 'active', state: 'active',
avatar_url: avatar_url:
'https://www.gravatar.com/avatar/762398957a8c6e04eed16da88098899d?s=80\u0026d=identicon', 'https://www.gravatar.com/avatar/762398957a8c6e04eed16da88098899d?s=80\u0026d=identicon',
web_url: 'http://127.0.0.1:3001/vsm-user-2-1589776313', web_url: 'http://127.0.0.1:3001/vsm-user-2-1589776313',
access_level: 30, access_level: 30,
expires_at: null, expires_at: null,
}, },
{ {
id: 32, id: 32,
name: 'VSM User3', name: 'VSM User3',
username: 'vsm-user-3-1589776313', username: 'vsm-user-3-1589776313',
state: 'active', state: 'active',
avatar_url: avatar_url:
'https://www.gravatar.com/avatar/f78932237e8a5c5376b65a709824802f?s=80\u0026d=identicon', 'https://www.gravatar.com/avatar/f78932237e8a5c5376b65a709824802f?s=80\u0026d=identicon',
web_url: 'http://127.0.0.1:3001/vsm-user-3-1589776313', web_url: 'http://127.0.0.1:3001/vsm-user-3-1589776313',
access_level: 30, access_level: 30,
expires_at: null, expires_at: null,
}, },
{ {
id: 33, id: 33,
name: 'VSM User4', name: 'VSM User4',
username: 'vsm-user-4-1589776313', username: 'vsm-user-4-1589776313',
state: 'active', state: 'active',
avatar_url: avatar_url:
'https://www.gravatar.com/avatar/ab506dc600d1a941e4d77d5ceeeba73f?s=80\u0026d=identicon', 'https://www.gravatar.com/avatar/ab506dc600d1a941e4d77d5ceeeba73f?s=80\u0026d=identicon',
web_url: 'http://127.0.0.1:3001/vsm-user-4-1589776313', web_url: 'http://127.0.0.1:3001/vsm-user-4-1589776313',
access_level: 30, access_level: 30,
expires_at: null, expires_at: null,
}, },
]; ];
...@@ -3002,6 +3002,9 @@ msgstr "" ...@@ -3002,6 +3002,9 @@ msgstr ""
msgid "Assignee(s)" msgid "Assignee(s)"
msgstr "" msgstr ""
msgid "Assignees"
msgstr ""
msgid "Assigns %{assignee_users_sentence}." msgid "Assigns %{assignee_users_sentence}."
msgstr "" msgstr ""
......
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