Add policy type filter

Add a dropdown to filter policies by their type.

Changelog: added
EE: true
parent f473eb36
......@@ -25,3 +25,20 @@ export const POLICY_KINDS = {
ciliumNetwork: 'CiliumNetworkPolicy',
scanExecution: 'scanner_profile',
};
export const POLICY_TYPE_NETWORK = s__('SecurityPolicies|Network');
export const POLICY_TYPE_SCAN_EXECUTION = s__('SecurityPolicies|Scan execution');
export const POLICY_TYPE_OPTIONS = {
POLICY_TYPE_NETWORK: {
value: 'POLICY_TYPE_NETWORK',
text: POLICY_TYPE_NETWORK,
},
POLICY_TYPE_SCAN_EXECUTION: {
value: 'POLICY_TYPE_SCAN_EXECUTION',
text: POLICY_TYPE_SCAN_EXECUTION,
},
ALL: {
value: '',
text: s__('SecurityPolicies|All policies'),
},
};
......@@ -38,7 +38,6 @@ export default {
:label="s__('ThreatMonitoring|Environment')"
label-size="sm"
:label-for="$options.environmentFilterId"
class="col-sm-6 col-md-4 col-lg-3 col-xl-2"
>
<gl-dropdown
:id="$options.environmentFilterId"
......
<script>
import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { __ } from '~/locale';
import { POLICY_TYPE_OPTIONS } from '../constants';
export default {
name: 'PolicyTypeFilter',
components: {
GlFormGroup,
GlDropdown,
GlDropdownItem,
},
props: {
value: {
type: String,
required: true,
validator: (value) =>
Object.values(POLICY_TYPE_OPTIONS)
.map((option) => option.value)
.includes(value),
},
},
computed: {
selectedValueText() {
return Object.values(POLICY_TYPE_OPTIONS).find(({ value }) => value === this.value).text;
},
},
methods: {
setPolicyType({ value }) {
this.$emit('input', value);
},
},
policyTypeFilterId: 'policy-type-filter',
POLICY_TYPE_OPTIONS,
i18n: {
label: __('Type'),
},
};
</script>
<template>
<gl-form-group
:label="$options.i18n.label"
label-size="sm"
:label-for="$options.policyTypeFilterId"
>
<gl-dropdown
:id="$options.policyTypeFilterId"
class="gl-display-flex"
toggle-class="gl-truncate"
:text="selectedValueText"
>
<gl-dropdown-item
v-for="option in $options.POLICY_TYPE_OPTIONS"
:key="option.value"
:data-testid="`policy-type-${option.value}-option`"
@click="setPolicyType(option)"
>{{ option.text }}</gl-dropdown-item
>
</gl-dropdown>
</gl-form-group>
</template>
......@@ -17,7 +17,9 @@ import { setUrlFragment, mergeUrlParams } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import networkPoliciesQuery from '../graphql/queries/network_policies.query.graphql';
import scanExecutionPoliciesQuery from '../graphql/queries/scan_execution_policies.query.graphql';
import { POLICY_TYPE_NETWORK, POLICY_TYPE_SCAN_EXECUTION, POLICY_TYPE_OPTIONS } from './constants';
import EnvironmentPicker from './filters/environment_picker.vue';
import PolicyTypeFilter from './filters/policy_type_filter.vue';
import PolicyDrawer from './policy_drawer/policy_drawer.vue';
const createPolicyFetchError = ({ gqlError, networkError }) => {
......@@ -46,6 +48,7 @@ export default {
GlLink,
GlIcon,
EnvironmentPicker,
PolicyTypeFilter,
PolicyDrawer,
},
directives: {
......@@ -80,7 +83,7 @@ export default {
},
error: createPolicyFetchError,
skip() {
return this.isLoadingEnvironments;
return this.isLoadingEnvironments || !this.shouldShowNetworkPolicies;
},
},
scanExecutionPolicies: {
......@@ -101,6 +104,7 @@ export default {
selectedPolicy: null,
networkPolicies: [],
scanExecutionPolicies: [],
selectedPolicyType: POLICY_TYPE_OPTIONS.ALL.value,
};
},
computed: {
......@@ -113,10 +117,26 @@ export default {
documentationFullPath() {
return setUrlFragment(this.documentationPath, 'container-network-policy');
},
shouldShowNetworkPolicies() {
return [
POLICY_TYPE_OPTIONS.ALL.value,
POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK.value,
].includes(this.selectedPolicyType);
},
shouldShowScanExecutionPolicies() {
return [
POLICY_TYPE_OPTIONS.ALL.value,
POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION.value,
].includes(this.selectedPolicyType);
},
policies() {
return [
...getPoliciesWithType(this.networkPolicies, s__('SecurityPolicies|Network')),
...getPoliciesWithType(this.scanExecutionPolicies, s__('SecurityPolicies|Scan execution')),
...(this.shouldShowNetworkPolicies
? getPoliciesWithType(this.networkPolicies, POLICY_TYPE_NETWORK)
: []),
...(this.shouldShowScanExecutionPolicies
? getPoliciesWithType(this.scanExecutionPolicies, POLICY_TYPE_SCAN_EXECUTION)
: []),
];
},
isLoadingPolicies() {
......@@ -229,8 +249,15 @@ export default {
</gl-alert>
<div class="pt-3 px-3 bg-gray-light">
<div class="row justify-content-between align-items-center">
<environment-picker ref="environmentsPicker" :include-all="true" />
<div class="row gl-justify-content-space-between gl-align-items-center">
<div class="col-12 col-sm-8 col-md-6 col-lg-5 row">
<policy-type-filter
v-model="selectedPolicyType"
class="col-6"
data-testid="policy-type-filter"
/>
<environment-picker ref="environmentsPicker" class="col-6" :include-all="true" />
</div>
<div class="col-sm-auto">
<gl-button
category="secondary"
......
import { POLICY_TYPE_OPTIONS } from 'ee/threat_monitoring/components/constants';
import PolicyTypeFilter from 'ee/threat_monitoring/components/filters/policy_type_filter.vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
describe('PolicyTypeFilter component', () => {
let wrapper;
const createWrapper = (value = '') => {
wrapper = mountExtended(PolicyTypeFilter, {
propsData: {
value,
},
});
};
const findToggle = () => wrapper.find('button[aria-haspopup="true"]');
afterEach(() => {
wrapper.destroy();
});
it.each`
value | expectedToggleText
${POLICY_TYPE_OPTIONS.ALL.value} | ${POLICY_TYPE_OPTIONS.ALL.text}
${POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK.value} | ${POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK.text}
${POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION.value} | ${POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION.text}
`('selects the correct option when value is "$value"', ({ value, expectedToggleText }) => {
createWrapper(value);
expect(findToggle().text()).toBe(expectedToggleText);
});
it('emits an event when an option is selected', () => {
createWrapper();
expect(wrapper.emitted('input')).toBeUndefined();
wrapper
.findByTestId(`policy-type-${POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK.value}-option`)
.trigger('click');
expect(wrapper.emitted('input')).toEqual([[POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK.value]]);
});
});
......@@ -2,6 +2,7 @@ import { GlTable, GlDrawer } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils';
import { merge } from 'lodash';
import VueApollo from 'vue-apollo';
import { POLICY_TYPE_OPTIONS } from 'ee/threat_monitoring/components/constants';
import PolicyDrawer from 'ee/threat_monitoring/components/policy_drawer/policy_drawer.vue';
import PolicyList from 'ee/threat_monitoring/components/policy_list.vue';
import networkPoliciesQuery from 'ee/threat_monitoring/graphql/queries/network_policies.query.graphql';
......@@ -82,6 +83,7 @@ describe('PolicyList component', () => {
const mountShallowWrapper = factory(shallowMountExtended);
const mountWrapper = factory();
const findPolicyTypeFilter = () => wrapper.findByTestId('policy-type-filter');
const findEnvironmentsPicker = () => wrapper.find({ ref: 'environmentsPicker' });
const findPoliciesTable = () => wrapper.findComponent(GlTable);
const findPolicyStatusCells = () => wrapper.findAllByTestId('policy-status-cell');
......@@ -132,6 +134,16 @@ describe('PolicyList component', () => {
it("sets table's loading state", () => {
expect(findPoliciesTable().attributes('busy')).toBe('true');
});
});
describe('given policies have been fetched', () => {
let rows;
beforeEach(async () => {
mountWrapper();
await waitForPromises();
rows = wrapper.findAll('tr');
});
it('fetches network policies on environment change', async () => {
store.dispatch.mockReset();
......@@ -142,15 +154,16 @@ describe('PolicyList component', () => {
environmentId: environments[0].global_id,
});
});
});
describe('given policies have been fetched', () => {
let rows;
beforeEach(async () => {
mountWrapper();
await waitForPromises();
rows = wrapper.findAll('tr');
it('if network policies are filtered out, changing the environment does not trigger a fetch', async () => {
store.dispatch.mockReset();
expect(requestHandlers.networkPolicies).toHaveBeenCalledTimes(1);
findPolicyTypeFilter().vm.$emit(
'input',
POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION.value,
);
await store.commit('threatMonitoring/SET_CURRENT_ENVIRONMENT_ID', 2);
expect(requestHandlers.networkPolicies).toHaveBeenCalledTimes(1);
});
describe.each`
......@@ -174,6 +187,20 @@ describe('PolicyList component', () => {
expect(row.findAll('td').at(2).text()).toBe(expectedPolicyType);
});
});
it.each`
description | filterBy | hiddenTypes
${'network'} | ${POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK} | ${[POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION]}
${'scan execution'} | ${POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION} | ${[POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK]}
`('policies filtered by $description type', async ({ filterBy, hiddenTypes }) => {
findPolicyTypeFilter().vm.$emit('input', filterBy.value);
await wrapper.vm.$nextTick();
expect(findPoliciesTable().text()).toContain(filterBy.text);
hiddenTypes.forEach((hiddenType) => {
expect(findPoliciesTable().text()).not.toContain(hiddenType.text);
});
});
});
describe('status column', () => {
......
......@@ -29032,6 +29032,9 @@ msgstr ""
msgid "SecurityOrchestration|Security policy project"
msgstr ""
msgid "SecurityPolicies|All policies"
msgstr ""
msgid "SecurityPolicies|Description"
msgstr ""
......
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