Commit 7db22d0b authored by Michael Lunøe's avatar Michael Lunøe Committed by Martin Wortschack

Refactor(VSA): separate filter bar store

Separate filter bar store and actions from app
store and actions to prepare for a general
analytics filter bar solution

Use url-sync component instead of mixin to
separate concerns between base and filter-bar
parent 1a4cdc25
<script> <script>
import { historyPushState } from '~/lib/utils/common_utils'; import { historyPushState } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility'; import { mergeUrlParams } from '~/lib/utils/url_utility';
export default { export default {
props: { props: {
...@@ -14,7 +14,7 @@ export default { ...@@ -14,7 +14,7 @@ export default {
immediate: true, immediate: true,
deep: true, deep: true,
handler(newQuery) { handler(newQuery) {
historyPushState(setUrlParams(newQuery, window.location.href, true)); historyPushState(mergeUrlParams(newQuery, window.location.href, { spreadArrays: true }));
}, },
}, },
}, },
......
...@@ -65,10 +65,6 @@ export default { ...@@ -65,10 +65,6 @@ export default {
'selectedGroup', 'selectedGroup',
'selectedProjects', 'selectedProjects',
'selectedStage', 'selectedStage',
'selectedMilestone',
'selectedAuthor',
'selectedLabels',
'selectedAssignees',
'stages', 'stages',
'currentStageEvents', 'currentStageEvents',
'errorCode', 'errorCode',
...@@ -123,18 +119,12 @@ export default { ...@@ -123,18 +119,12 @@ export default {
}, },
query() { query() {
const selectedProjectIds = this.selectedProjectIds?.length ? this.selectedProjectIds : null; const selectedProjectIds = this.selectedProjectIds?.length ? this.selectedProjectIds : null;
const selectedLabels = this.selectedLabels?.length ? this.selectedLabels : null;
const selectedAssignees = this.selectedAssignees?.length ? this.selectedAssignees : null;
return { return {
group_id: !this.hideGroupDropDown ? this.currentGroupPath : null, group_id: !this.hideGroupDropDown ? this.currentGroupPath : null,
'project_ids[]': selectedProjectIds, project_ids: selectedProjectIds,
created_after: toYmd(this.startDate), created_after: toYmd(this.startDate),
created_before: toYmd(this.endDate), created_before: toYmd(this.endDate),
milestone_title: this.selectedMilestone,
author_username: this.selectedAuthor,
'label_name[]': selectedLabels,
'assignee_username[]': selectedAssignees,
}; };
}, },
stageCount() { stageCount() {
......
<script> <script>
import { mapState, mapActions } from 'vuex'; import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale'; import { __ } from '~/locale';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue';
import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue'; import LabelToken from '~/vue_shared/components/filtered_search_bar/tokens/label_token.vue';
...@@ -28,6 +29,7 @@ export default { ...@@ -28,6 +29,7 @@ export default {
name: 'FilterBar', name: 'FilterBar',
components: { components: {
FilteredSearchBar, FilteredSearchBar,
UrlSync,
}, },
props: { props: {
groupPath: { groupPath: {
...@@ -37,11 +39,14 @@ export default { ...@@ -37,11 +39,14 @@ export default {
}, },
computed: { computed: {
...mapState('filters', { ...mapState('filters', {
milestones: state => state.milestones.data, selectedMilestone: state => state.milestones.selected,
labels: state => state.labels.data, selectedAuthor: state => state.authors.selected,
authors: state => state.authors.data, selectedLabels: state => state.labels.selected,
assignees: state => state.assignees.data, selectedAssignees: state => state.assignees.selected,
initialTokens: state => state.initialTokens, milestonesData: state => state.milestones.data,
labelsData: state => state.labels.data,
authorsData: state => state.authors.data,
assigneesData: state => state.assignees.data,
}), }),
tokens() { tokens() {
return [ return [
...@@ -50,7 +55,7 @@ export default { ...@@ -50,7 +55,7 @@ export default {
title: __('Milestone'), title: __('Milestone'),
type: 'milestone', type: 'milestone',
token: MilestoneToken, token: MilestoneToken,
initialMilestones: this.milestones, initialMilestones: this.milestonesData,
unique: true, unique: true,
symbol: '%', symbol: '%',
operators: [{ value: '=', description: 'is', default: 'true' }], operators: [{ value: '=', description: 'is', default: 'true' }],
...@@ -61,7 +66,7 @@ export default { ...@@ -61,7 +66,7 @@ export default {
title: __('Label'), title: __('Label'),
type: 'labels', type: 'labels',
token: LabelToken, token: LabelToken,
initialLabels: this.labels, initialLabels: this.labelsData,
unique: false, unique: false,
symbol: '~', symbol: '~',
operators: [{ value: '=', description: 'is', default: 'true' }], operators: [{ value: '=', description: 'is', default: 'true' }],
...@@ -72,7 +77,7 @@ export default { ...@@ -72,7 +77,7 @@ export default {
title: __('Author'), title: __('Author'),
type: 'author', type: 'author',
token: AuthorToken, token: AuthorToken,
initialAuthors: this.authors, initialAuthors: this.authorsData,
unique: true, unique: true,
operators: [{ value: '=', description: 'is', default: 'true' }], operators: [{ value: '=', description: 'is', default: 'true' }],
fetchAuthors: this.fetchAuthors, fetchAuthors: this.fetchAuthors,
...@@ -82,13 +87,24 @@ export default { ...@@ -82,13 +87,24 @@ export default {
title: __('Assignees'), title: __('Assignees'),
type: 'assignees', type: 'assignees',
token: AuthorToken, token: AuthorToken,
initialAuthors: this.assignees, initialAuthors: this.assigneesData,
unique: false, unique: false,
operators: [{ value: '=', description: 'is', default: 'true' }], operators: [{ value: '=', description: 'is', default: 'true' }],
fetchAuthors: this.fetchAssignees, fetchAuthors: this.fetchAssignees,
}, },
]; ];
}, },
query() {
const selectedLabels = this.selectedLabels?.length ? this.selectedLabels : null;
const selectedAssignees = this.selectedAssignees?.length ? this.selectedAssignees : null;
return {
milestone_title: this.selectedMilestone,
author_username: this.selectedAuthor,
label_name: selectedLabels,
assignee_username: selectedAssignees,
};
},
}, },
methods: { methods: {
...mapActions('filters', [ ...mapActions('filters', [
...@@ -104,8 +120,7 @@ export default { ...@@ -104,8 +120,7 @@ export default {
selectedAuthor: author = null, selectedAuthor: author = null,
selectedAssignees: assignees = [], selectedAssignees: assignees = [],
selectedLabels: labels = [], selectedLabels: labels = [],
} = this.initialTokens; } = this;
return prepareTokens({ milestone, author, assignees, labels }); return prepareTokens({ milestone, author, assignees, labels });
}, },
processFilters(filters) { processFilters(filters) {
...@@ -130,7 +145,6 @@ export default { ...@@ -130,7 +145,6 @@ export default {
return acc; return acc;
}, {}); }, {});
}, },
handleFilter(filters) { handleFilter(filters) {
const { labels, milestone, author, assignees } = this.processFilters(filters); const { labels, milestone, author, assignees } = this.processFilters(filters);
...@@ -146,12 +160,16 @@ export default { ...@@ -146,12 +160,16 @@ export default {
</script> </script>
<template> <template>
<filtered-search-bar <div>
:namespace="groupPath" <filtered-search-bar
recent-searches-storage-key="value-stream-analytics" class="gl-flex-grow-1"
:search-input-placeholder="__('Filter results')" :namespace="groupPath"
:tokens="tokens" recent-searches-storage-key="value-stream-analytics"
:initial-filter-value="initialFilterValue()" :search-input-placeholder="__('Filter results')"
@onFilter="handleFilter" :tokens="tokens"
/> :initial-filter-value="initialFilterValue()"
@onFilter="handleFilter"
/>
<url-sync :query="query" />
</div>
</template> </template>
...@@ -5,6 +5,21 @@ import httpStatus from '~/lib/utils/http_status'; ...@@ -5,6 +5,21 @@ import httpStatus from '~/lib/utils/http_status';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { removeFlash, handleErrorOrRethrow, isStageNameExistsError } from '../utils'; import { removeFlash, handleErrorOrRethrow, isStageNameExistsError } from '../utils';
const appendExtension = path => (path.indexOf('.') > -1 ? path : `${path}.json`);
export const setPaths = ({ dispatch }, options) => {
const { groupPath = '', milestonesPath = '', labelsPath = '' } = options;
// TODO: After we remove instance VSA we can rely on the paths from the BE
// https://gitlab.com/gitlab-org/gitlab/-/issues/223735
const milestonesEndpoint = milestonesPath || `/groups/${groupPath}/-/milestones`;
const labelsEndpoint = labelsPath || `/groups/${groupPath}/-/labels`;
return dispatch('filters/setEndpoints', {
labelsEndpoint: appendExtension(labelsEndpoint),
milestonesEndpoint: appendExtension(milestonesEndpoint),
});
};
export const setFeatureFlags = ({ commit }, featureFlags) => export const setFeatureFlags = ({ commit }, featureFlags) =>
commit(types.SET_FEATURE_FLAGS, featureFlags); commit(types.SET_FEATURE_FLAGS, featureFlags);
...@@ -237,20 +252,33 @@ export const removeStage = ({ dispatch, getters }, stageId) => { ...@@ -237,20 +252,33 @@ export const removeStage = ({ dispatch, getters }, 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);
export const initializeCycleAnalytics = ({ dispatch, commit }, initialData = {}) => { export const initializeCycleAnalytics = ({ dispatch, commit }, initialData = {}) => {
commit(types.INITIALIZE_CYCLE_ANALYTICS, initialData); commit(types.INITIALIZE_CYCLE_ANALYTICS, initialData);
const { featureFlags = {} } = initialData; const {
featureFlags = {},
milestonesPath,
labelsPath,
selectedAuthor,
selectedMilestone,
selectedAssignees,
selectedLabels,
} = initialData;
commit(types.SET_FEATURE_FLAGS, featureFlags); commit(types.SET_FEATURE_FLAGS, featureFlags);
if (initialData.group?.fullPath) { if (initialData.group?.fullPath) {
return dispatch('filters/initialize', { groupPath: initialData.group.fullPath, ...initialData }) return Promise.all([
dispatch('setPaths', { groupPath: initialData.group.fullPath, milestonesPath, labelsPath }),
dispatch('filters/initialize', {
selectedAuthor,
selectedMilestone,
selectedAssignees,
selectedLabels,
}),
])
.then(() => dispatch('fetchCycleAnalyticsData')) .then(() => dispatch('fetchCycleAnalyticsData'))
.then(() => dispatch('initializeCycleAnalyticsSuccess')); .then(() => dispatch('initializeCycleAnalyticsSuccess'));
} }
...@@ -336,3 +364,7 @@ export const fetchValueStreams = ({ commit, dispatch, getters, state }) => { ...@@ -336,3 +364,7 @@ export const fetchValueStreams = ({ commit, dispatch, getters, state }) => {
} }
return dispatch('fetchValueStreamData'); return dispatch('fetchValueStreamData');
}; };
export const setFilters = ({ dispatch }) => {
return dispatch('fetchCycleAnalyticsData');
};
...@@ -10,34 +10,36 @@ export const hasNoAccessError = state => state.errorCode === httpStatus.FORBIDDE ...@@ -10,34 +10,36 @@ export const hasNoAccessError = state => state.errorCode === httpStatus.FORBIDDE
export const currentValueStreamId = ({ selectedValueStream }) => export const currentValueStreamId = ({ selectedValueStream }) =>
selectedValueStream?.id || DEFAULT_VALUE_STREAM_ID; selectedValueStream?.id || DEFAULT_VALUE_STREAM_ID;
export const currentGroupPath = ({ selectedGroup }) => export const currentGroupPath = ({ selectedGroup }) => selectedGroup?.fullPath || null;
selectedGroup && selectedGroup.fullPath ? selectedGroup.fullPath : null;
export const currentGroupParentPath = ({ selectedGroup }, getters) => export const currentGroupParentPath = ({ selectedGroup }, getters) =>
selectedGroup?.parentId || getters.currentGroupPath; selectedGroup?.parentId || getters.currentGroupPath;
export const selectedProjectIds = ({ selectedProjects }) => export const selectedProjectIds = ({ selectedProjects }) =>
selectedProjects.length ? selectedProjects.map(({ id }) => id) : []; selectedProjects?.map(({ id }) => id) || [];
export const cycleAnalyticsRequestParams = ( export const cycleAnalyticsRequestParams = (state, getters) => {
{ const {
startDate = null, startDate = null,
endDate = null, endDate = null,
selectedAuthor = null, filters: {
selectedMilestone = null, authors: { selected: selectedAuthor = null },
selectedAssignees = [], milestones: { selected: selectedMilestone = null },
selectedLabels = [], assignees: { selected: selectedAssignees = [] },
}, labels: { selected: selectedLabels = [] },
getters, },
) => ({ } = state;
project_ids: getters.selectedProjectIds,
created_after: startDate ? dateFormat(startDate, dateFormats.isoDate) : null, return {
created_before: endDate ? dateFormat(endDate, dateFormats.isoDate) : null, project_ids: getters.selectedProjectIds,
author_username: selectedAuthor, created_after: startDate ? dateFormat(startDate, dateFormats.isoDate) : null,
milestone_title: selectedMilestone, created_before: endDate ? dateFormat(endDate, dateFormats.isoDate) : null,
assignee_username: selectedAssignees, author_username: selectedAuthor,
label_name: selectedLabels, milestone_title: selectedMilestone,
}); assignee_username: selectedAssignees,
label_name: selectedLabels,
};
};
const filterStagesByHiddenStatus = (stages = [], isHidden = true) => const filterStagesByHiddenStatus = (stages = [], isHidden = true) =>
stages.filter(({ hidden = false }) => hidden === isHidden); stages.filter(({ hidden = false }) => hidden === isHidden);
......
...@@ -4,23 +4,17 @@ import { __ } from '~/locale'; ...@@ -4,23 +4,17 @@ import { __ } from '~/locale';
import Api from '~/api'; import Api from '~/api';
import * as types from './mutation_types'; import * as types from './mutation_types';
const appendExtension = path => (path.indexOf('.') > -1 ? path : `${path}.json`); export const setEndpoints = ({ commit }, { milestonesEndpoint, labelsEndpoint }) => {
commit(types.SET_MILESTONES_ENDPOINT, milestonesEndpoint);
// TODO: After we remove instance VSA we can rely on the paths from the BE commit(types.SET_LABELS_ENDPOINT, labelsEndpoint);
// https://gitlab.com/gitlab-org/gitlab/-/issues/223735
export const setPaths = ({ commit }, { groupPath = '', milestonesPath = '', labelsPath = '' }) => {
const ms = milestonesPath || `/groups/${groupPath}/-/milestones`;
const ls = labelsPath || `/groups/${groupPath}/-/labels`;
commit(types.SET_MILESTONES_PATH, appendExtension(ms));
commit(types.SET_LABELS_PATH, appendExtension(ls));
}; };
export const fetchMilestones = ({ commit, state }, search_title = '') => { export const fetchMilestones = ({ commit, state }, search_title = '') => {
commit(types.REQUEST_MILESTONES); commit(types.REQUEST_MILESTONES);
const { milestonesPath } = state; const { milestonesEndpoint } = state;
return axios return axios
.get(milestonesPath, { params: { search_title } }) .get(milestonesEndpoint, { params: { search_title } })
.then(response => { .then(response => {
commit(types.RECEIVE_MILESTONES_SUCCESS, response.data); commit(types.RECEIVE_MILESTONES_SUCCESS, response.data);
return response; return response;
...@@ -36,7 +30,7 @@ export const fetchLabels = ({ commit, state }, search = '') => { ...@@ -36,7 +30,7 @@ export const fetchLabels = ({ commit, state }, search = '') => {
commit(types.REQUEST_LABELS); commit(types.REQUEST_LABELS);
return axios return axios
.get(state.labelsPath, { params: { search } }) .get(state.labelsEndpoint, { params: { search } })
.then(response => { .then(response => {
commit(types.RECEIVE_LABELS_SUCCESS, response.data); commit(types.RECEIVE_LABELS_SUCCESS, response.data);
return response; return response;
...@@ -85,15 +79,12 @@ export const fetchAssignees = ({ commit, rootGetters }, query = '') => { ...@@ -85,15 +79,12 @@ export const fetchAssignees = ({ commit, rootGetters }, query = '') => {
}); });
}; };
export const setFilters = ({ dispatch }, nextFilters) => { export const setFilters = ({ commit, dispatch }, filters) => {
return Promise.resolve() commit(types.SET_SELECTED_FILTERS, filters);
.then(() => dispatch('setSelectedFilters', nextFilters, { root: true }))
.then(() => dispatch('fetchCycleAnalyticsData', null, { root: true })); return dispatch('setFilters', filters, { root: true });
}; };
export const initialize = ({ dispatch, commit }, initialFilters) => { export const initialize = ({ commit }, initialFilters) => {
commit(types.INITIALIZE, initialFilters); commit(types.SET_SELECTED_FILTERS, initialFilters);
return dispatch('setPaths', initialFilters).then(() =>
dispatch('setSelectedFilters', initialFilters, { root: true }),
);
}; };
export const INITIALIZE = 'INITIALIZE'; export const SET_MILESTONES_ENDPOINT = 'SET_MILESTONES_ENDPOINT';
export const SET_MILESTONES_PATH = 'SET_MILESTONES_PATH'; export const SET_LABELS_ENDPOINT = 'SET_LABELS_ENDPOINT';
export const SET_LABELS_PATH = 'SET_LABELS_PATH';
export const REQUEST_MILESTONES = 'REQUEST_MILESTONES'; export const REQUEST_MILESTONES = 'REQUEST_MILESTONES';
export const RECEIVE_MILESTONES_SUCCESS = 'RECEIVE_MILESTONES_SUCCESS'; export const RECEIVE_MILESTONES_SUCCESS = 'RECEIVE_MILESTONES_SUCCESS';
...@@ -17,3 +16,5 @@ export const RECEIVE_AUTHORS_ERROR = 'RECEIVE_AUTHORS_ERROR'; ...@@ -17,3 +16,5 @@ export const RECEIVE_AUTHORS_ERROR = 'RECEIVE_AUTHORS_ERROR';
export const REQUEST_ASSIGNEES = 'REQUEST_ASSIGNEES'; export const REQUEST_ASSIGNEES = 'REQUEST_ASSIGNEES';
export const RECEIVE_ASSIGNEES_SUCCESS = 'RECEIVE_ASSIGNEES_SUCCESS'; export const RECEIVE_ASSIGNEES_SUCCESS = 'RECEIVE_ASSIGNEES_SUCCESS';
export const RECEIVE_ASSIGNEES_ERROR = 'RECEIVE_ASSIGNEES_ERROR'; export const RECEIVE_ASSIGNEES_ERROR = 'RECEIVE_ASSIGNEES_ERROR';
export const SET_SELECTED_FILTERS = 'SET_SELECTED_FILTERS';
import * as types from './mutation_types'; import * as types from './mutation_types';
export default { export default {
[types.INITIALIZE]( [types.SET_SELECTED_FILTERS](state, params) {
state, const {
{
selectedAuthor = null, selectedAuthor = null,
selectedMilestone = null, selectedMilestone = null,
selectedAssignees = [], selectedAssignees = [],
selectedLabels = [], selectedLabels = [],
} = {}, } = params;
) { state.authors.selected = selectedAuthor;
state.initialTokens = { state.assignees.selected = selectedAssignees;
selectedAuthor, state.milestones.selected = selectedMilestone;
selectedMilestone, state.labels.selected = selectedLabels;
selectedAssignees,
selectedLabels,
};
}, },
[types.SET_MILESTONES_PATH](state, milestonesPath) { [types.SET_MILESTONES_ENDPOINT](state, milestonesEndpoint) {
state.milestonesPath = milestonesPath; state.milestonesEndpoint = milestonesEndpoint;
}, },
[types.SET_LABELS_PATH](state, labelsPath) { [types.SET_LABELS_ENDPOINT](state, labelsEndpoint) {
state.labelsPath = labelsPath; state.labelsEndpoint = labelsEndpoint;
}, },
[types.REQUEST_MILESTONES](state) { [types.REQUEST_MILESTONES](state) {
state.milestones.isLoading = true; state.milestones.isLoading = true;
......
export default () => ({ export default () => ({
milestonesPath: '', milestonesEndpoint: '',
labelsPath: '', labelsEndpoint: '',
milestones: { milestones: {
isLoading: false, isLoading: false,
data: [], data: [],
selected: null,
}, },
labels: { labels: {
isLoading: false, isLoading: false,
data: [], data: [],
selected: [],
}, },
authors: { authors: {
isLoading: false, isLoading: false,
data: [], data: [],
selected: null,
}, },
assignees: { assignees: {
isLoading: false, isLoading: false,
data: [], data: [],
}, selected: [],
initialTokens: {
selectedMilestone: null,
selectedAuthor: null,
selectedAssignees: [],
selectedLabels: [],
}, },
}); });
...@@ -4,7 +4,6 @@ export const SET_SELECTED_GROUP = 'SET_SELECTED_GROUP'; ...@@ -4,7 +4,6 @@ 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 SET_SELECTED_VALUE_STREAM = 'SET_SELECTED_VALUE_STREAM'; export const SET_SELECTED_VALUE_STREAM = 'SET_SELECTED_VALUE_STREAM';
export const REQUEST_CYCLE_ANALYTICS_DATA = 'REQUEST_CYCLE_ANALYTICS_DATA'; export const REQUEST_CYCLE_ANALYTICS_DATA = 'REQUEST_CYCLE_ANALYTICS_DATA';
......
...@@ -115,13 +115,6 @@ export default { ...@@ -115,13 +115,6 @@ export default {
state.isSavingStageOrder = false; state.isSavingStageOrder = false;
state.errorSavingStageOrder = true; state.errorSavingStageOrder = true;
}, },
[types.SET_SELECTED_FILTERS](state, params) {
const { selectedAuthor, selectedAssignees, selectedMilestone, selectedLabels } = params;
state.selectedAuthor = selectedAuthor;
state.selectedAssignees = selectedAssignees;
state.selectedMilestone = selectedMilestone;
state.selectedLabels = selectedLabels;
},
[types.REQUEST_CREATE_VALUE_STREAM](state) { [types.REQUEST_CREATE_VALUE_STREAM](state) {
state.isCreatingValueStream = true; state.isCreatingValueStream = true;
state.createValueStreamErrors = {}; state.createValueStreamErrors = {};
......
...@@ -16,10 +16,6 @@ export default () => ({ ...@@ -16,10 +16,6 @@ export default () => ({
selectedGroup: null, selectedGroup: null,
selectedProjects: [], selectedProjects: [],
selectedStage: null, selectedStage: null,
selectedAuthor: null,
selectedMilestone: null,
selectedAssignees: [],
selectedLabels: [],
selectedValueStream: null, selectedValueStream: null,
currentStageEvents: [], currentStageEvents: [],
......
...@@ -20,8 +20,8 @@ import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_wo ...@@ -20,8 +20,8 @@ import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_wo
import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue'; import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { toYmd } from 'ee/analytics/shared/utils'; import { toYmd } from 'ee/analytics/shared/utils';
import UrlSyncMixin from 'ee/analytics/shared/mixins/url_sync_mixin';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import * as commonUtils from '~/lib/utils/common_utils'; import * as commonUtils from '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility'; import * as urlUtils from '~/lib/utils/url_utility';
import * as mockData from '../mock_data'; import * as mockData from '../mock_data';
...@@ -45,6 +45,7 @@ const defaultStubs = { ...@@ -45,6 +45,7 @@ const defaultStubs = {
GroupsDropdownFilter: true, GroupsDropdownFilter: true,
ValueStreamSelect: true, ValueStreamSelect: true,
Metrics: true, Metrics: true,
UrlSync,
}; };
const defaultFeatureFlags = { const defaultFeatureFlags = {
...@@ -57,10 +58,6 @@ const defaultFeatureFlags = { ...@@ -57,10 +58,6 @@ const defaultFeatureFlags = {
const initialCycleAnalyticsState = { const initialCycleAnalyticsState = {
createdAfter: mockData.startDate, createdAfter: mockData.startDate,
createdBefore: mockData.endDate, createdBefore: mockData.endDate,
selectedMilestone: null,
selectedAuthor: null,
selectedAssignees: [],
selectedLabels: [],
group: selectedGroup, group: selectedGroup,
}; };
...@@ -84,7 +81,6 @@ function createComponent({ ...@@ -84,7 +81,6 @@ function createComponent({
const comp = func(Component, { const comp = func(Component, {
localVue, localVue,
store, store,
mixins: [UrlSyncMixin],
propsData: { propsData: {
emptyStateSvgPath, emptyStateSvgPath,
noDataSvgPath, noDataSvgPath,
...@@ -124,6 +120,14 @@ function createComponent({ ...@@ -124,6 +120,14 @@ function createComponent({
return comp; return comp;
} }
async function shouldMergeUrlParams(wrapper, result) {
await wrapper.vm.$nextTick();
expect(urlUtils.mergeUrlParams).toHaveBeenCalledWith(result, window.location.href, {
spreadArrays: true,
});
expect(commonUtils.historyPushState).toHaveBeenCalled();
}
describe('Cycle Analytics component', () => { describe('Cycle Analytics component', () => {
let wrapper; let wrapper;
let mock; let mock;
...@@ -134,13 +138,6 @@ describe('Cycle Analytics component', () => { ...@@ -134,13 +138,6 @@ describe('Cycle Analytics component', () => {
.findAll(StageNavItem) .findAll(StageNavItem)
.at(index); .at(index);
const shouldSetUrlParams = result => {
return wrapper.vm.$nextTick().then(() => {
expect(urlUtils.setUrlParams).toHaveBeenCalledWith(result, window.location.href, true);
expect(commonUtils.historyPushState).toHaveBeenCalled();
});
};
const displaysProjectsDropdownFilter = flag => { const displaysProjectsDropdownFilter = flag => {
expect(wrapper.find(ProjectsDropdownFilter).exists()).toBe(flag); expect(wrapper.find(ProjectsDropdownFilter).exists()).toBe(flag);
}; };
...@@ -650,18 +647,14 @@ describe('Cycle Analytics component', () => { ...@@ -650,18 +647,14 @@ describe('Cycle Analytics component', () => {
created_after: toYmd(mockData.startDate), created_after: toYmd(mockData.startDate),
created_before: toYmd(mockData.endDate), created_before: toYmd(mockData.endDate),
group_id: selectedGroup.fullPath, group_id: selectedGroup.fullPath,
'project_ids[]': null, project_ids: null,
milestone_title: null,
author_username: null,
'assignee_username[]': null,
'label_name[]': null,
}; };
const selectedProjectIds = mockData.selectedProjects.map(({ id }) => id); const selectedProjectIds = mockData.selectedProjects.map(({ id }) => id);
beforeEach(() => { beforeEach(() => {
commonUtils.historyPushState = jest.fn(); commonUtils.historyPushState = jest.fn();
urlUtils.setUrlParams = jest.fn(); urlUtils.mergeUrlParams = jest.fn();
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
wrapper = createComponent(); wrapper = createComponent();
...@@ -670,13 +663,13 @@ describe('Cycle Analytics component', () => { ...@@ -670,13 +663,13 @@ describe('Cycle Analytics component', () => {
}); });
it('sets the created_after and created_before url parameters', () => { it('sets the created_after and created_before url parameters', () => {
return shouldSetUrlParams(defaultParams); return shouldMergeUrlParams(wrapper, defaultParams);
}); });
describe('with hideGroupDropDown=true', () => { describe('with hideGroupDropDown=true', () => {
beforeEach(() => { beforeEach(() => {
commonUtils.historyPushState = jest.fn(); commonUtils.historyPushState = jest.fn();
urlUtils.setUrlParams = jest.fn(); urlUtils.mergeUrlParams = jest.fn();
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
...@@ -693,7 +686,7 @@ describe('Cycle Analytics component', () => { ...@@ -693,7 +686,7 @@ describe('Cycle Analytics component', () => {
}); });
it('sets the group_id url parameter', () => { it('sets the group_id url parameter', () => {
return shouldSetUrlParams({ return shouldMergeUrlParams(wrapper, {
...defaultParams, ...defaultParams,
created_after: toYmd(mockData.startDate), created_after: toYmd(mockData.startDate),
created_before: toYmd(mockData.endDate), created_before: toYmd(mockData.endDate),
...@@ -710,7 +703,7 @@ describe('Cycle Analytics component', () => { ...@@ -710,7 +703,7 @@ describe('Cycle Analytics component', () => {
}); });
it('sets the group_id url parameter', () => { it('sets the group_id url parameter', () => {
return shouldSetUrlParams({ return shouldMergeUrlParams(wrapper, {
...defaultParams, ...defaultParams,
group_id: fakeGroup.fullPath, group_id: fakeGroup.fullPath,
}); });
...@@ -728,35 +721,12 @@ describe('Cycle Analytics component', () => { ...@@ -728,35 +721,12 @@ describe('Cycle Analytics component', () => {
}); });
it('sets the project_ids url parameter', () => { it('sets the project_ids url parameter', () => {
return shouldSetUrlParams({ return shouldMergeUrlParams(wrapper, {
...defaultParams, ...defaultParams,
created_after: toYmd(mockData.startDate), created_after: toYmd(mockData.startDate),
created_before: toYmd(mockData.endDate), created_before: toYmd(mockData.endDate),
group_id: selectedGroup.fullPath, group_id: selectedGroup.fullPath,
'project_ids[]': selectedProjectIds, project_ids: selectedProjectIds,
});
});
});
describe.each`
stateKey | payload | paramKey
${'selectedMilestone'} | ${'12.0'} | ${'milestone_title'}
${'selectedAuthor'} | ${'rootUser'} | ${'author_username'}
${'selectedAssignees'} | ${['rootUser', 'secondaryUser']} | ${'assignee_username[]'}
${'selectedLabels'} | ${['Afternix', 'Brouceforge']} | ${'label_name[]'}
`('with a $stateKey updates the $paramKey url parameter', ({ stateKey, payload, paramKey }) => {
beforeEach(() => {
wrapper.vm.$store.dispatch('filters/setFilters', {
...initialCycleAnalyticsState,
group: selectedGroup,
selectedProjects: mockData.selectedProjects,
[stateKey]: payload,
});
});
it(`sets the ${paramKey} url parameter`, () => {
return shouldSetUrlParams({
...defaultParams,
[paramKey]: payload,
}); });
}); });
}); });
......
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import storeConfig from 'ee/analytics/cycle_analytics/store';
import FilterBar, { prepareTokens } from 'ee/analytics/cycle_analytics/components/filter_bar.vue'; import FilterBar, { prepareTokens } from 'ee/analytics/cycle_analytics/components/filter_bar.vue';
import initialFiltersState from 'ee/analytics/cycle_analytics/store/modules/filters/state'; import initialFiltersState from 'ee/analytics/cycle_analytics/store/modules/filters/state';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
import { filterMilestones, filterLabels } from '../mock_data'; import { filterMilestones, filterLabels } from '../mock_data';
import * as commonUtils from '~/lib/utils/common_utils';
import * as urlUtils from '~/lib/utils/url_utility';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -13,9 +19,32 @@ const labelsTokenType = 'labels'; ...@@ -13,9 +19,32 @@ const labelsTokenType = 'labels';
const authorTokenType = 'author'; const authorTokenType = 'author';
const assigneesTokenType = 'assignees'; const assigneesTokenType = 'assignees';
const initialFilterBarState = {
selectedMilestone: null,
selectedAuthor: null,
selectedAssignees: null,
selectedLabels: null,
};
const defaultParams = {
milestone_title: null,
author_username: null,
assignee_username: null,
label_name: null,
};
async function shouldMergeUrlParams(wrapper, result) {
await wrapper.vm.$nextTick();
expect(urlUtils.mergeUrlParams).toHaveBeenCalledWith(result, window.location.href, {
spreadArrays: true,
});
expect(commonUtils.historyPushState).toHaveBeenCalled();
}
describe('Filter bar', () => { describe('Filter bar', () => {
let wrapper; let wrapper;
let store; let store;
let mock;
let setFiltersMock; let setFiltersMock;
...@@ -38,21 +67,30 @@ describe('Filter bar', () => { ...@@ -38,21 +67,30 @@ describe('Filter bar', () => {
}); });
}; };
const createComponent = initialStore => const createComponent = initialStore => {
shallowMount(FilterBar, { return shallowMount(FilterBar, {
localVue, localVue,
store: initialStore, store: initialStore,
propsData: { propsData: {
groupPath: 'foo', groupPath: 'foo',
}, },
stubs: {
UrlSync,
},
}); });
};
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
mock.restore();
}); });
const selectedMilestone = [filterMilestones[0]]; const selectedMilestone = [filterMilestones[0]];
const selectedLabel = [filterLabels[0]]; const selectedLabels = [filterLabels[0]];
const findFilteredSearch = () => wrapper.find(FilteredSearchBar); const findFilteredSearch = () => wrapper.find(FilteredSearchBar);
const getSearchToken = type => const getSearchToken = type =>
...@@ -75,7 +113,7 @@ describe('Filter bar', () => { ...@@ -75,7 +113,7 @@ describe('Filter bar', () => {
beforeEach(() => { beforeEach(() => {
store = createStore({ store = createStore({
milestones: { data: selectedMilestone }, milestones: { data: selectedMilestone },
labels: { data: selectedLabel }, labels: { data: selectedLabels },
authors: { data: [] }, authors: { data: [] },
assignees: { data: [] }, assignees: { data: [] },
}); });
...@@ -101,7 +139,7 @@ describe('Filter bar', () => { ...@@ -101,7 +139,7 @@ describe('Filter bar', () => {
it('provides the initial label token', () => { it('provides the initial label token', () => {
const { initialLabels: labelToken } = getSearchToken(labelsTokenType); const { initialLabels: labelToken } = getSearchToken(labelsTokenType);
expect(labelToken).toHaveLength(selectedLabel.length); expect(labelToken).toHaveLength(selectedLabels.length);
}); });
}); });
...@@ -117,13 +155,13 @@ describe('Filter bar', () => { ...@@ -117,13 +155,13 @@ describe('Filter bar', () => {
it('clicks on the search button, setFilters is dispatched', () => { it('clicks on the search button, setFilters is dispatched', () => {
findFilteredSearch().vm.$emit('onFilter', [ findFilteredSearch().vm.$emit('onFilter', [
{ type: 'milestone', value: { data: selectedMilestone[0].title, operator: '=' } }, { type: 'milestone', value: { data: selectedMilestone[0].title, operator: '=' } },
{ type: 'labels', value: { data: selectedLabel[0].title, operator: '=' } }, { type: 'labels', value: { data: selectedLabels[0].title, operator: '=' } },
]); ]);
expect(setFiltersMock).toHaveBeenCalledWith( expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ {
selectedLabels: [selectedLabel[0].title], selectedLabels: [selectedLabels[0].title],
selectedMilestone: selectedMilestone[0].title, selectedMilestone: selectedMilestone[0].title,
selectedAssignees: [], selectedAssignees: [],
selectedAuthor: null, selectedAuthor: null,
...@@ -206,4 +244,31 @@ describe('Filter bar', () => { ...@@ -206,4 +244,31 @@ describe('Filter bar', () => {
expect(res).toEqual(result); expect(res).toEqual(result);
}); });
}); });
describe.each`
stateKey | payload | paramKey
${'selectedMilestone'} | ${'12.0'} | ${'milestone_title'}
${'selectedAuthor'} | ${'rootUser'} | ${'author_username'}
${'selectedLabels'} | ${['Afternix', 'Brouceforge']} | ${'label_name'}
${'selectedAssignees'} | ${['rootUser', 'secondaryUser']} | ${'assignee_username'}
`('with a $stateKey updates the $paramKey url parameter', ({ stateKey, payload, paramKey }) => {
beforeEach(() => {
commonUtils.historyPushState = jest.fn();
urlUtils.mergeUrlParams = jest.fn();
mock = new MockAdapter(axios);
wrapper = createComponent(storeConfig);
wrapper.vm.$store.dispatch('filters/setFilters', {
...initialFilterBarState,
[stateKey]: payload,
});
});
it(`sets the ${paramKey} url parameter`, () => {
return shouldMergeUrlParams(wrapper, {
...defaultParams,
[paramKey]: payload,
});
});
});
}); });
...@@ -16,6 +16,10 @@ import { ...@@ -16,6 +16,10 @@ import {
valueStreams, valueStreams,
} from '../mock_data'; } from '../mock_data';
const groupPath = 'fake_group_path';
const milestonesPath = 'fake_milestones_path';
const labelsPath = 'fake_labels_path';
const stageData = { events: [] }; const stageData = { events: [] };
const error = new Error(`Request failed with status code ${httpStatusCodes.NOT_FOUND}`); const error = new Error(`Request failed with status code ${httpStatusCodes.NOT_FOUND}`);
const flashErrorMessage = 'There was an error while fetching value stream analytics data.'; const flashErrorMessage = 'There was an error while fetching value stream analytics data.';
...@@ -102,6 +106,48 @@ describe('Cycle analytics actions', () => { ...@@ -102,6 +106,48 @@ describe('Cycle analytics actions', () => {
}); });
}); });
describe('setPaths', () => {
describe('with endpoint paths provided', () => {
it('dispatches the filters/setEndpoints action', () => {
return testAction(
actions.setPaths,
{ groupPath, milestonesPath, labelsPath },
state,
[],
[
{
type: 'filters/setEndpoints',
payload: {
labelsEndpoint: 'fake_labels_path.json',
milestonesEndpoint: 'fake_milestones_path.json',
},
},
],
);
});
});
describe('without endpoint paths provided', () => {
it('dispatches the filters/setEndpoints action', () => {
return testAction(
actions.setPaths,
{ groupPath },
state,
[],
[
{
type: 'filters/setEndpoints',
payload: {
labelsEndpoint: '/groups/fake_group_path/-/labels.json',
milestonesEndpoint: '/groups/fake_group_path/-/milestones.json',
},
},
],
);
});
});
});
describe('setDateRange', () => { describe('setDateRange', () => {
const payload = { startDate, endDate }; const payload = { startDate, endDate };
...@@ -1047,4 +1093,10 @@ describe('Cycle analytics actions', () => { ...@@ -1047,4 +1093,10 @@ describe('Cycle analytics actions', () => {
); );
}); });
}); });
describe('setFilters', () => {
it('dispatches the fetchCycleAnalyticsData action', () => {
return testAction(actions.setFilters, null, state, [], [{ type: 'fetchCycleAnalyticsData' }]);
});
});
}); });
...@@ -121,10 +121,12 @@ describe('Cycle analytics getters', () => { ...@@ -121,10 +121,12 @@ describe('Cycle analytics getters', () => {
startDate, startDate,
endDate, endDate,
selectedProjects, selectedProjects,
selectedAuthor, filters: {
selectedMilestone, authors: { selected: selectedAuthor },
selectedAssignees, milestones: { selected: selectedMilestone },
selectedLabels, assignees: { selected: selectedAssignees },
labels: { selected: selectedLabels },
},
}; };
}); });
......
...@@ -8,8 +8,8 @@ import httpStatusCodes from '~/lib/utils/http_status'; ...@@ -8,8 +8,8 @@ import httpStatusCodes from '~/lib/utils/http_status';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import { filterMilestones, filterUsers, filterLabels } from '../../../mock_data'; import { filterMilestones, filterUsers, filterLabels } from '../../../mock_data';
const milestonesPath = 'fake_milestones_path'; const milestonesEndpoint = 'fake_milestones_endpoint';
const labelsPath = 'fake_labels_path'; const labelsEndpoint = 'fake_labels_endpoint';
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -33,44 +33,35 @@ describe('Filters actions', () => { ...@@ -33,44 +33,35 @@ describe('Filters actions', () => {
describe('initialize', () => { describe('initialize', () => {
const initialData = { const initialData = {
milestonesPath, milestonesEndpoint,
labelsPath, labelsEndpoint,
selectedAuthor: 'Mr cool', selectedAuthor: 'Mr cool',
selectedMilestone: 'NEXT', selectedMilestone: 'NEXT',
}; };
it('dispatches setPaths, setSelectedFilters', () => { it('does not dispatch', () => {
return actions const result = actions.initialize(
.initialize( {
{ state,
state, dispatch: mockDispatch,
dispatch: mockDispatch, commit: mockCommit,
commit: mockCommit, },
}, initialData,
initialData, );
) expect(result).toBeUndefined();
.then(() => { expect(mockDispatch).not.toHaveBeenCalled();
expect(mockDispatch).toHaveBeenCalledTimes(2);
expect(mockDispatch).toHaveBeenCalledWith('setPaths', initialData);
expect(mockDispatch).toHaveBeenCalledWith('setSelectedFilters', initialData, {
root: true,
});
});
}); });
it(`commits the ${types.INITIALIZE}`, () => { it(`commits the ${types.SET_SELECTED_FILTERS}`, () => {
return actions actions.initialize(
.initialize( {
{ state,
state, dispatch: mockDispatch,
dispatch: mockDispatch, commit: mockCommit,
commit: mockCommit, },
}, initialData,
initialData, );
) expect(mockCommit).toHaveBeenCalledWith(types.SET_SELECTED_FILTERS, initialData);
.then(() => {
expect(mockCommit).toHaveBeenCalledWith(types.INITIALIZE, initialData);
});
}); });
}); });
...@@ -80,57 +71,36 @@ describe('Filters actions', () => { ...@@ -80,57 +71,36 @@ describe('Filters actions', () => {
selectedMilestone: 'NEXT', selectedMilestone: 'NEXT',
}; };
it('dispatches the root/setSelectedFilters and root/fetchCycleAnalyticsData actions', () => { it('dispatches the root/setFilters action', () => {
return testAction( return testAction(
actions.setFilters, actions.setFilters,
nextFilters, nextFilters,
state, state,
[],
[ [
{ {
type: 'setSelectedFilters',
payload: nextFilters, payload: nextFilters,
}, type: types.SET_SELECTED_FILTERS,
{
type: 'fetchCycleAnalyticsData',
payload: null,
}, },
], ],
);
});
it('sets the selectedLabels from the labels available', () => {
return testAction(
actions.setFilters,
{ ...nextFilters, selectedLabels: [filterLabels[1].title] },
{ ...state, labels: { data: filterLabels } },
[],
[ [
{ {
type: 'setSelectedFilters', type: 'setFilters',
payload: { payload: nextFilters,
...nextFilters,
selectedLabels: [filterLabels[1].title],
},
},
{
type: 'fetchCycleAnalyticsData',
payload: null,
}, },
], ],
); );
}); });
}); });
describe('setPaths', () => { describe('setEndpoints', () => {
it('sets the api paths', () => { it('sets the api paths', () => {
return testAction( return testAction(
actions.setPaths, actions.setEndpoints,
{ milestonesPath, labelsPath }, { milestonesEndpoint, labelsEndpoint },
state, state,
[ [
{ payload: 'fake_milestones_path.json', type: types.SET_MILESTONES_PATH }, { payload: 'fake_milestones_endpoint', type: types.SET_MILESTONES_ENDPOINT },
{ payload: 'fake_labels_path.json', type: types.SET_LABELS_PATH }, { payload: 'fake_labels_endpoint', type: types.SET_LABELS_ENDPOINT },
], ],
[], [],
); );
...@@ -185,14 +155,14 @@ describe('Filters actions', () => { ...@@ -185,14 +155,14 @@ describe('Filters actions', () => {
describe('fetchMilestones', () => { describe('fetchMilestones', () => {
describe('success', () => { describe('success', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(milestonesPath).replyOnce(httpStatusCodes.OK, filterMilestones); mock.onGet(milestonesEndpoint).replyOnce(httpStatusCodes.OK, filterMilestones);
}); });
it('dispatches RECEIVE_MILESTONES_SUCCESS with received data', () => { it('dispatches RECEIVE_MILESTONES_SUCCESS with received data', () => {
return testAction( return testAction(
actions.fetchMilestones, actions.fetchMilestones,
null, null,
{ ...state, milestonesPath }, { ...state, milestonesEndpoint },
[ [
{ type: types.REQUEST_MILESTONES }, { type: types.REQUEST_MILESTONES },
{ type: types.RECEIVE_MILESTONES_SUCCESS, payload: filterMilestones }, { type: types.RECEIVE_MILESTONES_SUCCESS, payload: filterMilestones },
...@@ -237,7 +207,7 @@ describe('Filters actions', () => { ...@@ -237,7 +207,7 @@ describe('Filters actions', () => {
return testAction( return testAction(
actions.fetchAssignees, actions.fetchAssignees,
null, null,
{ ...state, milestonesPath }, { ...state, milestonesEndpoint },
[ [
{ type: types.REQUEST_ASSIGNEES }, { type: types.REQUEST_ASSIGNEES },
{ type: types.RECEIVE_ASSIGNEES_SUCCESS, payload: filterUsers }, { type: types.RECEIVE_ASSIGNEES_SUCCESS, payload: filterUsers },
...@@ -275,14 +245,14 @@ describe('Filters actions', () => { ...@@ -275,14 +245,14 @@ describe('Filters actions', () => {
describe('fetchLabels', () => { describe('fetchLabels', () => {
describe('success', () => { describe('success', () => {
beforeEach(() => { beforeEach(() => {
mock.onGet(labelsPath).replyOnce(httpStatusCodes.OK, filterLabels); mock.onGet(labelsEndpoint).replyOnce(httpStatusCodes.OK, filterLabels);
}); });
it('dispatches RECEIVE_LABELS_SUCCESS with received data', () => { it('dispatches RECEIVE_LABELS_SUCCESS with received data', () => {
return testAction( return testAction(
actions.fetchLabels, actions.fetchLabels,
null, null,
{ ...state, labelsPath }, { ...state, labelsEndpoint },
[ [
{ type: types.REQUEST_LABELS }, { type: types.REQUEST_LABELS },
{ type: types.RECEIVE_LABELS_SUCCESS, payload: filterLabels }, { type: types.RECEIVE_LABELS_SUCCESS, payload: filterLabels },
......
...@@ -11,7 +11,12 @@ const labels = filterLabels.map(convertObjectPropsToCamelCase); ...@@ -11,7 +11,12 @@ const labels = filterLabels.map(convertObjectPropsToCamelCase);
describe('Filters mutations', () => { describe('Filters mutations', () => {
beforeEach(() => { beforeEach(() => {
state = { initialTokens: {}, milestones: {}, authors: {}, labels: {}, assignees: {} }; state = {
authors: { selected: null },
milestones: { selected: null },
assignees: { selected: [] },
labels: { selected: [] },
};
}); });
afterEach(() => { afterEach(() => {
...@@ -19,9 +24,9 @@ describe('Filters mutations', () => { ...@@ -19,9 +24,9 @@ describe('Filters mutations', () => {
}); });
it.each` it.each`
mutation | stateKey | value mutation | stateKey | value
${types.SET_MILESTONES_PATH} | ${'milestonesPath'} | ${'new-milestone-path'} ${types.SET_MILESTONES_ENDPOINT} | ${'milestonesEndpoint'} | ${'new-milestone-endpoint'}
${types.SET_LABELS_PATH} | ${'labelsPath'} | ${'new-label-path'} ${types.SET_LABELS_ENDPOINT} | ${'labelsEndpoint'} | ${'new-label-endpoint'}
`('$mutation will set $stateKey=$value', ({ mutation, stateKey, value }) => { `('$mutation will set $stateKey=$value', ({ mutation, stateKey, value }) => {
mutations[mutation](state, value); mutations[mutation](state, value);
...@@ -29,15 +34,15 @@ describe('Filters mutations', () => { ...@@ -29,15 +34,15 @@ describe('Filters mutations', () => {
}); });
it.each` it.each`
mutation | rootKey | stateKey | value mutation | stateKey | value
${types.INITIALIZE} | ${'initialTokens'} | ${'selectedAuthor'} | ${null} ${types.SET_SELECTED_FILTERS} | ${'authors'} | ${null}
${types.INITIALIZE} | ${'initialTokens'} | ${'selectedMilestone'} | ${null} ${types.SET_SELECTED_FILTERS} | ${'milestones'} | ${null}
${types.INITIALIZE} | ${'initialTokens'} | ${'selectedAssignees'} | ${[]} ${types.SET_SELECTED_FILTERS} | ${'assignees'} | ${[]}
${types.INITIALIZE} | ${'initialTokens'} | ${'selectedLabels'} | ${[]} ${types.SET_SELECTED_FILTERS} | ${'labels'} | ${[]}
`('$mutation will set $stateKey with a given value', ({ mutation, rootKey, stateKey, value }) => { `('$mutation will set $stateKey with a given value', ({ mutation, stateKey, value }) => {
mutations[mutation](state); mutations[mutation](state, { [stateKey]: { selected: value } });
expect(state[rootKey][stateKey]).toEqual(value); expect(state[stateKey].selected).toEqual(value);
}); });
it.each` it.each`
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import setWindowLocation from 'helpers/set_window_location_helper'; import setWindowLocation from 'helpers/set_window_location_helper';
import { historyPushState } from '~/lib/utils/common_utils'; import { historyPushState } from '~/lib/utils/common_utils';
import { setUrlParams } from '~/lib/utils/url_utility'; import { mergeUrlParams } from '~/lib/utils/url_utility';
import UrlSyncComponent from '~/vue_shared/components/url_sync.vue'; import UrlSyncComponent from '~/vue_shared/components/url_sync.vue';
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
setUrlParams: jest.fn(val => `urlParams: ${val}`), mergeUrlParams: jest.fn((query, url) => `urlParams: ${query} ${url}`),
})); }));
jest.mock('~/lib/utils/common_utils', () => ({ jest.mock('~/lib/utils/common_utils', () => ({
...@@ -27,12 +27,12 @@ describe('url sync component', () => { ...@@ -27,12 +27,12 @@ describe('url sync component', () => {
}; };
function expectUrlSync(query) { function expectUrlSync(query) {
expect(setUrlParams).toHaveBeenCalledTimes(1); expect(mergeUrlParams).toHaveBeenCalledTimes(1);
expect(setUrlParams).toHaveBeenCalledWith(query, TEST_HOST, true); expect(mergeUrlParams).toHaveBeenCalledWith(query, TEST_HOST, { spreadArrays: true });
const setUrlParamsReturnValue = setUrlParams.mock.results[0].value; const mergeUrlParamsReturnValue = mergeUrlParams.mock.results[0].value;
expect(historyPushState).toHaveBeenCalledTimes(1); expect(historyPushState).toHaveBeenCalledTimes(1);
expect(historyPushState).toHaveBeenCalledWith(setUrlParamsReturnValue); expect(historyPushState).toHaveBeenCalledWith(mergeUrlParamsReturnValue);
} }
beforeEach(() => { beforeEach(() => {
...@@ -44,7 +44,7 @@ describe('url sync component', () => { ...@@ -44,7 +44,7 @@ describe('url sync component', () => {
describe('when the query is modified', () => { describe('when the query is modified', () => {
const newQuery = { foo: true }; const newQuery = { foo: true };
beforeEach(() => { beforeEach(() => {
setUrlParams.mockClear(); mergeUrlParams.mockClear();
historyPushState.mockClear(); historyPushState.mockClear();
wrapper.setProps({ query: newQuery }); wrapper.setProps({ query: newQuery });
}); });
......
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