Commit 0716b8a1 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch 'add_scan_result_policy_into_policy_list' into 'master'

Add scan result policy into policy list

See merge request gitlab-org/gitlab!77778
parents 741814dd f8846f09
......@@ -42,6 +42,13 @@ export const POLICY_TYPE_COMPONENT_OPTIONS = {
urlParameter: 'scan_execution_policy',
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 = {
......@@ -53,6 +60,10 @@ export const POLICY_TYPE_OPTIONS = {
value: 'POLICY_TYPE_SCAN_EXECUTION',
text: s__('SecurityOrchestration|Scan execution'),
},
POLICY_TYPE_SCAN_RESULT: {
value: 'POLICY_TYPE_SCAN_RESULT',
text: s__('SecurityOrchestration|Scan result'),
},
ALL: {
value: '',
text: s__('SecurityOrchestration|All policies'),
......
......@@ -14,8 +14,10 @@ import createFlash from '~/flash';
import { getTimeago } from '~/lib/utils/datetime_utility';
import { setUrlFragment, mergeUrlParams } from '~/lib/utils/url_utility';
import { __, s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import networkPoliciesQuery from '../../graphql/queries/network_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 { POLICY_TYPE_COMPONENT_OPTIONS, POLICY_TYPE_OPTIONS } from '../constants';
import EnvironmentPicker from '../environment_picker.vue';
......@@ -57,6 +59,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [glFeatureFlagMixin()],
inject: ['documentationPath', 'projectPath', 'newPolicyPath'],
props: {
shouldUpdatePolicyList: {
......@@ -100,12 +103,25 @@ export default {
},
error: createPolicyFetchError,
},
scanResultPolicies: {
query: scanResultPoliciesQuery,
variables() {
return {
fullPath: this.projectPath,
};
},
update(data) {
return data?.project?.scanResultPolicies?.nodes ?? [];
},
error: createPolicyFetchError,
},
},
data() {
return {
selectedPolicy: null,
networkPolicies: [],
scanExecutionPolicies: [],
scanResultPolicies: [],
selectedPolicyType: POLICY_TYPE_OPTIONS.ALL.value,
};
},
......@@ -113,10 +129,14 @@ export default {
...mapState('threatMonitoring', ['allEnvironments', 'currentEnvironmentId', 'hasEnvironment']),
...mapGetters('threatMonitoring', ['currentEnvironmentGid']),
allPolicyTypes() {
return {
const allTypes = {
[POLICY_TYPE_OPTIONS.POLICY_TYPE_NETWORK.value]: this.networkPolicies,
[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() {
return setUrlFragment(this.documentationPath, 'container-network-policy');
......@@ -141,7 +161,8 @@ export default {
isLoadingPolicies() {
return (
this.$apollo.queries.networkPolicies.loading ||
this.$apollo.queries.scanExecutionPolicies.loading
this.$apollo.queries.scanExecutionPolicies.loading ||
this.$apollo.queries.scanResultPolicies.loading
);
},
hasSelectedPolicy() {
......@@ -207,11 +228,15 @@ export default {
return fields;
},
isFeatureEnabled() {
return this.glFeatures.scanResultPolicy;
},
},
watch: {
shouldUpdatePolicyList(newShouldUpdatePolicyList) {
if (newShouldUpdatePolicyList) {
this.$apollo.queries.scanExecutionPolicies.refetch();
this.$apollo.queries.scanResultPolicies.refetch();
this.$emit('update-policy-list', false);
}
},
......
<script>
import { GlFormGroup, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { POLICY_TYPE_OPTIONS } from './constants';
export default {
......@@ -10,6 +11,7 @@ export default {
GlDropdown,
GlDropdownItem,
},
mixins: [glFeatureFlagMixin()],
props: {
value: {
type: String,
......@@ -24,6 +26,16 @@ export default {
selectedValueText() {
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: {
setPolicyType({ value }) {
......@@ -51,7 +63,7 @@ export default {
:text="selectedValueText"
>
<gl-dropdown-item
v-for="option in $options.POLICY_TYPE_OPTIONS"
v-for="option in policyTypeOptions"
:key="option.value"
:data-testid="`policy-type-${option.value}-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 = '') => {
if (typeName === POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.typeName) {
return POLICY_TYPE_COMPONENT_OPTIONS.scanExecution.value;
}
if (typeName === POLICY_TYPE_COMPONENT_OPTIONS.scanResult.typeName) {
return POLICY_TYPE_COMPONENT_OPTIONS.scanResult.value;
}
return null;
};
......
......@@ -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 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 scanResultPoliciesQuery from 'ee/threat_monitoring/graphql/queries/scan_result_policies.query.graphql';
import createStore from 'ee/threat_monitoring/store';
import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { networkPolicies, scanExecutionPolicies } from '../../mocks/mock_apollo';
import {
networkPolicies,
scanExecutionPolicies,
scanResultPolicies,
} from '../../mocks/mock_apollo';
import {
mockNetworkPoliciesResponse,
mockScanExecutionPoliciesResponse,
mockScanResultPoliciesResponse,
} from '../../mocks/mock_data';
const localVue = createLocalVue();
......@@ -35,9 +41,11 @@ const environments = [
];
const networkPoliciesSpy = networkPolicies(mockNetworkPoliciesResponse);
const scanExecutionPoliciesSpy = scanExecutionPolicies(mockScanExecutionPoliciesResponse);
const scanResultPoliciesSpy = scanResultPolicies(mockScanResultPoliciesResponse);
const defaultRequestHandlers = {
networkPolicies: networkPoliciesSpy,
scanExecutionPolicies: scanExecutionPoliciesSpy,
scanResultPolicies: scanResultPoliciesSpy,
};
const pendingHandler = jest.fn(() => new Promise(() => {}));
......@@ -81,10 +89,12 @@ describe('PoliciesList component', () => {
documentationPath: 'path/to/docs',
newPolicyPath: 'path/to/policy',
projectPath: fullPath,
glFeatures: { scanResultPolicy: true },
},
apolloProvider: createMockApollo([
[networkPoliciesQuery, requestHandlers.networkPolicies],
[scanExecutionPoliciesQuery, requestHandlers.scanExecutionPolicies],
[scanResultPoliciesQuery, requestHandlers.scanResultPolicies],
]),
stubs: {
PolicyDrawer: stubComponent(PolicyDrawer, {
......@@ -163,7 +173,7 @@ describe('PoliciesList component', () => {
});
it('does render default network policies', () => {
expect(findPolicyStatusCells()).toHaveLength(5);
expect(findPolicyStatusCells()).toHaveLength(6);
});
it('fetches network policies on environment change', async () => {
......@@ -190,9 +200,10 @@ describe('PoliciesList component', () => {
describe.each`
rowIndex | expectedPolicyName | expectedPolicyType
${1} | ${mockScanExecutionPoliciesResponse[0].name} | ${'Scan execution'}
${3} | ${mockNetworkPoliciesResponse[0].name} | ${'Network'}
${4} | ${PREDEFINED_NETWORK_POLICIES[0].name} | ${'Network'}
${5} | ${PREDEFINED_NETWORK_POLICIES[1].name} | ${'Network'}
${2} | ${mockScanResultPoliciesResponse[0].name} | ${'Scan result'}
${3} | ${mockNetworkPoliciesResponse[1].name} | ${'Network'}
${4} | ${mockNetworkPoliciesResponse[0].name} | ${'Network'}
${5} | ${PREDEFINED_NETWORK_POLICIES[0].name} | ${'Network'}
`('policy in row #$rowIndex', ({ rowIndex, expectedPolicyName, expectedPolicyType }) => {
let row;
......@@ -211,8 +222,9 @@ describe('PoliciesList component', () => {
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]}
${'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, 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 }) => {
findPolicyTypeFilter().vm.$emit('input', filterBy.value);
await wrapper.vm.$nextTick();
......@@ -258,7 +270,7 @@ describe('PoliciesList component', () => {
});
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.attributes('class')).toBe('gl-sr-only');
......@@ -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[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 result'} | ${mockScanResultPoliciesResponse[0]} | ${'scanResult'} | ${'path/to/policy?environment_id=2&type=scan_result_policy'}
`('given there is a $description policy selected', ({ policy, policyType, editPolicyPath }) => {
beforeEach(() => {
mountShallowWrapper();
......@@ -331,7 +344,7 @@ describe('PoliciesList component', () => {
});
it('does not render default network policies', () => {
expect(findPolicyStatusCells()).toHaveLength(1);
expect(findPolicyStatusCells()).toHaveLength(2);
});
});
});
......@@ -57,6 +57,18 @@ export const scanExecutionPolicies = (nodes) =>
},
});
export const scanResultPolicies = (nodes) =>
jest.fn().mockResolvedValue({
data: {
project: {
id: '3',
scanResultPolicies: {
nodes,
},
},
},
});
export const mockLinkSecurityPolicyProjectResponses = {
success: jest.fn().mockResolvedValue({ data: { securityPolicyProjectAssign: { errors: [] } } }),
failure: jest
......
......@@ -200,8 +200,40 @@ export const mockScanExecutionPolicy = {
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 mockScanResultPoliciesResponse = [mockScanResultPolicy];
export const mockNominalHistory = [
['2019-12-04T00:00:00.000Z', 56],
['2019-12-05T00:00:00.000Z', 2647],
......
......@@ -31642,12 +31642,18 @@ msgstr ""
msgid "SecurityOrchestration|Scan Execution"
msgstr ""
msgid "SecurityOrchestration|Scan Result"
msgstr ""
msgid "SecurityOrchestration|Scan execution"
msgstr ""
msgid "SecurityOrchestration|Scan execution policies can only be created by project owners."
msgstr ""
msgid "SecurityOrchestration|Scan result"
msgstr ""
msgid "SecurityOrchestration|Scan to be performed %{cadence}"
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