Commit 6c98c4c1 authored by mlunoe's avatar mlunoe

Feat(Analytics filter module): lists + values

Handle both lists and individual values for each
filter property with a different key, to allow
users to use a single or multiple selections of
each filter token
parent 3c0f28c6
<script>
import { mapState, mapActions } from 'vuex';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { __ } from '~/locale';
import MilestoneToken from '../../shared/components/tokens/milestone_token.vue';
import LabelToken from '../../shared/components/tokens/label_token.vue';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import { processFilters } from '../../shared/utils';
export default {
......@@ -23,12 +23,12 @@ export default {
},
computed: {
...mapState('filters', {
milestonePath: 'milestonePath',
labelsPath: 'labelsPath',
milestones: state => state.milestones.data,
milestonesLoading: state => state.milestones.isLoading,
labels: state => state.labels.data,
labelsLoading: state => state.labels.isLoading,
selectedMilestone: state => state.milestones.selected,
selectedLabelList: state => state.labels.selectedList,
milestonesData: state => state.milestones.data,
labelsData: state => state.labels.data,
}),
tokens() {
return [
......@@ -37,7 +37,7 @@ export default {
title: __('Milestone'),
type: 'milestone',
token: MilestoneToken,
milestones: this.milestones,
milestones: this.milestonesData,
unique: true,
symbol: '%',
isLoading: this.milestonesLoading,
......@@ -48,7 +48,7 @@ export default {
title: __('Label'),
type: 'labels',
token: LabelToken,
labels: this.labels,
labels: this.labelsData,
unique: false,
symbol: '~',
isLoading: this.labelsLoading,
......@@ -62,13 +62,13 @@ export default {
this.fetchLabels();
},
methods: {
...mapActions('filters', ['fetchMilestones', 'fetchLabels', 'setFilters']),
...mapActions('filters', ['setFilters', 'fetchMilestones', 'fetchLabels']),
handleFilter(filters) {
const { labels, milestone } = processFilters(filters);
this.setFilters({
selectedMilestone: milestone ? milestone[0] : null,
selectedLabels: labels,
selectedLabelList: labels,
});
},
},
......
......@@ -12,7 +12,7 @@ export const fetchMergeRequests = ({ commit, state, rootState }) => {
const { projectId, pageInfo } = state;
const { selected: milestoneTitle } = rootState.filters.milestones;
const { selected: labelNames } = rootState.filters.labels;
const { selectedList: labelNames } = rootState.filters.labels;
const params = {
project_id: projectId,
......
......@@ -28,8 +28,8 @@ export default {
...mapState('filters', {
selectedMilestone: state => state.milestones.selected,
selectedAuthor: state => state.authors.selected,
selectedLabels: state => state.labels.selected,
selectedAssignees: state => state.assignees.selected,
selectedLabelList: state => state.labels.selectedList,
selectedAssigneeList: state => state.assignees.selectedList,
milestonesData: state => state.milestones.data,
labelsData: state => state.labels.data,
authorsData: state => state.authors.data,
......@@ -83,14 +83,16 @@ export default {
];
},
query() {
const selectedLabels = this.selectedLabels?.length ? this.selectedLabels : null;
const selectedAssignees = this.selectedAssignees?.length ? this.selectedAssignees : null;
const selectedLabelList = this.selectedLabelList?.length ? this.selectedLabelList : null;
const selectedAssigneeList = this.selectedAssigneeList?.length
? this.selectedAssigneeList
: null;
return {
milestone_title: this.selectedMilestone,
author_username: this.selectedAuthor,
label_name: selectedLabels,
assignee_username: selectedAssignees,
label_name: selectedLabelList,
assignee_username: selectedAssigneeList,
};
},
},
......@@ -106,8 +108,8 @@ export default {
const {
selectedMilestone: milestone = null,
selectedAuthor: author = null,
selectedAssignees: assignees = [],
selectedLabels: labels = [],
selectedAssigneeList: assignees = [],
selectedLabelList: labels = [],
} = this;
return prepareTokens({ milestone, author, assignees, labels });
},
......@@ -117,8 +119,8 @@ export default {
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) : [],
selectedAssigneeList: assignees ? assignees.map(a => a.value) : [],
selectedLabelList: labels ? labels.map(l => l.value) : [],
});
},
},
......
......@@ -22,8 +22,8 @@ export const cycleAnalyticsRequestParams = (state, getters) => {
filters: {
authors: { selected: selectedAuthor = null },
milestones: { selected: selectedMilestone = null },
assignees: { selected: selectedAssignees = [] },
labels: { selected: selectedLabels = [] },
assignees: { selectedList: selectedAssigneeList = [] },
labels: { selectedList: selectedLabelList = [] },
},
} = state;
......@@ -33,8 +33,8 @@ export const cycleAnalyticsRequestParams = (state, getters) => {
created_before: endDate ? dateFormat(endDate, dateFormats.isoDate) : null,
author_username: selectedAuthor,
milestone_title: selectedMilestone,
assignee_username: selectedAssignees,
label_name: selectedLabels,
assignee_username: selectedAssigneeList,
label_name: selectedLabelList,
};
};
......
......@@ -4,14 +4,22 @@ export default {
[types.SET_SELECTED_FILTERS](state, params) {
const {
selectedAuthor = null,
selectedAuthorList = [],
selectedMilestone = null,
selectedAssignees = [],
selectedLabels = [],
selectedMilestoneList = [],
selectedAssignee = null,
selectedAssigneeList = [],
selectedLabel = null,
selectedLabelList = [],
} = params;
state.authors.selected = selectedAuthor;
state.assignees.selected = selectedAssignees;
state.authors.selectedList = selectedAuthorList;
state.assignees.selected = selectedAssignee;
state.assignees.selectedList = selectedAssigneeList;
state.milestones.selected = selectedMilestone;
state.labels.selected = selectedLabels;
state.milestones.selectedList = selectedMilestoneList;
state.labels.selected = selectedLabel;
state.labels.selectedList = selectedLabelList;
},
[types.SET_MILESTONES_ENDPOINT](state, milestonesEndpoint) {
state.milestonesEndpoint = milestonesEndpoint;
......
......@@ -7,23 +7,27 @@ export default () => ({
errorCode: null,
data: [],
selected: null,
selectedList: [],
},
labels: {
isLoading: false,
errorCode: null,
data: [],
selected: [],
selected: null,
selectedList: [],
},
authors: {
isLoading: false,
errorCode: null,
data: [],
selected: null,
selectedList: [],
},
assignees: {
isLoading: false,
errorCode: null,
data: [],
selected: [],
selected: null,
selectedList: [],
},
});
......@@ -118,7 +118,7 @@ describe('FilteredSearchBar', () => {
expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(),
{
selectedLabels: [{ value: 'my-label', operator: '=' }],
selectedLabelList: [{ value: 'my-label', operator: '=' }],
selectedMilestone: { value: 'my-milestone', operator: '=' },
},
undefined,
......
......@@ -23,8 +23,8 @@ const assigneesTokenType = 'assignees';
const initialFilterBarState = {
selectedMilestone: null,
selectedAuthor: null,
selectedAssignees: null,
selectedLabels: null,
selectedAssigneeList: null,
selectedLabelList: null,
};
const defaultParams = {
......@@ -91,7 +91,7 @@ describe('Filter bar', () => {
});
const selectedMilestone = [filterMilestones[0]];
const selectedLabels = [filterLabels[0]];
const selectedLabelList = [filterLabels[0]];
const findFilteredSearch = () => wrapper.find(FilteredSearchBar);
const getSearchToken = type =>
......@@ -114,7 +114,7 @@ describe('Filter bar', () => {
beforeEach(() => {
store = createStore({
milestones: { data: selectedMilestone },
labels: { data: selectedLabels },
labels: { data: selectedLabelList },
authors: { data: [] },
assignees: { data: [] },
});
......@@ -140,7 +140,7 @@ describe('Filter bar', () => {
it('provides the initial label token', () => {
const { initialLabels: labelToken } = getSearchToken(labelsTokenType);
expect(labelToken).toHaveLength(selectedLabels.length);
expect(labelToken).toHaveLength(selectedLabelList.length);
});
});
......@@ -157,7 +157,7 @@ describe('Filter bar', () => {
it('clicks on the search button, setFilters is dispatched', () => {
const filters = [
{ type: 'milestone', value: { data: selectedMilestone[0].title, operator: '=' } },
{ type: 'labels', value: { data: selectedLabels[0].title, operator: '=' } },
{ type: 'labels', value: { data: selectedLabelList[0].title, operator: '=' } },
];
findFilteredSearch().vm.$emit('onFilter', filters);
......@@ -167,9 +167,9 @@ describe('Filter bar', () => {
expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(),
{
selectedLabels: [selectedLabels[0].title],
selectedLabelList: [selectedLabelList[0].title],
selectedMilestone: selectedMilestone[0].title,
selectedAssignees: [],
selectedAssigneeList: [],
selectedAuthor: null,
},
undefined,
......@@ -178,11 +178,11 @@ describe('Filter bar', () => {
});
describe.each`
stateKey | payload | paramKey
${'selectedMilestone'} | ${'12.0'} | ${'milestone_title'}
${'selectedAuthor'} | ${'rootUser'} | ${'author_username'}
${'selectedLabels'} | ${['Afternix', 'Brouceforge']} | ${'label_name'}
${'selectedAssignees'} | ${['rootUser', 'secondaryUser']} | ${'assignee_username'}
stateKey | payload | paramKey
${'selectedMilestone'} | ${'12.0'} | ${'milestone_title'}
${'selectedAuthor'} | ${'rootUser'} | ${'author_username'}
${'selectedLabelList'} | ${['Afternix', 'Brouceforge']} | ${'label_name'}
${'selectedAssigneeList'} | ${['rootUser', 'secondaryUser']} | ${'assignee_username'}
`('with a $stateKey updates the $paramKey url parameter', ({ stateKey, payload, paramKey }) => {
beforeEach(() => {
commonUtils.historyPushState = jest.fn();
......
......@@ -73,8 +73,8 @@ describe('Cycle analytics getters', () => {
describe('cycleAnalyticsRequestParams', () => {
const selectedAuthor = 'Gohan';
const selectedMilestone = 'SSJ4';
const selectedAssignees = ['krillin', 'gotenks'];
const selectedLabels = ['cell saga', 'buu saga'];
const selectedAssigneeList = ['krillin', 'gotenks'];
const selectedLabelList = ['cell saga', 'buu saga'];
beforeEach(() => {
const fullPath = 'cool-beans';
......@@ -88,8 +88,8 @@ describe('Cycle analytics getters', () => {
filters: {
authors: { selected: selectedAuthor },
milestones: { selected: selectedMilestone },
assignees: { selected: selectedAssignees },
labels: { selected: selectedLabels },
assignees: { selectedList: selectedAssigneeList },
labels: { selectedList: selectedLabelList },
},
};
});
......@@ -101,8 +101,8 @@ describe('Cycle analytics getters', () => {
${'project_ids'} | ${[1, 2]}
${'author_username'} | ${selectedAuthor}
${'milestone_title'} | ${selectedMilestone}
${'assignee_username'} | ${selectedAssignees}
${'label_name'} | ${selectedLabels}
${'assignee_username'} | ${selectedAssigneeList}
${'label_name'} | ${selectedLabelList}
`('should return the $param with value $value', ({ param, value }) => {
expect(
getters.cycleAnalyticsRequestParams(state, { selectedProjectIds: [1, 2] }),
......@@ -112,9 +112,9 @@ describe('Cycle analytics getters', () => {
});
it.each`
param | stateKey | value
${'assignee_username'} | ${'selectedAssignees'} | ${[]}
${'label_name'} | ${'selectedLabels'} | ${[]}
param | stateKey | value
${'assignee_username'} | ${'selectedAssigneeList'} | ${[]}
${'label_name'} | ${'selectedLabelList'} | ${[]}
`('should not return the $param when $stateKey=$value', ({ param, stateKey, value }) => {
expect(
getters.cycleAnalyticsRequestParams(
......
......@@ -9,14 +9,16 @@ const milestones = filterMilestones.map(convertObjectPropsToCamelCase);
const users = filterUsers.map(convertObjectPropsToCamelCase);
const labels = filterLabels.map(convertObjectPropsToCamelCase);
const filterValue = { value: 'foo' };
describe('Filters mutations', () => {
const errorCode = 500;
beforeEach(() => {
state = {
authors: { selected: null },
milestones: { selected: null },
assignees: { selected: [] },
labels: { selected: [] },
authors: { selected: null, selectedList: [] },
milestones: { selected: null, selectedList: [] },
assignees: { selected: null, selectedList: [] },
labels: { selected: null, selectedList: [] },
};
});
......@@ -28,6 +30,7 @@ describe('Filters mutations', () => {
mutation | stateKey | value
${types.SET_MILESTONES_ENDPOINT} | ${'milestonesEndpoint'} | ${'new-milestone-endpoint'}
${types.SET_LABELS_ENDPOINT} | ${'labelsEndpoint'} | ${'new-label-endpoint'}
${types.SET_GROUP_ENDPOINT} | ${'groupEndpoint'} | ${'new-group-endpoint'}
`('$mutation will set $stateKey=$value', ({ mutation, stateKey, value }) => {
mutations[mutation](state, value);
......@@ -35,16 +38,31 @@ describe('Filters mutations', () => {
});
it.each`
mutation | stateKey | value
${types.SET_SELECTED_FILTERS} | ${'authors'} | ${null}
${types.SET_SELECTED_FILTERS} | ${'milestones'} | ${null}
${types.SET_SELECTED_FILTERS} | ${'assignees'} | ${[]}
${types.SET_SELECTED_FILTERS} | ${'labels'} | ${[]}
`('$mutation will set $stateKey with a given value', ({ mutation, stateKey, value }) => {
mutations[mutation](state, { [stateKey]: { selected: value } });
mutation | stateKey | stateProp | filterName | value
${types.SET_SELECTED_FILTERS} | ${'authors'} | ${'selected'} | ${'selectedAuthor'} | ${null}
${types.SET_SELECTED_FILTERS} | ${'authors'} | ${'selected'} | ${'selectedAuthor'} | ${filterValue}
${types.SET_SELECTED_FILTERS} | ${'authors'} | ${'selectedList'} | ${'selectedAuthorList'} | ${[]}
${types.SET_SELECTED_FILTERS} | ${'authors'} | ${'selectedList'} | ${'selectedAuthorList'} | ${[filterValue]}
${types.SET_SELECTED_FILTERS} | ${'milestones'} | ${'selected'} | ${'selectedMilestone'} | ${null}
${types.SET_SELECTED_FILTERS} | ${'milestones'} | ${'selected'} | ${'selectedMilestone'} | ${filterValue}
${types.SET_SELECTED_FILTERS} | ${'milestones'} | ${'selectedList'} | ${'selectedMilestoneList'} | ${[]}
${types.SET_SELECTED_FILTERS} | ${'milestones'} | ${'selectedList'} | ${'selectedMilestoneList'} | ${[filterValue]}
${types.SET_SELECTED_FILTERS} | ${'assignees'} | ${'selected'} | ${'selectedAssignee'} | ${null}
${types.SET_SELECTED_FILTERS} | ${'assignees'} | ${'selected'} | ${'selectedAssignee'} | ${filterValue}
${types.SET_SELECTED_FILTERS} | ${'assignees'} | ${'selectedList'} | ${'selectedAssigneeList'} | ${[]}
${types.SET_SELECTED_FILTERS} | ${'assignees'} | ${'selectedList'} | ${'selectedAssigneeList'} | ${[filterValue]}
${types.SET_SELECTED_FILTERS} | ${'labels'} | ${'selected'} | ${'selectedLabel'} | ${null}
${types.SET_SELECTED_FILTERS} | ${'labels'} | ${'selected'} | ${'selectedLabel'} | ${filterValue}
${types.SET_SELECTED_FILTERS} | ${'labels'} | ${'selectedList'} | ${'selectedLabelList'} | ${[]}
${types.SET_SELECTED_FILTERS} | ${'labels'} | ${'selectedList'} | ${'selectedLabelList'} | ${[filterValue]}
`(
'$mutation will set $stateKey with a given value',
({ mutation, stateKey, stateProp, filterName, value }) => {
mutations[mutation](state, { [filterName]: value });
expect(state[stateKey].selected).toEqual(value);
});
expect(state[stateKey][stateProp]).toEqual(value);
},
);
it.each`
mutation | rootKey | stateKey | value
......
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