Commit 31b126c1 authored by Lukas Eipert's avatar Lukas Eipert

Dashboard: Convert selected options into a Set

A Set is per definition unique, so it makes it a bit easier to work
with. Additionally it allows us to overwrite the complete Selection at
once without iterating over all the options.

Also add missing spec for the moderator.
parent 357573ba
...@@ -26,6 +26,9 @@ export default { ...@@ -26,6 +26,9 @@ export default {
filter() { filter() {
return this.getFilter(this.filterId); return this.getFilter(this.filterId);
}, },
selection() {
return this.getFilter(this.filterId).selection;
},
selectedOptionText() { selectedOptionText() {
return this.getSelectedOptionNames(this.filterId) || '-'; return this.getSelectedOptionNames(this.filterId) || '-';
}, },
...@@ -39,6 +42,9 @@ export default { ...@@ -39,6 +42,9 @@ export default {
}); });
this.$emit('change'); this.$emit('change');
}, },
isSelected(option) {
return this.selection.has(option.id);
},
}, },
}; };
</script> </script>
...@@ -57,11 +63,11 @@ export default { ...@@ -57,11 +63,11 @@ export default {
@click="clickFilter(option)" @click="clickFilter(option)"
> >
<icon <icon
v-if="option.selected" v-if="isSelected(option)"
class="vertical-align-middle js-check" class="vertical-align-middle js-check"
name="mobile-issue-close" name="mobile-issue-close"
/> />
<span class="vertical-align-middle" :class="{ 'prepend-left-20': !option.selected }">{{ <span class="vertical-align-middle" :class="{ 'prepend-left-20': !isSelected(option) }">{{
option.name option.name
}}</span> }}</span>
</gl-dropdown-item> </gl-dropdown-item>
......
import Vue from 'vue'; import Vue from 'vue';
import GroupSecurityDashboardApp from './components/app.vue'; import GroupSecurityDashboardApp from './components/app.vue';
import store from './store'; import createStore from './store';
export default () => { export default () => {
const el = document.getElementById('js-group-security-dashboard'); const el = document.getElementById('js-group-security-dashboard');
const store = createStore();
return new Vue({ return new Vue({
el, el,
store, store,
......
...@@ -7,14 +7,17 @@ import vulnerabilities from './modules/vulnerabilities/index'; ...@@ -7,14 +7,17 @@ import vulnerabilities from './modules/vulnerabilities/index';
Vue.use(Vuex); Vue.use(Vuex);
const store = new Vuex.Store({ export default () => {
const store = new Vuex.Store({
modules: { modules: {
filters, filters,
projects, projects,
vulnerabilities, vulnerabilities,
}, },
}); });
configureModerator(store);
export default () => store; configureModerator(store);
return store;
};
...@@ -4,23 +4,21 @@ export default function configureModerator(store) { ...@@ -4,23 +4,21 @@ export default function configureModerator(store) {
store.subscribe(({ type, payload }) => { store.subscribe(({ type, payload }) => {
switch (type) { switch (type) {
case `projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`: case `projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`:
return store.dispatch('filters/setFilterOptions', { store.dispatch('filters/setFilterOptions', {
filterId: 'project_id', filterId: 'project_id',
options: [ options: [
{ {
name: 'All', name: 'All',
id: 'all', id: 'all',
selected: true,
}, },
...payload.projects.map(project => ({ ...payload.projects.map(project => ({
name: project.name, name: project.name,
id: project.id.toString(), id: project.id.toString(),
selected: false,
})), })),
], ],
}); });
break;
default: default:
return null;
} }
}); });
} }
...@@ -2,11 +2,10 @@ import { sprintf, __ } from '~/locale'; ...@@ -2,11 +2,10 @@ import { sprintf, __ } from '~/locale';
export const getFilter = state => filterId => state.filters.find(filter => filter.id === filterId); export const getFilter = state => filterId => state.filters.find(filter => filter.id === filterId);
export const getSelectedOptions = (state, getters) => filterId => export const getSelectedOptions = (state, getters) => filterId => {
getters.getFilter(filterId).options.filter(option => option.selected); const filter = getters.getFilter(filterId);
return filter.options.filter(option => filter.selection.has(option.id));
export const getSelectedOptionIds = (state, getters) => filterId => };
getters.getSelectedOptions(filterId).map(option => option.id);
export const getSelectedOptionNames = (state, getters) => filterId => { export const getSelectedOptionNames = (state, getters) => filterId => {
const selectedOptions = getters.getSelectedOptions(filterId); const selectedOptions = getters.getSelectedOptions(filterId);
...@@ -21,22 +20,17 @@ export const getSelectedOptionNames = (state, getters) => filterId => { ...@@ -21,22 +20,17 @@ export const getSelectedOptionNames = (state, getters) => filterId => {
: firstOption; : firstOption;
}; };
export const getFilterIds = state => state.filters.map(filter => filter.id);
/** /**
* Loops through all the filters and returns all the active ones * Loops through all the filters and returns all the active ones
* stripping out any that are set to 'all' * stripping out any that are set to 'all'
* @returns Object * @returns Object
* e.g. { type: ['sast'], severity: ['high', 'medium'] } * e.g. { type: ['sast'], severity: ['high', 'medium'] }
*/ */
export const activeFilters = (state, getters) => export const activeFilters = state =>
getters.getFilterIds.reduce( state.filters.reduce((acc, filter) => {
(result, filterId) => ({ acc[filter.id] = [...filter.selection].filter(option => option !== 'all');
...result, return acc;
[filterId]: getters.getSelectedOptionIds(filterId).filter(option => option !== 'all'), }, {});
}),
{},
);
// prevent babel-plugin-rewire from generating an invalid default during karma tests // prevent babel-plugin-rewire from generating an invalid default during karma tests
// This is no longer needed after gitlab-ce#52179 is merged // This is no longer needed after gitlab-ce#52179 is merged
......
...@@ -6,39 +6,24 @@ export default { ...@@ -6,39 +6,24 @@ export default {
const activeFilter = state.filters.find(filter => filter.id === filterId); const activeFilter = state.filters.find(filter => filter.id === filterId);
if (activeFilter) { if (activeFilter) {
let activeOptions; let selection = new Set(activeFilter.selection);
if (optionId === 'all') { if (optionId === 'all') {
activeOptions = activeFilter.options.map(option => ({ selection = new Set(['all']);
...option,
selected: option.id === optionId,
}));
} else { } else {
activeOptions = activeFilter.options.map(option => { selection.delete('all');
if (option.id === 'all') { if (selection.has(optionId)) {
return { selection.delete(optionId);
...option, } else {
selected: false, selection.add(optionId);
};
} }
if (option.id === optionId) {
return {
...option,
selected: !option.selected,
};
}
return option;
});
} }
// This prevents us from selecting nothing at all // This prevents us from selecting nothing at all
if (!activeOptions.find(option => option.selected)) { if (selection.size === 0) {
activeOptions[0].selected = true; selection = new Set(['all']);
} }
activeFilter.selection = selection;
activeFilter.options = activeOptions;
} }
}, },
[types.SET_FILTER_OPTIONS](state, payload) { [types.SET_FILTER_OPTIONS](state, payload) {
......
...@@ -9,13 +9,13 @@ export default () => ({ ...@@ -9,13 +9,13 @@ export default () => ({
{ {
name: 'All', name: 'All',
id: 'all', id: 'all',
selected: true,
}, },
...Object.entries(SEVERITIES).map(severity => { ...Object.entries(SEVERITIES).map(severity => {
const [id, name] = severity; const [id, name] = severity;
return { id, name }; return { id, name };
}), }),
], ],
selection: new Set(['all']),
}, },
{ {
name: 'Report type', name: 'Report type',
...@@ -24,13 +24,13 @@ export default () => ({ ...@@ -24,13 +24,13 @@ export default () => ({
{ {
name: 'All', name: 'All',
id: 'all', id: 'all',
selected: true,
}, },
...Object.entries(REPORT_TYPES).map(type => { ...Object.entries(REPORT_TYPES).map(type => {
const [id, name] = type; const [id, name] = type;
return { id, name }; return { id, name };
}), }),
], ],
selection: new Set(['all']),
}, },
{ {
name: 'Project', name: 'Project',
...@@ -39,9 +39,9 @@ export default () => ({ ...@@ -39,9 +39,9 @@ export default () => ({
{ {
name: 'All', name: 'All',
id: 'all', id: 'all',
selected: true,
}, },
], ],
selection: new Set(['all']),
}, },
], ],
}); });
...@@ -6,15 +6,10 @@ describe('filters module getters', () => { ...@@ -6,15 +6,10 @@ describe('filters module getters', () => {
const getFilter = filterId => getters.getFilter(state)(filterId); const getFilter = filterId => getters.getFilter(state)(filterId);
const getSelectedOptions = filterId => const getSelectedOptions = filterId =>
getters.getSelectedOptions(state, { getFilter })(filterId); getters.getSelectedOptions(state, { getFilter })(filterId);
const getSelectedOptionIds = filterId =>
getters.getSelectedOptionIds(state, { getSelectedOptions })(filterId);
const getFilterIds = getters.getFilterIds(state);
return { return {
getFilter, getFilter,
getSelectedOptions, getSelectedOptions,
getSelectedOptionIds,
getFilterIds,
}; };
}; };
let state; let state;
...@@ -49,7 +44,8 @@ describe('filters module getters', () => { ...@@ -49,7 +44,8 @@ describe('filters module getters', () => {
filters: [ filters: [
{ {
id: 'severity', id: 'severity',
options: [{ id: 'critical', selected: true }, { id: 'high', selected: true }], options: [{ id: 'critical' }, { id: 'high' }],
selection: new Set(['critical', 'high']),
}, },
], ],
}; };
...@@ -60,20 +56,6 @@ describe('filters module getters', () => { ...@@ -60,20 +56,6 @@ describe('filters module getters', () => {
}); });
}); });
describe('getSelectedOptionIds', () => {
it('should return "one" as the selcted dummy ID', () => {
const dummyFilter = {
id: 'dummy',
options: [{ id: 'one', selected: true }, { id: 'anotherone', selected: false }],
};
state.filters.push(dummyFilter);
const selectedOptionIds = getters.getSelectedOptionIds(state, mockedGetters(state))('dummy');
expect(selectedOptionIds).toHaveLength(1);
expect(selectedOptionIds[0]).toEqual('one');
});
});
describe('getSelectedOptionNames', () => { describe('getSelectedOptionNames', () => {
it('should return "All" as the selected option', () => { it('should return "All" as the selected option', () => {
const selectedOptionNames = getters.getSelectedOptionNames(state, mockedGetters(state))( const selectedOptionNames = getters.getSelectedOptionNames(state, mockedGetters(state))(
...@@ -88,7 +70,8 @@ describe('filters module getters', () => { ...@@ -88,7 +70,8 @@ describe('filters module getters', () => {
filters: [ filters: [
{ {
id: 'severity', id: 'severity',
options: [{ name: 'Critical', selected: true }, { name: 'High', selected: true }], options: [{ name: 'Critical', id: 1 }, { name: 'High', id: 2 }],
selection: new Set([1, 2]),
}, },
], ],
}; };
...@@ -110,7 +93,8 @@ describe('filters module getters', () => { ...@@ -110,7 +93,8 @@ describe('filters module getters', () => {
it('should return multiple dummy filters"', () => { it('should return multiple dummy filters"', () => {
const dummyFilter = { const dummyFilter = {
id: 'dummy', id: 'dummy',
options: [{ id: 'one', selected: true }, { id: 'anotherone', selected: true }], options: [{ id: 'one' }, { id: 'two' }],
selection: new Set(['one', 'two']),
}; };
state.filters.push(dummyFilter); state.filters.push(dummyFilter);
const activeFilters = getters.activeFilters(state, mockedGetters(state)); const activeFilters = getters.activeFilters(state, mockedGetters(state));
......
...@@ -3,7 +3,6 @@ import * as types from 'ee/security_dashboard/store/modules/filters/mutation_typ ...@@ -3,7 +3,6 @@ import * as types from 'ee/security_dashboard/store/modules/filters/mutation_typ
import mutations from 'ee/security_dashboard/store/modules/filters/mutations'; import mutations from 'ee/security_dashboard/store/modules/filters/mutations';
describe('filters module mutations', () => { describe('filters module mutations', () => {
describe('SET_FILTER', () => {
let state; let state;
let severityFilter; let severityFilter;
let criticalOption; let criticalOption;
...@@ -13,7 +12,10 @@ describe('filters module mutations', () => { ...@@ -13,7 +12,10 @@ describe('filters module mutations', () => {
state = createState(); state = createState();
[severityFilter] = state.filters; [severityFilter] = state.filters;
[, criticalOption, highOption] = severityFilter.options; [, criticalOption, highOption] = severityFilter.options;
});
describe('SET_FILTER', () => {
beforeEach(() => {
mutations[types.SET_FILTER](state, { mutations[types.SET_FILTER](state, {
filterId: severityFilter.id, filterId: severityFilter.id,
optionId: criticalOption.id, optionId: criticalOption.id,
...@@ -21,11 +23,16 @@ describe('filters module mutations', () => { ...@@ -21,11 +23,16 @@ describe('filters module mutations', () => {
}); });
it('should make critical the selected option', () => { it('should make critical the selected option', () => {
expect(state.filters[0].options[1].selected).toEqual(true); expect(state.filters[0].selection).toEqual(new Set(['critical']));
}); });
it('should remove ALL as the selected option', () => { it('should set to `all` if no option is selected', () => {
expect(state.filters[0].options[0].selected).toEqual(false); mutations[types.SET_FILTER](state, {
filterId: severityFilter.id,
optionId: criticalOption.id,
});
expect(state.filters[0].selection).toEqual(new Set(['all']));
}); });
describe('on subsequent changes', () => { describe('on subsequent changes', () => {
...@@ -35,27 +42,22 @@ describe('filters module mutations', () => { ...@@ -35,27 +42,22 @@ describe('filters module mutations', () => {
optionId: highOption.id, optionId: highOption.id,
}); });
expect(state.filters[0].options[1].selected).toEqual(true); expect(state.filters[0].selection).toEqual(new Set(['high', 'critical']));
expect(state.filters[0].options[2].selected).toEqual(true);
}); });
}); });
}); });
describe('SET_FILTER_OPTIONS', () => { describe('SET_FILTER_OPTIONS', () => {
let state;
let firstFilter;
const options = [{ id: 0, name: 'c' }, { id: 3, name: 'c' }]; const options = [{ id: 0, name: 'c' }, { id: 3, name: 'c' }];
beforeEach(() => { beforeEach(() => {
state = createState(); const filterId = severityFilter.id;
[firstFilter] = state.filters;
const filterId = firstFilter.id;
mutations[types.SET_FILTER_OPTIONS](state, { filterId, options }); mutations[types.SET_FILTER_OPTIONS](state, { filterId, options });
}); });
it('should add all the options to the type filter', () => { it('should add all the options to the type filter', () => {
expect(firstFilter.options).toEqual(options); expect(severityFilter.options).toEqual(options);
}); });
}); });
}); });
import createStore from 'ee/security_dashboard/store/index';
import * as projectsMutationTypes from 'ee/security_dashboard/store/modules/projects/mutation_types';
describe('moderator', () => {
let store;
beforeEach(() => {
store = createStore();
});
it('sets project filter options after projects have been received', () => {
spyOn(store, 'dispatch').and.returnValue();
store.commit(`projects/${projectsMutationTypes.RECEIVE_PROJECTS_SUCCESS}`, {
projects: [{ name: 'foo', id: 1, otherProp: 'foobar' }],
});
expect(store.dispatch).toHaveBeenCalledWith(
'filters/setFilterOptions',
Object({
filterId: 'project_id',
options: [{ name: 'All', id: 'all' }, { name: 'foo', id: '1' }],
}),
);
});
});
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