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 {
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
......@@ -71,13 +72,14 @@ export default {
acc[type] = [];
}
acc[type].push(tokenValue);
acc[type].push({ value: tokenValue, operator });
return acc;
}, {});
},
filteredSearchSubmit(filters) {
const { label, milestone } = this.processFilters(filters);
this.setFilters({ label_name: label, milestone_title: milestone });
const { label: labelNames, milestone } = this.processFilters(filters);
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
import { urlParamsToObject } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import store from './store';
import transformFilters from './utils';
export default class FilteredSearchCodeReviewAnalytics extends FilteredSearchManager {
constructor() {
......@@ -23,6 +24,11 @@ export default class FilteredSearchCodeReviewAnalytics extends FilteredSearchMan
*/
updateObject = 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 }) => {
});
};
export const setFilters = ({ commit, dispatch }, { label_name, milestone_title }) => {
commit(types.SET_FILTERS, { selectedLabels: label_name, selectedMilestone: milestone_title });
export const setFilters = ({ commit, dispatch }, { labelNames, milestoneTitle }) => {
commit(types.SET_FILTERS, {
selectedLabels: labelNames,
selectedMilestone: milestoneTitle,
});
dispatch('mergeRequests/setPage', 1, { root: true });
dispatch('mergeRequests/fetchMergeRequests', null, { root: true });
......
......@@ -12,12 +12,14 @@ export const fetchMergeRequests = ({ commit, state, rootState }) => {
const { projectId, pageInfo } = state;
const { selected: milestoneTitle } = rootState.filters.milestones;
const { selected: labelName } = rootState.filters.labels;
const { selected: labelNames } = rootState.filters.labels;
const params = {
project_id: projectId,
milestone_title: Array.isArray(milestoneTitle) ? milestoneTitle.join('') : milestoneTitle,
label_name: labelName,
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,
};
......
/**
* 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
before_action :authorize_read_code_review_analytics!
before_action do
push_frontend_feature_flag(:code_review_analytics_has_new_search)
push_frontend_feature_flag(:not_issuable_queries, @project, default_enabled: true)
end
def index
......
......@@ -109,8 +109,8 @@ describe('FilteredSearchBar', () => {
expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(),
{
label_name: ['my-label'],
milestone_title: ['my-milestone'],
labelNames: [{ value: 'my-label', operator: '=' }],
milestoneTitle: { value: 'my-milestone', operator: '=' },
},
undefined,
);
......@@ -124,8 +124,8 @@ describe('FilteredSearchBar', () => {
expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(),
{
label_name: undefined,
milestone_title: ['milestone with spaces'],
labelNames: undefined,
milestoneTitle: { value: 'milestone with spaces', operator: '=' },
},
undefined,
);
......@@ -139,8 +139,8 @@ describe('FilteredSearchBar', () => {
expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(),
{
label_name: undefined,
milestone_title: ['milestone with spaces'],
labelNames: undefined,
milestoneTitle: { value: 'milestone with spaces', operator: '=' },
},
undefined,
);
......@@ -154,8 +154,8 @@ describe('FilteredSearchBar', () => {
expect(setFiltersMock).toHaveBeenCalledWith(
expect.anything(),
{
label_name: undefined,
milestone_title: ['milestone "with" spaces'],
labelNames: undefined,
milestoneTitle: { value: 'milestone "with" spaces', operator: '=' },
},
undefined,
);
......
......@@ -149,13 +149,16 @@ describe('Code review analytics filters actions', () => {
});
describe('setFilters', () => {
const selectedMilestone = 'my milestone';
const selectedLabels = ['first label', 'second label'];
const selectedMilestone = { value: 'my milestone', operator: '=' };
const selectedLabels = [
{ value: 'first label', operator: '=' },
{ value: 'second label', operator: '!=' },
];
it('commits the SET_FILTERS mutation', () => {
testAction(
actions.setFilters,
{ milestone_title: selectedMilestone, label_name: selectedLabels },
{ labelNames: selectedLabels, milestoneTitle: selectedMilestone },
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