Commit fbbf4bd6 authored by Savas Vedova's avatar Savas Vedova

Merge branch '284471-old-project-filter' into 'master'

Add deprecated project filter component

See merge request gitlab-org/gitlab!65631
parents 8e1dbf0a 0fdf4020
...@@ -12,11 +12,18 @@ import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants'; ...@@ -12,11 +12,18 @@ import { DASHBOARD_TYPES } from 'ee/security_dashboard/store/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ActivityFilter from './activity_filter.vue'; import ActivityFilter from './activity_filter.vue';
import ProjectFilter from './project_filter.vue'; import ProjectFilter from './project_filter.vue';
import ProjectFilterDeprecated from './project_filter_deprecated.vue';
import ScannerFilter from './scanner_filter.vue'; import ScannerFilter from './scanner_filter.vue';
import SimpleFilter from './simple_filter.vue'; import SimpleFilter from './simple_filter.vue';
export default { export default {
components: { SimpleFilter, ScannerFilter, ActivityFilter, ProjectFilter }, components: {
SimpleFilter,
ScannerFilter,
ActivityFilter,
ProjectFilter,
ProjectFilterDeprecated,
},
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
inject: ['dashboardType'], inject: ['dashboardType'],
props: { props: {
...@@ -108,7 +115,7 @@ export default { ...@@ -108,7 +115,7 @@ export default {
:filter="projectFilter" :filter="projectFilter"
@filter-changed="updateFilterQuery" @filter-changed="updateFilterQuery"
/> />
<simple-filter <project-filter-deprecated
v-else-if="shouldShowProjectFilter" v-else-if="shouldShowProjectFilter"
:filter="projectFilter" :filter="projectFilter"
:data-testid="projectFilter.id" :data-testid="projectFilter.id"
......
...@@ -46,6 +46,7 @@ export default { ...@@ -46,6 +46,7 @@ export default {
data: () => ({ data: () => ({
projectsCache: {}, projectsCache: {},
projects: [], projects: [],
searchTerm: '',
hasDropdownBeenOpened: false, hasDropdownBeenOpened: false,
}), }),
computed: { computed: {
......
<script>
import FilterBody from './filter_body.vue';
import FilterItem from './filter_item.vue';
import SimpleFilter from './simple_filter.vue';
const SHOW_SEARCH_BOX_THRESHOLD = 20;
export default {
components: { FilterBody, FilterItem },
extends: SimpleFilter,
data() {
return {
searchTerm: '',
};
},
computed: {
filteredOptions() {
return this.options.filter((option) =>
option.name.toLowerCase().includes(this.searchTerm.toLowerCase()),
);
},
showSearchBox() {
return this.options.length >= SHOW_SEARCH_BOX_THRESHOLD;
},
},
};
</script>
<template>
<filter-body
v-model.trim="searchTerm"
:name="filter.name"
:selected-options="selectedOptionsOrAll"
:show-search-box="showSearchBox"
>
<filter-item
v-if="filter.allOption && !searchTerm.length"
:is-checked="isNoOptionsSelected"
:text="filter.allOption.name"
data-testid="allOption"
@click="deselectAllOptions"
/>
<filter-item
v-for="option in filteredOptions"
:key="option.id"
:is-checked="isSelected(option)"
:text="option.name"
:data-testid="`${filter.id}:${option.id}`"
@click="toggleOption(option)"
/>
</filter-body>
</template>
...@@ -10,21 +10,9 @@ export default { ...@@ -10,21 +10,9 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
// Number of options that must exist for the search box to show.
searchBoxShowThreshold: {
type: Number,
required: false,
default: 20,
},
loading: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
searchTerm: '',
selectedOptions: undefined, selectedOptions: undefined,
}; };
}, },
...@@ -45,11 +33,6 @@ export default { ...@@ -45,11 +33,6 @@ export default {
// This is passed to the vulnerability list's GraphQL query as a variable. // This is passed to the vulnerability list's GraphQL query as a variable.
return { [this.filter.id]: this.selectedOptions.map((x) => x.id) }; return { [this.filter.id]: this.selectedOptions.map((x) => x.id) };
}, },
filteredOptions() {
return this.options.filter((option) =>
option.name.toLowerCase().includes(this.searchTerm.toLowerCase()),
);
},
querystringIds() { querystringIds() {
const ids = this.$route?.query[this.filter.id] || []; const ids = this.$route?.query[this.filter.id] || [];
const idArray = Array.isArray(ids) ? ids : [ids]; const idArray = Array.isArray(ids) ? ids : [ids];
...@@ -71,9 +54,6 @@ export default { ...@@ -71,9 +54,6 @@ export default {
return options; return options;
}, },
showSearchBox() {
return this.options.length >= this.searchBoxShowThreshold;
},
}, },
watch: { watch: {
selectedOptions() { selectedOptions() {
...@@ -119,22 +99,16 @@ export default { ...@@ -119,22 +99,16 @@ export default {
</script> </script>
<template> <template>
<filter-body <filter-body :name="filter.name" :selected-options="selectedOptionsOrAll">
v-model.trim="searchTerm"
:name="filter.name"
:selected-options="selectedOptionsOrAll"
:show-search-box="showSearchBox"
:loading="loading"
>
<filter-item <filter-item
v-if="filter.allOption && !searchTerm.length" v-if="filter.allOption"
:is-checked="isNoOptionsSelected" :is-checked="isNoOptionsSelected"
:text="filter.allOption.name" :text="filter.allOption.name"
data-testid="allOption" data-testid="allOption"
@click="deselectAllOptions" @click="deselectAllOptions"
/> />
<filter-item <filter-item
v-for="option in filteredOptions" v-for="option in options"
:key="option.id" :key="option.id"
:is-checked="isSelected(option)" :is-checked="isSelected(option)"
:text="option.name" :text="option.name"
......
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import FilterBody from 'ee/security_dashboard/components/shared/filters/filter_body.vue';
import FilterItem from 'ee/security_dashboard/components/shared/filters/filter_item.vue';
import ProjectFilterDeprecated from 'ee/security_dashboard/components/shared/filters/project_filter_deprecated.vue';
import { getProjectFilter } from 'ee/security_dashboard/helpers';
const generateProjects = (length) =>
Array.from({ length }, (_, i) => ({ id: i + 1, name: `Option ${i + 1}` }));
describe('Project Filter Deprecated component', () => {
let wrapper;
const createWrapper = ({ projects }) => {
wrapper = shallowMount(ProjectFilterDeprecated, {
propsData: { filter: getProjectFilter(projects) },
});
};
const dropdownItems = () => wrapper.findAllComponents(FilterItem);
const filterBody = () => wrapper.findComponent(FilterBody);
afterEach(() => {
wrapper.destroy();
});
describe('search box', () => {
it.each`
phrase | count | shouldShow
${'shows'} | ${20} | ${true}
${'hides'} | ${15} | ${false}
`('$phrase search box if there are $count options', ({ count, shouldShow }) => {
createWrapper({ projects: generateProjects(count) });
expect(filterBody().props('showSearchBox')).toBe(shouldShow);
});
it('filters options when something is typed in the search box', async () => {
const projects = generateProjects(11);
const expectedProjectNames = ['Option 1', 'Option 10', 'Option 11'];
createWrapper({ projects });
filterBody().vm.$emit('input', '1');
await nextTick();
expect(dropdownItems()).toHaveLength(3);
expect(dropdownItems().wrappers.map((x) => x.props('text'))).toEqual(expectedProjectNames);
});
});
});
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import FilterBody from 'ee/security_dashboard/components/shared/filters/filter_body.vue';
import SimpleFilter from 'ee/security_dashboard/components/shared/filters/simple_filter.vue'; import SimpleFilter from 'ee/security_dashboard/components/shared/filters/simple_filter.vue';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -36,7 +35,6 @@ describe('Simple Filter component', () => { ...@@ -36,7 +35,6 @@ describe('Simple Filter component', () => {
const allOptionItem = () => wrapper.find('[data-testid="allOption"]'); const allOptionItem = () => wrapper.find('[data-testid="allOption"]');
const isChecked = (item) => item.props('isChecked'); const isChecked = (item) => item.props('isChecked');
const filterQuery = () => wrapper.vm.$route.query[filter.id]; const filterQuery = () => wrapper.vm.$route.query[filter.id];
const filterBody = () => wrapper.find(FilterBody);
const clickAllOptionItem = async () => { const clickAllOptionItem = async () => {
allOptionItem().vm.$emit('click'); allOptionItem().vm.$emit('click');
...@@ -94,37 +92,6 @@ describe('Simple Filter component', () => { ...@@ -94,37 +92,6 @@ describe('Simple Filter component', () => {
}); });
}); });
describe('search box', () => {
it.each`
phrase | count | searchBoxShowThreshold
${'shows'} | ${5} | ${5}
${'hides'} | ${7} | ${8}
`('$phrase search box if there are $count options', ({ count, searchBoxShowThreshold }) => {
createWrapper({ options: generateOptions(count) }, { searchBoxShowThreshold });
const shouldShow = count >= searchBoxShowThreshold;
expect(filterBody().props('showSearchBox')).toBe(shouldShow);
});
it('filters options when something is typed in the search box', async () => {
const expectedItems = filter.options.map((x) => x.name).filter((x) => x.includes('1'));
createWrapper({}, true);
filterBody().vm.$emit('input', '1');
await wrapper.vm.$nextTick();
expect(dropdownItems()).toHaveLength(3);
expect(dropdownItems().wrappers.map((x) => x.props('text'))).toEqual(expectedItems);
});
});
describe('loading prop', () => {
it.each([true, false])(`sets the filter body loading prop to %s`, (loading) => {
createWrapper({}, { loading });
expect(filterBody().props('loading')).toBe(loading);
});
});
describe('selecting options', () => { describe('selecting options', () => {
beforeEach(() => { beforeEach(() => {
createWrapper({ defaultOptions: optionsAt([1, 2, 3]) }); createWrapper({ defaultOptions: optionsAt([1, 2, 3]) });
......
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