Commit f8846f09 authored by Zamir Martins Filho's avatar Zamir Martins Filho

Add scan result policy into policy list

EE: true
Changelog: added
parent 6662fa41
...@@ -42,6 +42,13 @@ export const POLICY_TYPE_COMPONENT_OPTIONS = { ...@@ -42,6 +42,13 @@ export const POLICY_TYPE_COMPONENT_OPTIONS = {
urlParameter: 'scan_execution_policy', urlParameter: 'scan_execution_policy',
value: 'scanExecution', value: 'scanExecution',
}, },
scanResult: {
component: 'scan-result-policy-editor',
text: s__('SecurityOrchestration|Scan Result'),
typeName: 'ScanResultPolicy',
urlParameter: 'scan_result_policy',
value: 'scanResult',
},
}; };
export const POLICY_TYPE_OPTIONS = { export const POLICY_TYPE_OPTIONS = {
...@@ -53,6 +60,10 @@ export const POLICY_TYPE_OPTIONS = { ...@@ -53,6 +60,10 @@ export const POLICY_TYPE_OPTIONS = {
value: 'POLICY_TYPE_SCAN_EXECUTION', value: 'POLICY_TYPE_SCAN_EXECUTION',
text: s__('SecurityOrchestration|Scan execution'), text: s__('SecurityOrchestration|Scan execution'),
}, },
POLICY_TYPE_SCAN_RESULT: {
value: 'POLICY_TYPE_SCAN_RESULT',
text: s__('SecurityOrchestration|Scan result'),
},
ALL: { ALL: {
value: '', value: '',
text: s__('SecurityOrchestration|All policies'), text: s__('SecurityOrchestration|All policies'),
......
...@@ -14,8 +14,10 @@ import createFlash from '~/flash'; ...@@ -14,8 +14,10 @@ import createFlash from '~/flash';
import { getTimeago } from '~/lib/utils/datetime_utility'; import { getTimeago } from '~/lib/utils/datetime_utility';
import { setUrlFragment, mergeUrlParams } from '~/lib/utils/url_utility'; import { setUrlFragment, mergeUrlParams } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
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 scanResultPoliciesQuery from '../../graphql/queries/scan_result_policies.query.graphql';
import { getPolicyType } from '../../utils'; import { getPolicyType } from '../../utils';
import { POLICY_TYPE_COMPONENT_OPTIONS, POLICY_TYPE_OPTIONS } from '../constants'; import { POLICY_TYPE_COMPONENT_OPTIONS, POLICY_TYPE_OPTIONS } from '../constants';
import EnvironmentPicker from '../environment_picker.vue'; import EnvironmentPicker from '../environment_picker.vue';
...@@ -57,6 +59,7 @@ export default { ...@@ -57,6 +59,7 @@ export default {
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
mixins: [glFeatureFlagMixin()],
inject: ['documentationPath', 'projectPath', 'newPolicyPath'], inject: ['documentationPath', 'projectPath', 'newPolicyPath'],
props: { props: {
shouldUpdatePolicyList: { shouldUpdatePolicyList: {
...@@ -100,12 +103,25 @@ export default { ...@@ -100,12 +103,25 @@ export default {
}, },
error: createPolicyFetchError, error: createPolicyFetchError,
}, },
scanResultPolicies: {
query: scanResultPoliciesQuery,
variables() {
return {
fullPath: this.projectPath,
};
},
update(data) {
return data?.project?.scanResultPolicies?.nodes ?? [];
},
error: createPolicyFetchError,
},
}, },
data() { data() {
return { return {
selectedPolicy: null, selectedPolicy: null,
networkPolicies: [], networkPolicies: [],
scanExecutionPolicies: [], scanExecutionPolicies: [],
scanResultPolicies: [],
selectedPolicyType: POLICY_TYPE_OPTIONS.ALL.value, selectedPolicyType: POLICY_TYPE_OPTIONS.ALL.value,
}; };
}, },
...@@ -113,10 +129,14 @@ export default { ...@@ -113,10 +129,14 @@ export default {
...mapState('threatMonitoring', ['allEnvironments', 'currentEnvironmentId', 'hasEnvironment']), ...mapState('threatMonitoring', ['allEnvironments', 'currentEnvironmentId', 'hasEnvironment']),
...mapGetters('threatMonitoring', ['currentEnvironmentGid']), ...mapGetters('threatMonitoring', ['currentEnvironmentGid']),
allPolicyTypes() { allPolicyTypes() {
return { const allTypes = {
[POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK.value]: this.networkPolicies, [POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK.value]: this.networkPolicies,
[POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION.value]: this.scanExecutionPolicies, [POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION.value]: this.scanExecutionPolicies,
}; };
if (this.isFeatureEnabled) {
allTypes[POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_RESULT.value] = this.scanResultPolicies;
}
return allTypes;
}, },
documentationFullPath() { documentationFullPath() {
return setUrlFragment(this.documentationPath, 'container-network-policy'); return setUrlFragment(this.documentationPath, 'container-network-policy');
...@@ -141,7 +161,8 @@ export default { ...@@ -141,7 +161,8 @@ export default {
isLoadingPolicies() { isLoadingPolicies() {
return ( return (
this.$apollo.queries.networkPolicies.loading || this.$apollo.queries.networkPolicies.loading ||
this.$apollo.queries.scanExecutionPolicies.loading this.$apollo.queries.scanExecutionPolicies.loading ||
this.$apollo.queries.scanResultPolicies.loading
); );
}, },
hasSelectedPolicy() { hasSelectedPolicy() {
...@@ -207,11 +228,15 @@ export default { ...@@ -207,11 +228,15 @@ export default {
return fields; return fields;
}, },
isFeatureEnabled() {
return this.glFeatures.scanResultPolicy;
},
}, },
watch: { watch: {
shouldUpdatePolicyList(newShouldUpdatePolicyList) { shouldUpdatePolicyList(newShouldUpdatePolicyList) {
if (newShouldUpdatePolicyList) { if (newShouldUpdatePolicyList) {
this.$apollo.queries.scanExecutionPolicies.refetch(); this.$apollo.queries.scanExecutionPolicies.refetch();
this.$apollo.queries.scanResultPolicies.refetch();
this.$emit('update-policy-list', false); this.$emit('update-policy-list', false);
} }
}, },
......
<script> <script>
import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { POLICY_TYPE_OPTIONS } from './constants'; import { POLICY_TYPE_OPTIONS } from './constants';
export default { export default {
...@@ -10,6 +11,7 @@ export default { ...@@ -10,6 +11,7 @@ export default {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
}, },
mixins: [glFeatureFlagMixin()],
props: { props: {
value: { value: {
type: String, type: String,
...@@ -24,6 +26,16 @@ export default { ...@@ -24,6 +26,16 @@ export default {
selectedValueText() { selectedValueText() {
return Object.values(POLICY_TYPE_OPTIONS).find(({ value }) => value === this.value).text; return Object.values(POLICY_TYPE_OPTIONS).find(({ value }) => value === this.value).text;
}, },
isFeatureEnabled() {
return this.glFeatures.scanResultPolicy;
},
policyTypeOptions() {
const policyType = POLICY_TYPE_OPTIONS;
if (!this.isFeatureEnabled) {
delete policyType.POLICY_TYPE_SCAN_RESULT;
}
return policyType;
},
}, },
methods: { methods: {
setPolicyType({ value }) { setPolicyType({ value }) {
...@@ -51,7 +63,7 @@ export default { ...@@ -51,7 +63,7 @@ export default {
:text="selectedValueText" :text="selectedValueText"
> >
<gl-dropdown-item <gl-dropdown-item
v-for="option in $options.POLICY_TYPE_OPTIONS" v-for="option in policyTypeOptions"
:key="option.value" :key="option.value"
:data-testid="`policy-type-${option.value}-option`" :data-testid="`policy-type-${option.value}-option`"
@click="setPolicyType(option)" @click="setPolicyType(option)"
......
query scanResultPolicies($fullPath: ID!) {
project(fullPath: $fullPath) {
id
scanResultPolicies {
nodes {
name
yaml
enabled
updatedAt
}
}
}
}
...@@ -25,6 +25,9 @@ export const getPolicyType = (typeName = '') => { ...@@ -25,6 +25,9 @@ export const getPolicyType = (typeName = '') => {
if (typeName === POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.typeName) { if (typeName === POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.typeName) {
return POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.value; return POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.value;
} }
if (typeName === POLICY_TYPE_COMPONENT_OPTIONS.scanResult.typeName) {
return POLICY_TYPE_COMPONENT_OPTIONS.scanResult.value;
}
return null; return null;
}; };
......
...@@ -8,15 +8,21 @@ import PolicyDrawer from 'ee/threat_monitoring/components/policy_drawer/policy_d ...@@ -8,15 +8,21 @@ import PolicyDrawer from 'ee/threat_monitoring/components/policy_drawer/policy_d
import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants'; import { PREDEFINED_NETWORK_POLICIES } from 'ee/threat_monitoring/constants';
import networkPoliciesQuery from 'ee/threat_monitoring/graphql/queries/network_policies.query.graphql'; import networkPoliciesQuery from 'ee/threat_monitoring/graphql/queries/network_policies.query.graphql';
import scanExecutionPoliciesQuery from 'ee/threat_monitoring/graphql/queries/scan_execution_policies.query.graphql'; import scanExecutionPoliciesQuery from 'ee/threat_monitoring/graphql/queries/scan_execution_policies.query.graphql';
import scanResultPoliciesQuery from 'ee/threat_monitoring/graphql/queries/scan_result_policies.query.graphql';
import createStore from 'ee/threat_monitoring/store'; import createStore from 'ee/threat_monitoring/store';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component'; import { stubComponent } from 'helpers/stub_component';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { networkPolicies, scanExecutionPolicies } from '../../mocks/mock_apollo'; import {
networkPolicies,
scanExecutionPolicies,
scanResultPolicies,
} from '../../mocks/mock_apollo';
import { import {
mockNetworkPoliciesResponse, mockNetworkPoliciesResponse,
mockScanExecutionPoliciesResponse, mockScanExecutionPoliciesResponse,
mockScanResultPoliciesResponse,
} from '../../mocks/mock_data'; } from '../../mocks/mock_data';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -35,9 +41,11 @@ const environments = [ ...@@ -35,9 +41,11 @@ const environments = [
]; ];
const networkPoliciesSpy = networkPolicies(mockNetworkPoliciesResponse); const networkPoliciesSpy = networkPolicies(mockNetworkPoliciesResponse);
const scanExecutionPoliciesSpy = scanExecutionPolicies(mockScanExecutionPoliciesResponse); const scanExecutionPoliciesSpy = scanExecutionPolicies(mockScanExecutionPoliciesResponse);
const scanResultPoliciesSpy = scanResultPolicies(mockScanResultPoliciesResponse);
const defaultRequestHandlers = { const defaultRequestHandlers = {
networkPolicies: networkPoliciesSpy, networkPolicies: networkPoliciesSpy,
scanExecutionPolicies: scanExecutionPoliciesSpy, scanExecutionPolicies: scanExecutionPoliciesSpy,
scanResultPolicies: scanResultPoliciesSpy,
}; };
const pendingHandler = jest.fn(() => new Promise(() => {})); const pendingHandler = jest.fn(() => new Promise(() => {}));
...@@ -81,10 +89,12 @@ describe('PoliciesList component', () => { ...@@ -81,10 +89,12 @@ describe('PoliciesList component', () => {
documentationPath: 'path/to/docs', documentationPath: 'path/to/docs',
newPolicyPath: 'path/to/policy', newPolicyPath: 'path/to/policy',
projectPath: fullPath, projectPath: fullPath,
glFeatures: { scanResultPolicy: true },
}, },
apolloProvider: createMockApollo([ apolloProvider: createMockApollo([
[networkPoliciesQuery, requestHandlers.networkPolicies], [networkPoliciesQuery, requestHandlers.networkPolicies],
[scanExecutionPoliciesQuery, requestHandlers.scanExecutionPolicies], [scanExecutionPoliciesQuery, requestHandlers.scanExecutionPolicies],
[scanResultPoliciesQuery, requestHandlers.scanResultPolicies],
]), ]),
stubs: { stubs: {
PolicyDrawer: stubComponent(PolicyDrawer, { PolicyDrawer: stubComponent(PolicyDrawer, {
...@@ -163,7 +173,7 @@ describe('PoliciesList component', () => { ...@@ -163,7 +173,7 @@ describe('PoliciesList component', () => {
}); });
it('does render default network policies', () => { it('does render default network policies', () => {
expect(findPolicyStatusCells()).toHaveLength(5); expect(findPolicyStatusCells()).toHaveLength(6);
}); });
it('fetches network policies on environment change', async () => { it('fetches network policies on environment change', async () => {
...@@ -190,9 +200,10 @@ describe('PoliciesList component', () => { ...@@ -190,9 +200,10 @@ describe('PoliciesList component', () => {
describe.each` describe.each`
rowIndex | expectedPolicyName | expectedPolicyType rowIndex | expectedPolicyName | expectedPolicyType
${1} | ${mockScanExecutionPoliciesResponse[0].name} | ${'Scan execution'} ${1} | ${mockScanExecutionPoliciesResponse[0].name} | ${'Scan execution'}
${3} | ${mockNetworkPoliciesResponse[0].name} | ${'Network'} ${2} | ${mockScanResultPoliciesResponse[0].name} | ${'Scan result'}
${4} | ${PREDEFINED_NETWORK_POLICIES[0].name} | ${'Network'} ${3} | ${mockNetworkPoliciesResponse[1].name} | ${'Network'}
${5} | ${PREDEFINED_NETWORK_POLICIES[1].name} | ${'Network'} ${4} | ${mockNetworkPoliciesResponse[0].name} | ${'Network'}
${5} | ${PREDEFINED_NETWORK_POLICIES[0].name} | ${'Network'}
`('policy in row #$rowIndex', ({ rowIndex, expectedPolicyName, expectedPolicyType }) => { `('policy in row #$rowIndex', ({ rowIndex, expectedPolicyName, expectedPolicyType }) => {
let row; let row;
...@@ -211,8 +222,9 @@ describe('PoliciesList component', () => { ...@@ -211,8 +222,9 @@ describe('PoliciesList component', () => {
it.each` it.each`
description | filterBy | hiddenTypes description | filterBy | hiddenTypes
${'network'} | ${POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK} | ${[POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION]} ${'network'} | ${POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK} | ${[POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION, POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_RESULT]}
${'scan execution'} | ${POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION} | ${[POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK]} ${'scan execution'} | ${POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION} | ${[POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK, POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_RESULT]}
${'scan result'} | ${POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_RESULT} | ${[POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK, POLICY_TYPE_OPTIONS.POLICY_TYPE_SCAN_EXECUTION]}
`('policies filtered by $description type', async ({ filterBy, hiddenTypes }) => { `('policies filtered by $description type', async ({ filterBy, hiddenTypes }) => {
findPolicyTypeFilter().vm.$emit('input', filterBy.value); findPolicyTypeFilter().vm.$emit('input', filterBy.value);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
...@@ -258,7 +270,7 @@ describe('PoliciesList component', () => { ...@@ -258,7 +270,7 @@ describe('PoliciesList component', () => {
}); });
it('renders a "Disabled" label for screen readers for disabled policies', () => { it('renders a "Disabled" label for screen readers for disabled policies', () => {
const span = findPolicyStatusCells().at(3).find('span'); const span = findPolicyStatusCells().at(4).find('span');
expect(span.exists()).toBe(true); expect(span.exists()).toBe(true);
expect(span.attributes('class')).toBe('gl-sr-only'); expect(span.attributes('class')).toBe('gl-sr-only');
...@@ -284,6 +296,7 @@ describe('PoliciesList component', () => { ...@@ -284,6 +296,7 @@ describe('PoliciesList component', () => {
${PREDEFINED_NETWORK_POLICIES[0].name} | ${PREDEFINED_NETWORK_POLICIES[0]} | ${'container'} | ${'path/to/policy?environment_id=2&type=container_policy&kind=CiliumNetworkPolicy'} ${PREDEFINED_NETWORK_POLICIES[0].name} | ${PREDEFINED_NETWORK_POLICIES[0]} | ${'container'} | ${'path/to/policy?environment_id=2&type=container_policy&kind=CiliumNetworkPolicy'}
${PREDEFINED_NETWORK_POLICIES[1].name} | ${PREDEFINED_NETWORK_POLICIES[1]} | ${'container'} | ${'path/to/policy?environment_id=2&type=container_policy&kind=CiliumNetworkPolicy'} ${PREDEFINED_NETWORK_POLICIES[1].name} | ${PREDEFINED_NETWORK_POLICIES[1]} | ${'container'} | ${'path/to/policy?environment_id=2&type=container_policy&kind=CiliumNetworkPolicy'}
${'scan execution'} | ${mockScanExecutionPoliciesResponse[0]} | ${'scanExecution'} | ${'path/to/policy?environment_id=2&type=scan_execution_policy'} ${'scan execution'} | ${mockScanExecutionPoliciesResponse[0]} | ${'scanExecution'} | ${'path/to/policy?environment_id=2&type=scan_execution_policy'}
${'scan result'} | ${mockScanResultPoliciesResponse[0]} | ${'scanResult'} | ${'path/to/policy?environment_id=2&type=scan_result_policy'}
`('given there is a $description policy selected', ({ policy, policyType, editPolicyPath }) => { `('given there is a $description policy selected', ({ policy, policyType, editPolicyPath }) => {
beforeEach(() => { beforeEach(() => {
mountShallowWrapper(); mountShallowWrapper();
...@@ -331,7 +344,7 @@ describe('PoliciesList component', () => { ...@@ -331,7 +344,7 @@ describe('PoliciesList component', () => {
}); });
it('does not render default network policies', () => { it('does not render default network policies', () => {
expect(findPolicyStatusCells()).toHaveLength(1); expect(findPolicyStatusCells()).toHaveLength(2);
}); });
}); });
}); });
...@@ -57,6 +57,18 @@ export const scanExecutionPolicies = (nodes) => ...@@ -57,6 +57,18 @@ export const scanExecutionPolicies = (nodes) =>
}, },
}); });
export const scanResultPolicies = (nodes) =>
jest.fn().mockResolvedValue({
data: {
project: {
id: '3',
scanResultPolicies: {
nodes,
},
},
},
});
export const mockLinkSecurityPolicyProjectResponses = { export const mockLinkSecurityPolicyProjectResponses = {
success: jest.fn().mockResolvedValue({ data: { securityPolicyProjectAssign: { errors: [] } } }), success: jest.fn().mockResolvedValue({ data: { securityPolicyProjectAssign: { errors: [] } } }),
failure: jest failure: jest
......
...@@ -200,8 +200,40 @@ export const mockScanExecutionPolicy = { ...@@ -200,8 +200,40 @@ export const mockScanExecutionPolicy = {
latestScan: { date: new Date('2021-06-07T00:00:00.000Z'), pipelineUrl: 'path/to/pipeline' }, latestScan: { date: new Date('2021-06-07T00:00:00.000Z'), pipelineUrl: 'path/to/pipeline' },
}; };
export const mockScanResultManifest = `type: scan_execution_policy
name: critical vulnerability CS approvals
description: This policy enforces critical vulnerability CS approvals
enabled: true
rules:
- type: scan_finding
branches:
- master
scanners:
- container_scanning
vulnerability_allowed: 1
severity_levels:
- critical
vulnerability_states:
- newly_added
actions:
- type: require_approval
approvals_required: 1
user_approvers:
- the.one
`;
export const mockScanResultPolicy = {
__typename: 'ScanResultPolicy',
name: 'critical vulnerability CS approvals',
updatedAt: new Date('2021-06-07T00:00:00.000Z'),
yaml: mockScanResultManifest,
enabled: true,
};
export const mockScanExecutionPoliciesResponse = [mockScanExecutionPolicy]; export const mockScanExecutionPoliciesResponse = [mockScanExecutionPolicy];
export const mockScanResultPoliciesResponse = [mockScanResultPolicy];
export const mockNominalHistory = [ export const mockNominalHistory = [
['2019-12-04T00:00:00.000Z', 56], ['2019-12-04T00:00:00.000Z', 56],
['2019-12-05T00:00:00.000Z', 2647], ['2019-12-05T00:00:00.000Z', 2647],
......
...@@ -31597,12 +31597,18 @@ msgstr "" ...@@ -31597,12 +31597,18 @@ msgstr ""
msgid "SecurityOrchestration|Scan Execution" msgid "SecurityOrchestration|Scan Execution"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Scan Result"
msgstr ""
msgid "SecurityOrchestration|Scan execution" msgid "SecurityOrchestration|Scan execution"
msgstr "" msgstr ""
msgid "SecurityOrchestration|Scan execution policies can only be created by project owners." msgid "SecurityOrchestration|Scan execution policies can only be created by project owners."
msgstr "" msgstr ""
msgid "SecurityOrchestration|Scan result"
msgstr ""
msgid "SecurityOrchestration|Scan to be performed %{cadence}" msgid "SecurityOrchestration|Scan to be performed %{cadence}"
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