Commit cd896116 authored by Enrique Alcántara's avatar Enrique Alcántara

Merge branch '321884-policies-edit-page' into 'master'

Update edit page to account for type parameter

See merge request gitlab-org/gitlab!68416
parents 5fc75197 0ad7ca52
...@@ -48,6 +48,11 @@ export default { ...@@ -48,6 +48,11 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
isEditing: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
const policy = this.existingPolicy const policy = this.existingPolicy
...@@ -85,9 +90,6 @@ export default { ...@@ -85,9 +90,6 @@ export default {
hasParsingError() { hasParsingError() {
return Boolean(this.yamlEditorError); return Boolean(this.yamlEditorError);
}, },
isEditing() {
return Boolean(this.existingPolicy);
},
}, },
methods: { methods: {
...mapActions('networkPolicies', ['createPolicy', 'updatePolicy', 'deletePolicy']), ...mapActions('networkPolicies', ['createPolicy', 'updatePolicy', 'deletePolicy']),
......
...@@ -17,6 +17,7 @@ export default { ...@@ -17,6 +17,7 @@ export default {
ScanExecutionPolicyEditor, ScanExecutionPolicyEditor,
}, },
mixins: [glFeatureFlagMixin()], mixins: [glFeatureFlagMixin()],
inject: ['policyType'],
props: { props: {
assignedPolicyProject: { assignedPolicyProject: {
type: Object, type: Object,
...@@ -31,19 +32,31 @@ export default { ...@@ -31,19 +32,31 @@ export default {
data() { data() {
return { return {
error: '', error: '',
policyType: POLICY_TYPE_COMPONENT_OPTIONS.container.value, newPolicyType: POLICY_TYPE_COMPONENT_OPTIONS.container.value,
}; };
}, },
computed: { computed: {
policyComponent() { currentPolicyType() {
return POLICY_TYPE_COMPONENT_OPTIONS[this.policyType].component; return this.isEditing ? this.policyType : this.newPolicyType;
},
isEditing() {
return Boolean(this.existingPolicy);
},
policyTypes() {
return Object.values(POLICY_TYPE_COMPONENT_OPTIONS);
},
policyOptions() {
return (
this.policyTypes.find((option) => {
return this.isEditing
? option.urlParameter === this.currentPolicyType
: option.value === this.currentPolicyType;
}) || POLICY_TYPE_COMPONENT_OPTIONS.container
);
}, },
shouldAllowPolicyTypeSelection() { shouldAllowPolicyTypeSelection() {
return !this.existingPolicy && this.glFeatures.securityOrchestrationPoliciesConfiguration; return !this.existingPolicy && this.glFeatures.securityOrchestrationPoliciesConfiguration;
}, },
shouldShowEnvironmentPicker() {
return POLICY_TYPE_COMPONENT_OPTIONS[this.policyType].shouldShowEnvironmentPicker;
},
}, },
created() { created() {
this.fetchEnvironments(); this.fetchEnvironments();
...@@ -53,11 +66,10 @@ export default { ...@@ -53,11 +66,10 @@ export default {
setError(error) { setError(error) {
this.error = error; this.error = error;
}, },
updatePolicyType(type) { handleNewPolicyType(type) {
this.policyType = type; this.newPolicyType = type;
}, },
}, },
policyTypes: Object.values(POLICY_TYPE_COMPONENT_OPTIONS),
}; };
</script> </script>
...@@ -73,18 +85,19 @@ export default { ...@@ -73,18 +85,19 @@ export default {
<gl-form-group :label="s__('NetworkPolicies|Policy type')" label-for="policyType"> <gl-form-group :label="s__('NetworkPolicies|Policy type')" label-for="policyType">
<gl-form-select <gl-form-select
id="policyType" id="policyType"
:value="policyType" :value="policyOptions.value"
:options="$options.policyTypes" :options="policyTypes"
:disabled="!shouldAllowPolicyTypeSelection" :disabled="!shouldAllowPolicyTypeSelection"
@change="updatePolicyType" @change="handleNewPolicyType"
/> />
</gl-form-group> </gl-form-group>
<environment-picker v-if="shouldShowEnvironmentPicker" class="gl-ml-5" /> <environment-picker v-if="policyOptions.shouldShowEnvironmentPicker" class="gl-ml-5" />
</div> </div>
<component <component
:is="policyComponent" :is="policyOptions.component"
:existing-policy="existingPolicy" :existing-policy="existingPolicy"
:assigned-policy-project="assignedPolicyProject" :assigned-policy-project="assignedPolicyProject"
:is-editing="isEditing"
@error="setError($event)" @error="setError($event)"
/> />
</section> </section>
......
export { fromYaml } from './from_yaml'; export { fromYaml } from './from_yaml';
export { toYaml } from './to_yaml';
export * from './constants'; export * from './constants';
export * from './utils'; export * from './utils';
......
import { safeDump } from 'js-yaml';
/*
Return yaml representation of a policy.
*/
export const toYaml = (policy) => {
return safeDump(policy);
};
<script> <script>
import { removeUnnecessaryDashes } from 'ee/threat_monitoring/utils';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { EDITOR_MODES, EDITOR_MODE_YAML } from '../constants'; import { EDITOR_MODES, EDITOR_MODE_YAML } from '../constants';
...@@ -9,8 +8,9 @@ import { ...@@ -9,8 +8,9 @@ import {
fromYaml, fromYaml,
GRAPHQL_ERROR_MESSAGE, GRAPHQL_ERROR_MESSAGE,
modifyPolicy, modifyPolicy,
SECURITY_POLICY_ACTIONS,
toYaml,
} from './lib'; } from './lib';
import { SECURITY_POLICY_ACTIONS } from './lib/constants';
export default { export default {
SECURITY_POLICY_ACTIONS, SECURITY_POLICY_ACTIONS,
...@@ -33,10 +33,15 @@ export default { ...@@ -33,10 +33,15 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
isEditing: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
const yamlEditorValue = this.existingPolicy const yamlEditorValue = this.existingPolicy
? removeUnnecessaryDashes(this.existingPolicy.manifest) ? toYaml(this.existingPolicy)
: DEFAULT_SCAN_EXECUTION_POLICY; : DEFAULT_SCAN_EXECUTION_POLICY;
return { return {
...@@ -47,11 +52,6 @@ export default { ...@@ -47,11 +52,6 @@ export default {
yamlEditorValue, yamlEditorValue,
}; };
}, },
computed: {
isEditing() {
return Boolean(this.existingPolicy);
},
},
methods: { methods: {
handleError(error) { handleError(error) {
if (error.message.toLowerCase().includes('graphql')) { if (error.message.toLowerCase().includes('graphql')) {
......
...@@ -23,6 +23,7 @@ export default () => { ...@@ -23,6 +23,7 @@ export default () => {
networkPoliciesEndpoint, networkPoliciesEndpoint,
threatMonitoringPath, threatMonitoringPath,
policy, policy,
policyType,
projectPath, projectPath,
projectId, projectId,
environmentId, environmentId,
...@@ -46,7 +47,7 @@ export default () => { ...@@ -46,7 +47,7 @@ export default () => {
}; };
if (policy) { if (policy) {
props.existingPolicy = JSON.parse(policy); props.existingPolicy = { type: policyType, ...JSON.parse(policy) };
} }
return new Vue({ return new Vue({
...@@ -56,6 +57,7 @@ export default () => { ...@@ -56,6 +57,7 @@ export default () => {
configureAgentHelpPath, configureAgentHelpPath,
createAgentHelpPath, createAgentHelpPath,
disableScanExecutionUpdate: parseBoolean(disableScanExecutionUpdate), disableScanExecutionUpdate: parseBoolean(disableScanExecutionUpdate),
policyType,
projectId, projectId,
projectPath, projectPath,
threatMonitoringPath, threatMonitoringPath,
......
...@@ -265,7 +265,7 @@ describe('NetworkPolicyEditor component', () => { ...@@ -265,7 +265,7 @@ describe('NetworkPolicyEditor component', () => {
}); });
}); });
describe('given existingPolicy property was provided', () => { describe('editing a policy', () => {
const manifest = toYaml({ const manifest = toYaml({
name: 'policy', name: 'policy',
endpointLabels: '', endpointLabels: '',
...@@ -276,6 +276,7 @@ describe('NetworkPolicyEditor component', () => { ...@@ -276,6 +276,7 @@ describe('NetworkPolicyEditor component', () => {
factory({ factory({
propsData: { propsData: {
existingPolicy: { name: 'policy', manifest }, existingPolicy: { name: 'policy', manifest },
isEditing: true,
}, },
}); });
}); });
......
...@@ -4,9 +4,10 @@ import { POLICY_TYPE_COMPONENT_OPTIONS } from 'ee/threat_monitoring/components/c ...@@ -4,9 +4,10 @@ import { POLICY_TYPE_COMPONENT_OPTIONS } from 'ee/threat_monitoring/components/c
import EnvironmentPicker from 'ee/threat_monitoring/components/environment_picker.vue'; import EnvironmentPicker from 'ee/threat_monitoring/components/environment_picker.vue';
import NetworkPolicyEditor from 'ee/threat_monitoring/components/policy_editor/network_policy/network_policy_editor.vue'; import NetworkPolicyEditor from 'ee/threat_monitoring/components/policy_editor/network_policy/network_policy_editor.vue';
import PolicyEditor from 'ee/threat_monitoring/components/policy_editor/policy_editor.vue'; import PolicyEditor from 'ee/threat_monitoring/components/policy_editor/policy_editor.vue';
import ScanExecutionPolicyEditor from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/scan_execution_policy_editor.vue';
import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants'; import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants';
import createStore from 'ee/threat_monitoring/store'; import createStore from 'ee/threat_monitoring/store';
import { mockL3Manifest } from '../../mocks/mock_data'; import { mockDastScanExecutionObject, mockL3Manifest } from '../../mocks/mock_data';
describe('PolicyEditor component', () => { describe('PolicyEditor component', () => {
let store; let store;
...@@ -16,6 +17,7 @@ describe('PolicyEditor component', () => { ...@@ -16,6 +17,7 @@ describe('PolicyEditor component', () => {
const findEnvironmentPicker = () => wrapper.findComponent(EnvironmentPicker); const findEnvironmentPicker = () => wrapper.findComponent(EnvironmentPicker);
const findFormSelect = () => wrapper.findComponent(GlFormSelect); const findFormSelect = () => wrapper.findComponent(GlFormSelect);
const findNeworkPolicyEditor = () => wrapper.findComponent(NetworkPolicyEditor); const findNeworkPolicyEditor = () => wrapper.findComponent(NetworkPolicyEditor);
const findScanExecutionPolicyEditor = () => wrapper.findComponent(ScanExecutionPolicyEditor);
const factory = ({ propsData = {}, provide = {} } = {}) => { const factory = ({ propsData = {}, provide = {} } = {}) => {
store = createStore(); store = createStore();
...@@ -27,7 +29,10 @@ describe('PolicyEditor component', () => { ...@@ -27,7 +29,10 @@ describe('PolicyEditor component', () => {
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT, assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
...propsData, ...propsData,
}, },
provide, provide: {
policyType: undefined,
...provide,
},
store, store,
stubs: { GlFormSelect }, stubs: { GlFormSelect },
}); });
...@@ -64,19 +69,44 @@ describe('PolicyEditor component', () => { ...@@ -64,19 +69,44 @@ describe('PolicyEditor component', () => {
expect(alert.exists()).toBe(true); expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(errorMessage); expect(alert.text()).toBe(errorMessage);
}); });
it.each`
policyType | option | findComponent
${'container'} | ${POLICY_TYPE_COMPONENT_OPTIONS.container} | ${findNeworkPolicyEditor}
${'scanExecution'} | ${POLICY_TYPE_COMPONENT_OPTIONS.scanExecution} | ${findScanExecutionPolicyEditor}
`(
'renders the policy editor of type $policyType when selected',
async ({ findComponent, option, policyType }) => {
const formSelect = findFormSelect();
formSelect.vm.$emit('change', policyType);
await wrapper.vm.$nextTick();
const component = findComponent();
expect(formSelect.attributes('value')).toBe(option.value);
expect(component.exists()).toBe(true);
expect(component.props('isEditing')).toBe(false);
},
);
}); });
describe('when an existing policy is present', () => { describe('when an existing policy is present', () => {
beforeEach(() => { it.each`
factory({ propsData: { existingPolicy: { manifest: mockL3Manifest } } }); policyType | option | existingPolicy | findComponent
}); ${'container_policy'} | ${POLICY_TYPE_COMPONENT_OPTIONS.container} | ${{ manifest: mockL3Manifest }} | ${findNeworkPolicyEditor}
${'scan_execution_policy'} | ${POLICY_TYPE_COMPONENT_OPTIONS.scanExecution} | ${mockDastScanExecutionObject} | ${findScanExecutionPolicyEditor}
it('renders the disabled form select', () => { `(
const formSelect = findFormSelect(); 'renders the disabled form select for existing policy of type $policyType',
expect(formSelect.exists()).toBe(true); async ({ existingPolicy, findComponent, option, policyType }) => {
expect(formSelect.attributes('value')).toBe(POLICY_TYPE_COMPONENT_OPTIONS.container.value); factory({ propsData: { existingPolicy }, provide: { policyType } });
expect(formSelect.attributes('disabled')).toBe('true'); await wrapper.vm.$nextTick();
}); const formSelect = findFormSelect();
expect(formSelect.exists()).toBe(true);
expect(formSelect.attributes('value')).toBe(option.value);
expect(formSelect.attributes('disabled')).toBe('true');
const component = findComponent();
expect(component.exists()).toBe(true);
expect(component.props('isEditing')).toBe(true);
},
);
}); });
describe('with "securityOrchestrationPoliciesConfiguration" feature flag enabled', () => { describe('with "securityOrchestrationPoliciesConfiguration" feature flag enabled', () => {
......
import { fromYaml } from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib';
import { import {
DEFAULT_SCAN_EXECUTION_POLICY, mockDastScanExecutionManifest,
fromYaml, mockDastScanExecutionObject,
} from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib'; } from 'ee_jest/threat_monitoring/mocks/mock_data';
describe('fromYaml', () => { describe('fromYaml', () => {
it('returns policy object', () => { it('returns policy object', () => {
expect(fromYaml(DEFAULT_SCAN_EXECUTION_POLICY)).toMatchObject({ expect(fromYaml(mockDastScanExecutionManifest)).toStrictEqual(mockDastScanExecutionObject);
name: '',
description: '',
enabled: false,
actions: [{ scan: 'dast', site_profile: '', scanner_profile: '' }],
rules: [{ branches: ['main'], type: 'pipeline' }],
});
}); });
}); });
import { toYaml } from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib';
import {
mockDastScanExecutionManifest,
mockDastScanExecutionObject,
} from 'ee_jest/threat_monitoring/mocks/mock_data';
describe('toYaml', () => {
it('returns policy object as yaml', () => {
expect(toYaml(mockDastScanExecutionObject)).toBe(mockDastScanExecutionManifest);
});
});
...@@ -3,10 +3,14 @@ import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/po ...@@ -3,10 +3,14 @@ import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/po
import { import {
DEFAULT_SCAN_EXECUTION_POLICY, DEFAULT_SCAN_EXECUTION_POLICY,
modifyPolicy, modifyPolicy,
SECURITY_POLICY_ACTIONS,
} from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib'; } from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib';
import { SECURITY_POLICY_ACTIONS } from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib/constants';
import ScanExecutionPolicyEditor from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/scan_execution_policy_editor.vue'; import ScanExecutionPolicyEditor from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/scan_execution_policy_editor.vue';
import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants'; import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants';
import {
mockDastScanExecutionManifest,
mockDastScanExecutionObject,
} from 'ee_jest/threat_monitoring/mocks/mock_data';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
...@@ -21,6 +25,12 @@ jest.mock('ee/threat_monitoring/components/policy_editor/scan_execution_policy/l ...@@ -21,6 +25,12 @@ jest.mock('ee/threat_monitoring/components/policy_editor/scan_execution_policy/l
fromYaml: jest.requireActual( fromYaml: jest.requireActual(
'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib', 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib',
).fromYaml, ).fromYaml,
toYaml: jest.requireActual(
'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib',
).toYaml,
SECURITY_POLICY_ACTIONS: jest.requireActual(
'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib',
).SECURITY_POLICY_ACTIONS,
modifyPolicy: jest.fn().mockResolvedValue({ modifyPolicy: jest.fn().mockResolvedValue({
mergeRequest: { id: '2' }, mergeRequest: { id: '2' },
policyProject: { fullPath: 'tests' }, policyProject: { fullPath: 'tests' },
...@@ -45,6 +55,10 @@ describe('ScanExecutionPolicyEditor', () => { ...@@ -45,6 +55,10 @@ describe('ScanExecutionPolicyEditor', () => {
}); });
}; };
const factoryWithExistingPolicy = () => {
return factory({ propsData: { existingPolicy: mockDastScanExecutionObject, isEditing: true } });
};
const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout); const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout);
afterEach(() => { afterEach(() => {
...@@ -63,13 +77,13 @@ describe('ScanExecutionPolicyEditor', () => { ...@@ -63,13 +77,13 @@ describe('ScanExecutionPolicyEditor', () => {
}); });
it.each` it.each`
status | action | event | factoryFn status | action | event | factoryFn | yamlEditorValue
${'to save a new policy'} | ${SECURITY_POLICY_ACTIONS.APPEND} | ${'save-policy'} | ${factory} ${'to save a new policy'} | ${SECURITY_POLICY_ACTIONS.APPEND} | ${'save-policy'} | ${factory} | ${DEFAULT_SCAN_EXECUTION_POLICY}
${'to update an existing policy'} | ${SECURITY_POLICY_ACTIONS.REPLACE} | ${'save-policy'} | ${() => factory({ propsData: { existingPolicy: { manifest: DEFAULT_SCAN_EXECUTION_POLICY } } })} ${'to update an existing policy'} | ${SECURITY_POLICY_ACTIONS.REPLACE} | ${'save-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
${'to delete an existing policy'} | ${SECURITY_POLICY_ACTIONS.REMOVE} | ${'remove-policy'} | ${() => factory({ propsData: { existingPolicy: { manifest: DEFAULT_SCAN_EXECUTION_POLICY } } })} ${'to delete an existing policy'} | ${SECURITY_POLICY_ACTIONS.REMOVE} | ${'remove-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
`( `(
'navigates to the new merge request when "modifyPolicy" is emitted $status', 'navigates to the new merge request when "modifyPolicy" is emitted $status',
async ({ action, event, factoryFn }) => { async ({ action, event, factoryFn, yamlEditorValue }) => {
factoryFn(); factoryFn();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
findPolicyEditorLayout().vm.$emit(event); findPolicyEditorLayout().vm.$emit(event);
...@@ -79,7 +93,7 @@ describe('ScanExecutionPolicyEditor', () => { ...@@ -79,7 +93,7 @@ describe('ScanExecutionPolicyEditor', () => {
action, action,
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT, assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
projectPath: defaultProjectPath, projectPath: defaultProjectPath,
yamlEditorValue: DEFAULT_SCAN_EXECUTION_POLICY, yamlEditorValue,
}); });
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(visitUrl).toHaveBeenCalled(); expect(visitUrl).toHaveBeenCalled();
......
...@@ -221,8 +221,8 @@ export const mockAlertDetails = { ...@@ -221,8 +221,8 @@ export const mockAlertDetails = {
}; };
export const mockDastScanExecutionManifest = `type: scan_execution_policy export const mockDastScanExecutionManifest = `type: scan_execution_policy
name: 'Test Dast' name: Test Dast
description: 'This is a good test' description: This is a good test
enabled: false enabled: false
rules: rules:
- type: pipeline - type: pipeline
...@@ -230,10 +230,25 @@ rules: ...@@ -230,10 +230,25 @@ rules:
- main - main
actions: actions:
- scan: dast - scan: dast
site_profile: 'required_site_profile' site_profile: required_site_profile
scanner_profile: 'required_scanner_profile' scanner_profile: required_scanner_profile
`; `;
export const mockDastScanExecutionObject = {
type: 'scan_execution_policy',
name: 'Test Dast',
description: 'This is a good test',
enabled: false,
rules: [{ type: 'pipeline', branches: ['main'] }],
actions: [
{
scan: 'dast',
site_profile: 'required_site_profile',
scanner_profile: 'required_scanner_profile',
},
],
};
export const mockL7Manifest = `apiVersion: cilium.io/v2 export const mockL7Manifest = `apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy kind: CiliumNetworkPolicy
metadata: metadata:
......
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