Commit a6526096 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch '249591-mlunoe-fix-getter-data-structure-for-filter' into 'master'

Fix(Analytics Filter Bars): ensure correct queries

Closes #249591

See merge request gitlab-org/gitlab!42477
parents 684630e8 b61be345
...@@ -3,6 +3,7 @@ import * as types from './mutation_types'; ...@@ -3,6 +3,7 @@ import * as types from './mutation_types';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils'; import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';
import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
export const setProjectId = ({ commit }, projectId) => commit(types.SET_PROJECT_ID, projectId); export const setProjectId = ({ commit }, projectId) => commit(types.SET_PROJECT_ID, projectId);
...@@ -10,17 +11,21 @@ export const fetchMergeRequests = ({ commit, state, rootState }) => { ...@@ -10,17 +11,21 @@ export const fetchMergeRequests = ({ commit, state, rootState }) => {
commit(types.REQUEST_MERGE_REQUESTS); commit(types.REQUEST_MERGE_REQUESTS);
const { projectId, pageInfo } = state; const { projectId, pageInfo } = state;
const {
filters: {
milestones: { selected: selectedMilestone },
labels: { selectedList: selectedLabelList },
},
} = rootState;
const { selected: milestoneTitle } = rootState.filters.milestones; const filterBarQuery = filterToQueryObject({
const { selectedList: labelNames } = rootState.filters.labels; milestone_title: selectedMilestone,
label_name: selectedLabelList,
});
const params = { const params = {
project_id: projectId, project_id: projectId,
milestone_title: milestoneTitle?.operator === '=' ? milestoneTitle.value : null,
label_name: labelNames?.filter(l => l.operator === '=').map(l => l.value),
'not[label_name]': labelNames?.filter(l => l.operator === '!=').map(l => l.value),
'not[milestone_title]': milestoneTitle?.operator === '!=' ? milestoneTitle.value : null,
page: pageInfo.page, page: pageInfo.page,
...filterBarQuery,
}; };
return API.codeReviewAnalytics(params) return API.codeReviewAnalytics(params)
......
import dateFormat from 'dateformat'; import dateFormat from 'dateformat';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import httpStatus from '~/lib/utils/http_status'; import httpStatus from '~/lib/utils/http_status';
import { filterToQueryObject } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import { dateFormats } from '../../shared/constants'; import { dateFormats } from '../../shared/constants';
import { transformStagesForPathNavigation } from '../utils'; import { transformStagesForPathNavigation } from '../utils';
import { DEFAULT_VALUE_STREAM_ID } from '../constants'; import { DEFAULT_VALUE_STREAM_ID } from '../constants';
...@@ -20,21 +21,25 @@ export const cycleAnalyticsRequestParams = (state, getters) => { ...@@ -20,21 +21,25 @@ export const cycleAnalyticsRequestParams = (state, getters) => {
startDate = null, startDate = null,
endDate = null, endDate = null,
filters: { filters: {
authors: { selected: selectedAuthor = null }, authors: { selected: selectedAuthor },
milestones: { selected: selectedMilestone = null }, milestones: { selected: selectedMilestone },
assignees: { selectedList: selectedAssigneeList = [] }, assignees: { selectedList: selectedAssigneeList },
labels: { selectedList: selectedLabelList = [] }, labels: { selectedList: selectedLabelList },
}, },
} = state; } = state;
const filterBarQuery = filterToQueryObject({
milestone_title: selectedMilestone,
author_username: selectedAuthor,
label_name: selectedLabelList,
assignee_username: selectedAssigneeList,
});
return { return {
project_ids: getters.selectedProjectIds, project_ids: getters.selectedProjectIds,
created_after: startDate ? dateFormat(startDate, dateFormats.isoDate) : null, created_after: startDate ? dateFormat(startDate, dateFormats.isoDate) : null,
created_before: endDate ? dateFormat(endDate, dateFormats.isoDate) : null, created_before: endDate ? dateFormat(endDate, dateFormats.isoDate) : null,
author_username: selectedAuthor, ...filterBarQuery,
milestone_title: selectedMilestone,
assignee_username: selectedAssigneeList,
label_name: selectedLabelList,
}; };
}; };
......
...@@ -8,9 +8,10 @@ import initialFiltersState from 'ee/analytics/shared/store/modules/filters/state ...@@ -8,9 +8,10 @@ import initialFiltersState from 'ee/analytics/shared/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 * as utils from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; import * as utils from '~/vue_shared/components/filtered_search_bar/filtered_search_utils';
import UrlSync from '~/vue_shared/components/url_sync.vue'; import UrlSync from '~/vue_shared/components/url_sync.vue';
import { filterMilestones, filterLabels } from '../../shared/store/modules/filters/mock_data';
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 { filterMilestones, filterLabels } from '../../shared/store/modules/filters/mock_data';
import { getFilterParams, getFilterValues } from '../../shared/store/modules/filters/test_helper';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -38,20 +39,10 @@ async function shouldMergeUrlParams(wrapper, result) { ...@@ -38,20 +39,10 @@ async function shouldMergeUrlParams(wrapper, result) {
expect(commonUtils.historyPushState).toHaveBeenCalled(); expect(commonUtils.historyPushState).toHaveBeenCalled();
} }
function getFilterParams(tokens, operator, key = 'value') { const selectedMilestoneParams = getFilterParams(filterMilestones);
return tokens.map(token => { const unselectedMilestoneParams = getFilterParams(filterMilestones, { operator: '!=' });
return { [key]: token.title, operator }; const selectedLabelParams = getFilterParams(filterLabels);
}); const unselectedLabelParams = getFilterParams(filterLabels, { operator: '!=' });
}
function getFilterValues(tokens) {
return tokens.map(token => token.title);
}
const selectedMilestoneParams = getFilterParams(filterMilestones, '=');
const unselectedMilestoneParams = getFilterParams(filterMilestones, '!=');
const selectedLabelParams = getFilterParams(filterLabels, '=');
const unselectedLabelParams = getFilterParams(filterLabels, '!=');
const milestoneValues = getFilterValues(filterMilestones); const milestoneValues = getFilterValues(filterMilestones);
const labelValues = getFilterValues(filterLabels); const labelValues = getFilterValues(filterLabels);
...@@ -163,9 +154,12 @@ describe('Filter bar', () => { ...@@ -163,9 +154,12 @@ describe('Filter bar', () => {
it('clicks on the search button, setFilters is dispatched', () => { it('clicks on the search button, setFilters is dispatched', () => {
const filters = [ const filters = [
{ type: 'milestone', value: getFilterParams(filterMilestones, '=', 'data')[2] }, { type: 'milestone', value: getFilterParams(filterMilestones, { key: 'data' })[2] },
{ type: 'labels', value: getFilterParams(filterLabels, '=', 'data')[2] }, { type: 'labels', value: getFilterParams(filterLabels, { key: 'data' })[2] },
{ type: 'labels', value: getFilterParams(filterLabels, '!=', 'data')[4] }, {
type: 'labels',
value: getFilterParams(filterLabels, { key: 'data', operator: '!=' })[4],
},
]; ];
findFilteredSearch().vm.$emit('onFilter', filters); findFilteredSearch().vm.$emit('onFilter', filters);
......
...@@ -8,9 +8,23 @@ import { ...@@ -8,9 +8,23 @@ import {
issueStage, issueStage,
stageMedians, stageMedians,
} from '../mock_data'; } from '../mock_data';
import {
filterMilestones,
filterUsers,
filterLabels,
} from '../../shared/store/modules/filters/mock_data';
import { getFilterParams, getFilterValues } from '../../shared/store/modules/filters/test_helper';
let state = null; let state = null;
const selectedMilestoneParams = getFilterParams(filterMilestones);
const selectedLabelParams = getFilterParams(filterLabels);
const selectedUserParams = getFilterParams(filterUsers, { prop: 'name' });
const milestoneValues = getFilterValues(filterMilestones);
const labelValues = getFilterValues(filterLabels);
const userValues = getFilterValues(filterUsers, { prop: 'name' });
describe('Cycle analytics getters', () => { describe('Cycle analytics getters', () => {
describe('hasNoAccessError', () => { describe('hasNoAccessError', () => {
beforeEach(() => { beforeEach(() => {
...@@ -71,11 +85,6 @@ describe('Cycle analytics getters', () => { ...@@ -71,11 +85,6 @@ describe('Cycle analytics getters', () => {
}); });
describe('cycleAnalyticsRequestParams', () => { describe('cycleAnalyticsRequestParams', () => {
const selectedAuthor = 'Gohan';
const selectedMilestone = 'SSJ4';
const selectedAssigneeList = ['krillin', 'gotenks'];
const selectedLabelList = ['cell saga', 'buu saga'];
beforeEach(() => { beforeEach(() => {
const fullPath = 'cool-beans'; const fullPath = 'cool-beans';
state = { state = {
...@@ -86,10 +95,10 @@ describe('Cycle analytics getters', () => { ...@@ -86,10 +95,10 @@ describe('Cycle analytics getters', () => {
endDate, endDate,
selectedProjects, selectedProjects,
filters: { filters: {
authors: { selected: selectedAuthor }, authors: { selected: selectedUserParams[0] },
milestones: { selected: selectedMilestone }, milestones: { selected: selectedMilestoneParams[1] },
assignees: { selectedList: selectedAssigneeList }, assignees: { selectedList: selectedUserParams[1] },
labels: { selectedList: selectedLabelList }, labels: { selectedList: selectedLabelParams },
}, },
}; };
}); });
...@@ -99,10 +108,10 @@ describe('Cycle analytics getters', () => { ...@@ -99,10 +108,10 @@ describe('Cycle analytics getters', () => {
${'created_after'} | ${'2018-12-15'} ${'created_after'} | ${'2018-12-15'}
${'created_before'} | ${'2019-01-14'} ${'created_before'} | ${'2019-01-14'}
${'project_ids'} | ${[1, 2]} ${'project_ids'} | ${[1, 2]}
${'author_username'} | ${selectedAuthor} ${'author_username'} | ${userValues[0]}
${'milestone_title'} | ${selectedMilestone} ${'milestone_title'} | ${milestoneValues[1]}
${'assignee_username'} | ${selectedAssigneeList} ${'assignee_username'} | ${userValues[1]}
${'label_name'} | ${selectedLabelList} ${'label_name'} | ${labelValues}
`('should return the $param with value $value', ({ param, value }) => { `('should return the $param with value $value', ({ param, value }) => {
expect( expect(
getters.cycleAnalyticsRequestParams(state, { selectedProjectIds: [1, 2] }), getters.cycleAnalyticsRequestParams(state, { selectedProjectIds: [1, 2] }),
...@@ -113,8 +122,8 @@ describe('Cycle analytics getters', () => { ...@@ -113,8 +122,8 @@ describe('Cycle analytics getters', () => {
it.each` it.each`
param | stateKey | value param | stateKey | value
${'assignee_username'} | ${'selectedAssigneeList'} | ${[]} ${'assignee_username'} | ${'userValues'} | ${[]}
${'label_name'} | ${'selectedLabelList'} | ${[]} ${'label_name'} | ${'labelValues'} | ${[]}
`('should not return the $param when $stateKey=$value', ({ param, stateKey, value }) => { `('should not return the $param when $stateKey=$value', ({ param, stateKey, value }) => {
expect( expect(
getters.cycleAnalyticsRequestParams( getters.cycleAnalyticsRequestParams(
......
...@@ -17,6 +17,7 @@ import { ...@@ -17,6 +17,7 @@ import {
filterLabels, filterLabels,
filterUsers, filterUsers,
} from '../../shared/store/modules/filters/mock_data'; } from '../../shared/store/modules/filters/mock_data';
import { getFilterParams, getFilterValues } from '../../shared/store/modules/filters/test_helper';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -60,18 +61,6 @@ async function shouldMergeUrlParams(wrapper, result) { ...@@ -60,18 +61,6 @@ async function shouldMergeUrlParams(wrapper, result) {
expect(commonUtils.historyPushState).toHaveBeenCalled(); expect(commonUtils.historyPushState).toHaveBeenCalled();
} }
function getFilterParams(tokens, options = {}) {
const { key = 'value', operator = '=', prop = 'title' } = options;
return tokens.map(token => {
return { [key]: token[prop], operator };
});
}
function getFilterValues(tokens, options = {}) {
const { prop = 'title' } = options;
return tokens.map(token => token[prop]);
}
const selectedBranchParams = getFilterParams(mockBranches, { prop: 'name' }); const selectedBranchParams = getFilterParams(mockBranches, { prop: 'name' });
const selectedMilestoneParams = getFilterParams(filterMilestones); const selectedMilestoneParams = getFilterParams(filterMilestones);
const selectedLabelParams = getFilterParams(filterLabels); const selectedLabelParams = getFilterParams(filterLabels);
......
export function getFilterParams(tokens, options = {}) {
const { key = 'value', operator = '=', prop = 'title' } = options;
return tokens.map(token => {
return { [key]: token[prop], operator };
});
}
export function getFilterValues(tokens, options = {}) {
const { prop = 'title' } = options;
return tokens.map(token => token[prop]);
}
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