Commit c6e772d8 authored by Illya Klymov's avatar Illya Klymov

Merge branch 'mw-cr-add-not-filter-support' into 'master'

[FE] Introduce not filters for code review analytics

Closes #206955

See merge request gitlab-org/gitlab!31891
parents 128724b6 3f6cec61
...@@ -57,6 +57,7 @@ export default { ...@@ -57,6 +57,7 @@ export default {
processFilters(filters) { processFilters(filters) {
return filters.reduce((acc, token) => { return filters.reduce((acc, token) => {
const { type, value } = token; const { type, value } = token;
const { operator } = value;
let tokenValue = value.data; let tokenValue = value.data;
// remove wrapping double quotes which were added for token values that include spaces // remove wrapping double quotes which were added for token values that include spaces
...@@ -71,13 +72,14 @@ export default { ...@@ -71,13 +72,14 @@ export default {
acc[type] = []; acc[type] = [];
} }
acc[type].push(tokenValue); acc[type].push({ value: tokenValue, operator });
return acc; return acc;
}, {}); }, {});
}, },
filteredSearchSubmit(filters) { filteredSearchSubmit(filters) {
const { label, milestone } = this.processFilters(filters); const { label: labelNames, milestone } = this.processFilters(filters);
this.setFilters({ label_name: label, milestone_title: milestone }); const milestoneTitle = milestone ? milestone[0] : null;
this.setFilters({ labelNames, milestoneTitle });
}, },
}, },
}; };
......
...@@ -3,6 +3,7 @@ import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_ma ...@@ -3,6 +3,7 @@ import FilteredSearchManager from 'ee_else_ce/filtered_search/filtered_search_ma
import { urlParamsToObject } from '~/lib/utils/common_utils'; import { urlParamsToObject } from '~/lib/utils/common_utils';
import { __ } from '~/locale'; import { __ } from '~/locale';
import store from './store'; import store from './store';
import transformFilters from './utils';
export default class FilteredSearchCodeReviewAnalytics extends FilteredSearchManager { export default class FilteredSearchCodeReviewAnalytics extends FilteredSearchManager {
constructor() { constructor() {
...@@ -23,6 +24,11 @@ export default class FilteredSearchCodeReviewAnalytics extends FilteredSearchMan ...@@ -23,6 +24,11 @@ export default class FilteredSearchCodeReviewAnalytics extends FilteredSearchMan
*/ */
updateObject = path => { updateObject = path => {
const filters = urlParamsToObject(path); const filters = urlParamsToObject(path);
store.dispatch('filters/setFilters', filters); const { selectedLabels, selectedMilestone } = transformFilters(filters);
store.dispatch('filters/setFilters', {
labelNames: selectedLabels,
milestoneTitle: selectedMilestone,
});
}; };
} }
...@@ -39,8 +39,11 @@ export const fetchLabels = ({ commit, state }) => { ...@@ -39,8 +39,11 @@ export const fetchLabels = ({ commit, state }) => {
}); });
}; };
export const setFilters = ({ commit, dispatch }, { label_name, milestone_title }) => { export const setFilters = ({ commit, dispatch }, { labelNames, milestoneTitle }) => {
commit(types.SET_FILTERS, { selectedLabels: label_name, selectedMilestone: milestone_title }); commit(types.SET_FILTERS, {
selectedLabels: labelNames,
selectedMilestone: milestoneTitle,
});
dispatch('mergeRequests/setPage', 1, { root: true }); dispatch('mergeRequests/setPage', 1, { root: true });
dispatch('mergeRequests/fetchMergeRequests', null, { root: true }); dispatch('mergeRequests/fetchMergeRequests', null, { root: true });
......
...@@ -12,12 +12,14 @@ export const fetchMergeRequests = ({ commit, state, rootState }) => { ...@@ -12,12 +12,14 @@ export const fetchMergeRequests = ({ commit, state, rootState }) => {
const { projectId, pageInfo } = state; const { projectId, pageInfo } = state;
const { selected: milestoneTitle } = rootState.filters.milestones; const { selected: milestoneTitle } = rootState.filters.milestones;
const { selected: labelName } = rootState.filters.labels; const { selected: labelNames } = rootState.filters.labels;
const params = { const params = {
project_id: projectId, project_id: projectId,
milestone_title: Array.isArray(milestoneTitle) ? milestoneTitle.join('') : milestoneTitle, milestone_title: milestoneTitle?.operator === '=' ? milestoneTitle.value : null,
label_name: labelName, 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,
}; };
......
/**
* Transforms a given filters object
* into the following structure:
* {
* selectedLabels: [{ value: 'foo', operator: ''}, { value: 'bar', operator: '!=' }],
* selectedMilestone: { value: 'milestone', operator: '='}
* }
*
* @param {Object} filters
* @returns {Object}
*/
const transformFilters = filters => {
const {
label_name: labelNames,
milestone_title: milestoneTitle,
'not[label_name]': notLabelNames,
'not[milestone_title]': notMilestoneTitle,
} = filters;
let selectedLabels = labelNames?.map(label => ({ value: label, operator: '=' }));
let selectedMilestone = null;
if (notLabelNames) {
selectedLabels = [
...selectedLabels,
...notLabelNames.map(label => ({ value: label, operator: '!=' })),
];
}
if (milestoneTitle) {
selectedMilestone = { value: milestoneTitle, operator: '=' };
} else if (notMilestoneTitle) {
selectedMilestone = { value: notMilestoneTitle, operator: '!=' };
}
return { selectedLabels, selectedMilestone };
};
export default transformFilters;
...@@ -6,6 +6,7 @@ module Projects ...@@ -6,6 +6,7 @@ module Projects
before_action :authorize_read_code_review_analytics! before_action :authorize_read_code_review_analytics!
before_action do before_action do
push_frontend_feature_flag(:code_review_analytics_has_new_search) push_frontend_feature_flag(:code_review_analytics_has_new_search)
push_frontend_feature_flag(:not_issuable_queries, @project, default_enabled: true)
end end
def index def index
......
...@@ -109,8 +109,8 @@ describe('FilteredSearchBar', () => { ...@@ -109,8 +109,8 @@ describe('FilteredSearchBar', () => {
expect(setFiltersMock).toHaveBeenCalledWith( expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ {
label_name: ['my-label'], labelNames: [{ value: 'my-label', operator: '=' }],
milestone_title: ['my-milestone'], milestoneTitle: { value: 'my-milestone', operator: '=' },
}, },
undefined, undefined,
); );
...@@ -124,8 +124,8 @@ describe('FilteredSearchBar', () => { ...@@ -124,8 +124,8 @@ describe('FilteredSearchBar', () => {
expect(setFiltersMock).toHaveBeenCalledWith( expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ {
label_name: undefined, labelNames: undefined,
milestone_title: ['milestone with spaces'], milestoneTitle: { value: 'milestone with spaces', operator: '=' },
}, },
undefined, undefined,
); );
...@@ -139,8 +139,8 @@ describe('FilteredSearchBar', () => { ...@@ -139,8 +139,8 @@ describe('FilteredSearchBar', () => {
expect(setFiltersMock).toHaveBeenCalledWith( expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ {
label_name: undefined, labelNames: undefined,
milestone_title: ['milestone with spaces'], milestoneTitle: { value: 'milestone with spaces', operator: '=' },
}, },
undefined, undefined,
); );
...@@ -154,8 +154,8 @@ describe('FilteredSearchBar', () => { ...@@ -154,8 +154,8 @@ describe('FilteredSearchBar', () => {
expect(setFiltersMock).toHaveBeenCalledWith( expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
{ {
label_name: undefined, labelNames: undefined,
milestone_title: ['milestone "with" spaces'], milestoneTitle: { value: 'milestone "with" spaces', operator: '=' },
}, },
undefined, undefined,
); );
......
...@@ -149,13 +149,16 @@ describe('Code review analytics filters actions', () => { ...@@ -149,13 +149,16 @@ describe('Code review analytics filters actions', () => {
}); });
describe('setFilters', () => { describe('setFilters', () => {
const selectedMilestone = 'my milestone'; const selectedMilestone = { value: 'my milestone', operator: '=' };
const selectedLabels = ['first label', 'second label']; const selectedLabels = [
{ value: 'first label', operator: '=' },
{ value: 'second label', operator: '!=' },
];
it('commits the SET_FILTERS mutation', () => { it('commits the SET_FILTERS mutation', () => {
testAction( testAction(
actions.setFilters, actions.setFilters,
{ milestone_title: selectedMilestone, label_name: selectedLabels }, { labelNames: selectedLabels, milestoneTitle: selectedMilestone },
state, state,
[ [
{ {
......
import transformFilters from 'ee/analytics/code_review_analytics/utils';
describe('CodeReviewAnalytics utils', () => {
describe('transformFilters', () => {
describe('when milestone_title and label_name filters are present', () => {
it('creates a selectedMilestone object and a selectedLabels array', () => {
const filters = {
milestone_title: 'my-milestone',
label_name: ['my-label', 'another label'],
};
expect(transformFilters(filters)).toEqual({
selectedMilestone: { value: 'my-milestone', operator: '=' },
selectedLabels: [
{ value: 'my-label', operator: '=' },
{ value: 'another label', operator: '=' },
],
});
});
});
describe('when "not[label_name]" filter is present', () => {
it('applies the "!=" operator to the selectedLabels array', () => {
const filters = {
milestone_title: 'my-milestone',
label_name: ['my-label'],
'not[label_name]': ['another label'],
};
expect(transformFilters(filters)).toEqual({
selectedMilestone: { value: 'my-milestone', operator: '=' },
selectedLabels: [
{ value: 'my-label', operator: '=' },
{ value: 'another label', operator: '!=' },
],
});
});
});
describe('when "not[milestone_title]" filter is present', () => {
it('applies the "!=" operator to the selectedMilestone object', () => {
const filters = {
'not[milestone_title]': 'my-milestone',
label_name: ['my-label'],
};
expect(transformFilters(filters)).toEqual({
selectedMilestone: { value: 'my-milestone', operator: '!=' },
selectedLabels: [{ value: 'my-label', operator: '=' }],
});
});
});
});
});
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