From 0a149963263e693e958e59242739391fad72c25a Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Tue, 5 Mar 2019 17:00:06 +0800 Subject: [PATCH 01/11] Improve dropdowns in Group Security Dashboard Each dropdown now: - stays open as items in it are checked/unchecked - has a header and close button - shows a search box when there are many items in it - use a more specific name for the "All" filter; e.g., "All severities" --- .../security_dashboard/components/filter.vue | 80 +++++++++++--- .../security_dashboard/store/moderator.js | 6 +- .../store/modules/filters/constants.js | 15 +++ .../store/modules/filters/state.js | 33 ++---- .../components/filter_spec.js | 100 ++++++++++++++++-- 5 files changed, 182 insertions(+), 52 deletions(-) diff --git a/ee/app/assets/javascripts/security_dashboard/components/filter.vue b/ee/app/assets/javascripts/security_dashboard/components/filter.vue index e848f3915f0c21..19c6cf938ba7ca 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/filter.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/filter.vue @@ -1,12 +1,13 @@ @@ -45,7 +59,7 @@ export default { diff --git a/ee/app/assets/javascripts/security_dashboard/store/moderator.js b/ee/app/assets/javascripts/security_dashboard/store/moderator.js index 0d3acab79e919f..b84d20891cdda7 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/moderator.js +++ b/ee/app/assets/javascripts/security_dashboard/store/moderator.js @@ -1,6 +1,7 @@ import * as vulnerabilitiesMutationTypes from './modules/vulnerabilities/mutation_types'; import * as filtersMutationTypes from './modules/filters/mutation_types'; import * as projectsMutationTypes from './modules/projects/mutation_types'; +import { BASE_FILTERS } from './modules/filters/constants'; export default function configureModerator(store) { store.$router.beforeEach((to, from, next) => { @@ -19,10 +20,7 @@ export default function configureModerator(store) { store.dispatch('filters/setFilterOptions', { filterId: 'project_id', options: [ - { - name: 'All', - id: 'all', - }, + BASE_FILTERS.project_id, ...payload.projects.map(project => ({ name: project.name, id: project.id.toString(), diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/constants.js b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/constants.js index 28b508d12a2a1b..dbd209f93a03f0 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/constants.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/constants.js @@ -27,3 +27,18 @@ export const REPORT_TYPES = { dependency_scanning: s__('ciReport|Dependency Scanning'), sast: s__('ciReport|SAST'), }; + +export const BASE_FILTERS = { + severity: { + name: 'All severities', + id: 'all', + }, + report_type: { + name: 'All report types', + id: 'all', + }, + project_id: { + name: 'All projects', + id: 'all', + }, +}; diff --git a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/state.js b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/state.js index 446be736b95bf4..befec495c88a3e 100644 --- a/ee/app/assets/javascripts/security_dashboard/store/modules/filters/state.js +++ b/ee/app/assets/javascripts/security_dashboard/store/modules/filters/state.js @@ -1,46 +1,25 @@ -import { SEVERITY_LEVELS, REPORT_TYPES } from './constants'; +import { SEVERITY_LEVELS, REPORT_TYPES, BASE_FILTERS } from './constants'; + +const mapToArray = map => Object.entries(map).map(([id, name]) => ({ id, name })); export default () => ({ filters: [ { name: 'Severity', id: 'severity', - options: [ - { - name: 'All', - id: 'all', - }, - ...Object.entries(SEVERITY_LEVELS).map(severity => { - const [id, name] = severity; - return { id, name }; - }), - ], + options: [BASE_FILTERS.severity, ...mapToArray(SEVERITY_LEVELS)], selection: new Set(['all']), }, { name: 'Report type', id: 'report_type', - options: [ - { - name: 'All', - id: 'all', - }, - ...Object.entries(REPORT_TYPES).map(type => { - const [id, name] = type; - return { id, name }; - }), - ], + options: [BASE_FILTERS.report_type, ...mapToArray(REPORT_TYPES)], selection: new Set(['all']), }, { name: 'Project', id: 'project_id', - options: [ - { - name: 'All', - id: 'all', - }, - ], + options: [BASE_FILTERS.project_id], selection: new Set(['all']), }, ], diff --git a/ee/spec/javascripts/security_dashboard/components/filter_spec.js b/ee/spec/javascripts/security_dashboard/components/filter_spec.js index 82312bbe8989d5..4955c2ffd188cd 100644 --- a/ee/spec/javascripts/security_dashboard/components/filter_spec.js +++ b/ee/spec/javascripts/security_dashboard/components/filter_spec.js @@ -6,8 +6,37 @@ import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper describe('Filter component', () => { let vm; let props; - const store = createStore(); - const Component = Vue.extend(component); + let store; + let Component; + + function isDropdownOpen() { + const toggleButton = vm.$el.querySelector('.dropdown-toggle'); + return toggleButton.getAttribute('aria-expanded') === 'true'; + } + + function setProjectsCount(count) { + const projects = new Array(count).fill(null).map((_, i) => ({ + name: i.toString(), + id: i.toString(), + })); + + store.dispatch('filters/setFilterOptions', { + filterId: 'project_id', + options: projects, + }); + } + + const findSearchInput = () => + (vm.$refs.searchBox || null) && vm.$refs.searchBox.$el.querySelector('input'); + + beforeEach(() => { + store = createStore(); + Component = Vue.extend(component); + }); + + afterEach(() => { + vm.$destroy(); + }); describe('severity', () => { beforeEach(() => { @@ -15,10 +44,6 @@ describe('Filter component', () => { vm = mountComponentWithStore(Component, { store, props }); }); - afterEach(() => { - vm.$destroy(); - }); - it('should display all 8 severity options', () => { expect(vm.$el.querySelectorAll('.dropdown-item').length).toEqual(8); }); @@ -30,5 +55,68 @@ describe('Filter component', () => { it('should display "Severity" as the option name', () => { expect(vm.$el.querySelector('.js-name').textContent).toContain('Severity'); }); + + it('should not have a search box', () => { + expect(findSearchInput()).toBeNull(); + }); + + it('should not be open', () => { + expect(isDropdownOpen()).toBe(false); + }); + + describe('when the dropdown is open', () => { + beforeEach(done => { + vm.$el.querySelector('.dropdown-toggle').click(); + vm.$nextTick(done); + }); + + it('should keep the menu open after clicking on an item', done => { + expect(isDropdownOpen()).toBe(true); + vm.$el.querySelector('.dropdown-item').click(); + vm.$nextTick(() => { + expect(isDropdownOpen()).toBe(true); + done(); + }); + }); + + it('should close the menu when the close button is clicked', done => { + expect(isDropdownOpen()).toBe(true); + vm.$refs.close.click(); + vm.$nextTick(() => { + expect(isDropdownOpen()).toBe(false); + done(); + }); + }); + }); + }); + + describe('Project', () => { + describe('when there are lots of projects', () => { + const lots = 30; + beforeEach(done => { + props = { filterId: 'project_id', dashboardDocumentation: '' }; + vm = mountComponentWithStore(Component, { store, props }); + setProjectsCount(lots); + vm.$nextTick(done); + }); + + it('should display a search box', () => { + expect(findSearchInput()).not.toBeNull(); + }); + + it(`should show all projects`, () => { + expect(vm.$el.querySelectorAll('.dropdown-item').length).toBe(lots); + }); + + it('should show only matching projects when a search term is entered', done => { + const input = findSearchInput(); + input.value = '0'; + input.dispatchEvent(new Event('input')); + vm.$nextTick(() => { + expect(vm.$el.querySelectorAll('.dropdown-item').length).toBe(3); + done(); + }); + }); + }); }); }); -- GitLab From c59b45048dc1ed085f48e5d921cb78789b6c8788 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Mon, 18 Mar 2019 15:36:31 +0800 Subject: [PATCH 02/11] Add changelog entry --- ...feature-dropdowns-to-group-security-dashboard-filters.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 ee/changelogs/unreleased/9253-add-full-feature-dropdowns-to-group-security-dashboard-filters.yml diff --git a/ee/changelogs/unreleased/9253-add-full-feature-dropdowns-to-group-security-dashboard-filters.yml b/ee/changelogs/unreleased/9253-add-full-feature-dropdowns-to-group-security-dashboard-filters.yml new file mode 100644 index 00000000000000..021be46b1f94d0 --- /dev/null +++ b/ee/changelogs/unreleased/9253-add-full-feature-dropdowns-to-group-security-dashboard-filters.yml @@ -0,0 +1,5 @@ +--- +title: Make editing the filters in the Group Security Dashboard easier. +merge_request: 10138 +author: +type: fixed -- GitLab From f1153dd6db481a21ea5f9df5a3b6fe6a4feb42de Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Mon, 18 Mar 2019 16:49:32 +0800 Subject: [PATCH 03/11] Fix tests broken by base filter name changes --- .../security_dashboard/store/filters/getters_spec.js | 5 +++-- .../javascripts/security_dashboard/store/moderator_spec.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ee/spec/frontend/security_dashboard/store/filters/getters_spec.js b/ee/spec/frontend/security_dashboard/store/filters/getters_spec.js index 0fc6fb6a2d5c57..2ca1aaa974d8cb 100644 --- a/ee/spec/frontend/security_dashboard/store/filters/getters_spec.js +++ b/ee/spec/frontend/security_dashboard/store/filters/getters_spec.js @@ -34,7 +34,7 @@ describe('filters module getters', () => { ); expect(selectedOptions).toHaveLength(1); - expect(selectedOptions[0].name).toEqual('All'); + expect(selectedOptions[0].name).toContain('All'); }); }); @@ -62,7 +62,8 @@ describe('filters module getters', () => { 'severity', ); - expect(selectedOptionNames).toEqual({ firstOption: 'All', extraOptionCount: '' }); + expect(selectedOptionNames.firstOption).toContain('All'); + expect(selectedOptionNames.extraOptionCount).toBe(''); }); it('should return the correct message when multiple filters are selected', () => { diff --git a/ee/spec/javascripts/security_dashboard/store/moderator_spec.js b/ee/spec/javascripts/security_dashboard/store/moderator_spec.js index e6c05a0a969033..75cd39cd5cae0e 100644 --- a/ee/spec/javascripts/security_dashboard/store/moderator_spec.js +++ b/ee/spec/javascripts/security_dashboard/store/moderator_spec.js @@ -22,7 +22,7 @@ describe('moderator', () => { 'filters/setFilterOptions', Object({ filterId: 'project_id', - options: [{ name: 'All', id: 'all' }, { name: 'foo', id: '1' }], + options: [{ name: 'All projects', id: 'all' }, { name: 'foo', id: '1' }], }), ); }); -- GitLab From 5fdb00605bf128f739b7733fc018e355aeefc247 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Tue, 19 Mar 2019 16:42:35 +0800 Subject: [PATCH 04/11] Remove unused component --- .../javascripts/security_dashboard/components/filter.vue | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ee/app/assets/javascripts/security_dashboard/components/filter.vue b/ee/app/assets/javascripts/security_dashboard/components/filter.vue index 19c6cf938ba7ca..2ec6ecaf870163 100644 --- a/ee/app/assets/javascripts/security_dashboard/components/filter.vue +++ b/ee/app/assets/javascripts/security_dashboard/components/filter.vue @@ -1,12 +1,11 @@