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 = { ...@@ -25,3 +25,20 @@ export const POLICY_KINDS = {
ciliumNetwork: 'CiliumNetworkPolicy', ciliumNetwork: 'CiliumNetworkPolicy',
scanExecution: 'scanner_profile', 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 { ...@@ -38,7 +38,6 @@ export default {
:label="s__('ThreatMonitoring|Environment')" :label="s__('ThreatMonitoring|Environment')"
label-size="sm" label-size="sm"
:label-for="$options.environmentFilterId" :label-for="$options.environmentFilterId"
class="col-sm-6 col-md-4 col-lg-3 col-xl-2"
> >
<gl-dropdown <gl-dropdown
:id="$options.environmentFilterId" :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'; ...@@ -17,7 +17,9 @@ import { setUrlFragment, mergeUrlParams } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import networkPoliciesQuery from '../graphql/queries/network_policies.query.graphql'; import networkPoliciesQuery from '../graphql/queries/network_policies.query.graphql';
import scanExecutionPoliciesQuery from '../graphql/queries/scan_execution_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 EnvironmentPicker from './filters/environment_picker.vue';
import PolicyTypeFilter from './filters/policy_type_filter.vue';
import PolicyDrawer from './policy_drawer/policy_drawer.vue'; import PolicyDrawer from './policy_drawer/policy_drawer.vue';
const createPolicyFetchError = ({ gqlError, networkError }) => { const createPolicyFetchError = ({ gqlError, networkError }) => {
...@@ -46,6 +48,7 @@ export default { ...@@ -46,6 +48,7 @@ export default {
GlLink, GlLink,
GlIcon, GlIcon,
EnvironmentPicker, EnvironmentPicker,
PolicyTypeFilter,
PolicyDrawer, PolicyDrawer,
}, },
directives: { directives: {
...@@ -80,7 +83,7 @@ export default { ...@@ -80,7 +83,7 @@ export default {
}, },
error: createPolicyFetchError, error: createPolicyFetchError,
skip() { skip() {
return this.isLoadingEnvironments; return this.isLoadingEnvironments || !this.shouldShowNetworkPolicies;
}, },
}, },
scanExecutionPolicies: { scanExecutionPolicies: {
...@@ -101,6 +104,7 @@ export default { ...@@ -101,6 +104,7 @@ export default {
selectedPolicy: null, selectedPolicy: null,
networkPolicies: [], networkPolicies: [],
scanExecutionPolicies: [], scanExecutionPolicies: [],
selectedPolicyType: POLICY_TYPE_OPTIONS.ALL.value,
}; };
}, },
computed: { computed: {
...@@ -113,10 +117,26 @@ export default { ...@@ -113,10 +117,26 @@ export default {
documentationFullPath() { documentationFullPath() {
return setUrlFragment(this.documentationPath, 'container-network-policy'); 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() { policies() {
return [ return [
...getPoliciesWithType(this.networkPolicies, s__('SecurityPolicies|Network')), ...(this.shouldShowNetworkPolicies
...getPoliciesWithType(this.scanExecutionPolicies, s__('SecurityPolicies|Scan execution')), ? getPoliciesWithType(this.networkPolicies, POLICY_TYPE_NETWORK)
: []),
...(this.shouldShowScanExecutionPolicies
? getPoliciesWithType(this.scanExecutionPolicies, POLICY_TYPE_SCAN_EXECUTION)
: []),
]; ];
}, },
isLoadingPolicies() { isLoadingPolicies() {
...@@ -229,8 +249,15 @@ export default { ...@@ -229,8 +249,15 @@ export default {
</gl-alert> </gl-alert>
<div class="pt-3 px-3 bg-gray-light"> <div class="pt-3 px-3 bg-gray-light">
<div class="row justify-content-between align-items-center"> <div class="row gl-justify-content-space-between gl-align-items-center">
<environment-picker ref="environmentsPicker" :include-all="true" /> <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"> <div class="col-sm-auto">
<gl-button <gl-button
category="secondary" 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'; ...@@ -2,6 +2,7 @@ import { GlTable, GlDrawer } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils'; import { createLocalVue } from '@vue/test-utils';
import { merge } from 'lodash'; import { merge } from 'lodash';
import VueApollo from 'vue-apollo'; 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 PolicyDrawer from 'ee/threat_monitoring/components/policy_drawer/policy_drawer.vue';
import PolicyList from 'ee/threat_monitoring/components/policy_list.vue'; import PolicyList from 'ee/threat_monitoring/components/policy_list.vue';
import networkPoliciesQuery from 'ee/threat_monitoring/graphql/queries/network_policies.query.graphql'; import networkPoliciesQuery from 'ee/threat_monitoring/graphql/queries/network_policies.query.graphql';
...@@ -82,6 +83,7 @@ describe('PolicyList component', () => { ...@@ -82,6 +83,7 @@ describe('PolicyList component', () => {
const mountShallowWrapper = factory(shallowMountExtended); const mountShallowWrapper = factory(shallowMountExtended);
const mountWrapper = factory(); const mountWrapper = factory();
const findPolicyTypeFilter = () => wrapper.findByTestId('policy-type-filter');
const findEnvironmentsPicker = () => wrapper.find({ ref: 'environmentsPicker' }); const findEnvironmentsPicker = () => wrapper.find({ ref: 'environmentsPicker' });
const findPoliciesTable = () => wrapper.findComponent(GlTable); const findPoliciesTable = () => wrapper.findComponent(GlTable);
const findPolicyStatusCells = () => wrapper.findAllByTestId('policy-status-cell'); const findPolicyStatusCells = () => wrapper.findAllByTestId('policy-status-cell');
...@@ -132,6 +134,16 @@ describe('PolicyList component', () => { ...@@ -132,6 +134,16 @@ describe('PolicyList component', () => {
it("sets table's loading state", () => { it("sets table's loading state", () => {
expect(findPoliciesTable().attributes('busy')).toBe('true'); 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 () => { it('fetches network policies on environment change', async () => {
store.dispatch.mockReset(); store.dispatch.mockReset();
...@@ -142,15 +154,16 @@ describe('PolicyList component', () => { ...@@ -142,15 +154,16 @@ describe('PolicyList component', () => {
environmentId: environments[0].global_id, environmentId: environments[0].global_id,
}); });
}); });
});
describe('given policies have been fetched', () => { it('if network policies are filtered out, changing the environment does not trigger a fetch', async () => {
let rows; store.dispatch.mockReset();
expect(requestHandlers.networkPolicies).toHaveBeenCalledTimes(1);
beforeEach(async () => { findPolicyTypeFilter().vm.$emit(
mountWrapper(); 'input',
await waitForPromises(); POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION.value,
rows = wrapper.findAll('tr'); );
await store.commit('threatMonitoring/SET_CURRENT_ENVIRONMENT_ID', 2);
expect(requestHandlers.networkPolicies).toHaveBeenCalledTimes(1);
}); });
describe.each` describe.each`
...@@ -174,6 +187,20 @@ describe('PolicyList component', () => { ...@@ -174,6 +187,20 @@ describe('PolicyList component', () => {
expect(row.findAll('td').at(2).text()).toBe(expectedPolicyType); 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', () => { describe('status column', () => {
......
...@@ -29032,6 +29032,9 @@ msgstr "" ...@@ -29032,6 +29032,9 @@ msgstr ""
msgid "SecurityOrchestration|Security policy project" msgid "SecurityOrchestration|Security policy project"
msgstr "" msgstr ""
msgid "SecurityPolicies|All policies"
msgstr ""
msgid "SecurityPolicies|Description" msgid "SecurityPolicies|Description"
msgstr "" 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