Commit 4262129a authored by Brandon Labuschagne's avatar Brandon Labuschagne

Merge branch '218624-filter-roadmap-by-confidentiality' into 'master'

Add confidential filter token in Roadmap

See merge request gitlab-org/gitlab!51196
parents 4a5ce587 84be43d0
......@@ -256,7 +256,7 @@ export default {
return {
...filter,
value: {
data: stripQuotes(valueString),
data: typeof valueString === 'string' ? stripQuotes(valueString) : valueString,
operator: filter.value.operator,
},
};
......
......@@ -67,8 +67,9 @@ You can also filter epics in the Roadmap view by:
- Author
- Label
- Milestone
- Confidentiality of epics
![roadmap date range in weeks](img/roadmap_filters_v13_7.png)
![roadmap date range in weeks](img/roadmap_filters_v13_8.png)
Roadmaps can also be [visualized inside an epic](../epics/index.md#roadmap-in-epics).
......
......@@ -6,6 +6,7 @@ import {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlFilteredSearchToken,
} from '@gitlab/ui';
import { __ } from '~/locale';
......@@ -132,10 +133,23 @@ export default {
});
},
},
{
type: 'confidential',
icon: 'eye-slash',
title: __('Confidential'),
unique: true,
token: GlFilteredSearchToken,
operators: [{ value: '=', description: __('is'), default: 'true' }],
options: [
{ icon: 'eye-slash', value: true, title: __('Yes') },
{ icon: 'eye', value: false, title: __('No') },
],
},
];
},
getFilteredSearchValue() {
const { authorUsername, labelName, milestoneTitle, search } = this.filterParams || {};
const { authorUsername, labelName, milestoneTitle, confidential, search } =
this.filterParams || {};
const filteredSearchValue = [];
if (authorUsername) {
......@@ -161,6 +175,13 @@ export default {
);
}
if (confidential !== undefined) {
filteredSearchValue.push({
type: 'confidential',
value: { data: confidential },
});
}
if (search) {
filteredSearchValue.push(search);
}
......@@ -169,7 +190,8 @@ export default {
},
updateUrl() {
const queryParams = urlParamsToObject(window.location.search);
const { authorUsername, labelName, milestoneTitle, search } = this.filterParams || {};
const { authorUsername, labelName, milestoneTitle, confidential, search } =
this.filterParams || {};
queryParams.state = this.epicsState;
queryParams.sort = this.sortedBy;
......@@ -191,6 +213,12 @@ export default {
queryParams['label_name[]'] = labelName;
}
if (confidential !== undefined) {
queryParams.confidential = confidential;
} else {
delete queryParams.confidential;
}
if (search) {
queryParams.search = search;
} else {
......@@ -229,6 +257,9 @@ export default {
case 'label_name':
labels.push(filter.value.data);
break;
case 'confidential':
filterParams.confidential = filter.value.data;
break;
default:
break;
}
......
......@@ -9,6 +9,7 @@ fragment BaseEpic on Epic {
dueDate
hasChildren
hasParent
confidential
descendantWeightSum {
closedIssues
openedIssues
......
......@@ -9,6 +9,7 @@ query epicChildEpics(
$dueDate: Time
$labelName: [String!] = []
$authorUsername: String = ""
$confidential: Boolean
$search: String = ""
) {
group(fullPath: $fullPath) {
......@@ -25,6 +26,7 @@ query epicChildEpics(
endDate: $dueDate
labelName: $labelName
authorUsername: $authorUsername
confidential: $confidential
search: $search
) {
edges {
......
......@@ -9,6 +9,7 @@ query groupEpics(
$labelName: [String!] = []
$authorUsername: String = ""
$milestoneTitle: String = ""
$confidential: Boolean
$search: String = ""
) {
group(fullPath: $fullPath) {
......@@ -22,6 +23,7 @@ query groupEpics(
labelName: $labelName
authorUsername: $authorUsername
milestoneTitle: $milestoneTitle
confidential: $confidential
search: $search
) {
edges {
......
......@@ -57,11 +57,17 @@ export default () => {
supportedPresetTypes.indexOf(dataset.presetType) > -1
? dataset.presetType
: PRESET_TYPES.MONTHS;
const filterParams = Object.assign(
convertObjectPropsToCamelCase(urlParamsToObject(window.location.search.substring(1)), {
const rawFilterParams = urlParamsToObject(window.location.search.substring(1));
const filterParams = {
...convertObjectPropsToCamelCase(rawFilterParams, {
dropKeys: ['scope', 'utf8', 'state', 'sort', 'layout'], // These keys are unsupported/unnecessary
}),
);
// We shall put parsed value of `confidential` only
// when it is defined.
...(rawFilterParams.confidential && {
confidential: parseBoolean(rawFilterParams.confidential),
}),
};
const timeframe = getTimeframeForPreset(
presetType,
window.innerWidth - el.offsetLeft - EPIC_DETAILS_CELL_WIDTH,
......
---
title: Add confidential filter token in Roadmap
merge_request: 51196
author:
type: added
import { GlSegmentedControl, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlSegmentedControl, GlDropdown, GlDropdownItem, GlFilteredSearchToken } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
......@@ -89,6 +89,7 @@ describe('RoadmapFilters', () => {
authorUsername: 'root',
labelName: ['Bug'],
milestoneTitle: '4.0',
confidential: true,
});
wrapper.vm.$store.dispatch('setSortedBy', 'end_date_asc');
......@@ -97,7 +98,7 @@ describe('RoadmapFilters', () => {
wrapper.vm.updateUrl();
expect(global.window.location.href).toBe(
`${TEST_HOST}/?state=${EPICS_STATES.CLOSED}&sort=end_date_asc&author_username=root&milestone_title=4.0&label_name%5B%5D=Bug`,
`${TEST_HOST}/?state=${EPICS_STATES.CLOSED}&sort=end_date_asc&author_username=root&milestone_title=4.0&label_name%5B%5D=Bug&confidential=true`,
);
});
});
......@@ -143,10 +144,18 @@ describe('RoadmapFilters', () => {
type: 'author_username',
value: { data: 'root' },
},
{
type: 'milestone_title',
value: { data: '4.0' },
},
{
type: 'label_name',
value: { data: 'Bug' },
},
{
type: 'confidential',
value: { data: true },
},
];
let filteredSearchBar;
......@@ -192,6 +201,18 @@ describe('RoadmapFilters', () => {
operators: [{ value: '=', description: 'is', default: 'true' }],
fetchMilestones: expect.any(Function),
},
{
type: 'confidential',
icon: 'eye-slash',
title: 'Confidential',
unique: true,
token: GlFilteredSearchToken,
operators: [{ value: '=', description: 'is', default: 'true' }],
options: [
{ icon: 'eye-slash', value: true, title: 'Yes' },
{ icon: 'eye', value: false, title: 'No' },
],
},
]);
});
......@@ -220,6 +241,8 @@ describe('RoadmapFilters', () => {
wrapper.vm.$store.dispatch('setFilterParams', {
authorUsername: 'root',
labelName: ['Bug'],
milestoneTitle: '4.0',
confidential: true,
});
await wrapper.vm.$nextTick();
......@@ -241,6 +264,8 @@ describe('RoadmapFilters', () => {
expect(wrapper.vm.setFilterParams).toHaveBeenCalledWith({
authorUsername: 'root',
labelName: ['Bug'],
milestoneTitle: '4.0',
confidential: true,
});
expect(wrapper.vm.fetchEpics).toHaveBeenCalled();
expect(wrapper.vm.updateUrl).toHaveBeenCalled();
......
......@@ -25,6 +25,7 @@ import {
tokenValueLabel,
tokenValueMilestone,
tokenValueMembership,
tokenValueConfidential,
} from './mock_data';
jest.mock('~/vue_shared/components/filtered_search_bar/filtered_search_utils', () => ({
......@@ -227,12 +228,13 @@ describe('FilteredSearchBarRoot', () => {
});
describe('removeQuotesEnclosure', () => {
const mockFilters = [tokenValueAuthor, tokenValueLabel, 'foo'];
const mockFilters = [tokenValueAuthor, tokenValueLabel, tokenValueConfidential, 'foo'];
it('returns filter array with unescaped strings for values which have spaces', () => {
expect(wrapper.vm.removeQuotesEnclosure(mockFilters)).toEqual([
tokenValueAuthor,
tokenValueLabel,
tokenValueConfidential,
'foo',
]);
});
......
......@@ -155,6 +155,14 @@ export const tokenValueMembership = {
},
};
export const tokenValueConfidential = {
type: 'confidential',
value: {
operator: '=',
data: true,
},
};
export const tokenValuePlain = {
type: 'filtered-search-term',
value: { data: 'foo' },
......
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