Commit 4da0b54f authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '273789-edit-delete-scan-execution-policy' into 'master'

Allow for update/delete of scan execution policies

See merge request gitlab-org/gitlab!66580
parents b493fbed 2a555301
...@@ -5,3 +5,9 @@ export const DEFAULT_MR_TITLE = s__('SecurityOrchestration|Update scan execution ...@@ -5,3 +5,9 @@ export const DEFAULT_MR_TITLE = s__('SecurityOrchestration|Update scan execution
export const GRAPHQL_ERROR_MESSAGE = s__( export const GRAPHQL_ERROR_MESSAGE = s__(
'SecurityOrchestration|There was a problem creating the new security policy', 'SecurityOrchestration|There was a problem creating the new security policy',
); );
export const SECURITY_POLICY_ACTIONS = {
APPEND: 'APPEND',
REMOVE: 'REMOVE',
REPLACE: 'REPLACE',
};
...@@ -2,7 +2,7 @@ import createPolicyProject from 'ee/threat_monitoring/graphql/mutations/create_p ...@@ -2,7 +2,7 @@ import createPolicyProject from 'ee/threat_monitoring/graphql/mutations/create_p
import createScanExecutionPolicy from 'ee/threat_monitoring/graphql/mutations/create_scan_execution_policy.mutation.graphql'; import createScanExecutionPolicy from 'ee/threat_monitoring/graphql/mutations/create_scan_execution_policy.mutation.graphql';
import { gqClient } from 'ee/threat_monitoring/utils'; import { gqClient } from 'ee/threat_monitoring/utils';
import createMergeRequestMutation from '~/graphql_shared/mutations/create_merge_request.mutation.graphql'; import createMergeRequestMutation from '~/graphql_shared/mutations/create_merge_request.mutation.graphql';
import { DEFAULT_MR_TITLE } from './constants'; import { DEFAULT_MR_TITLE, SECURITY_POLICY_ACTIONS } from './constants';
/** /**
* Checks if an error exists and throws it if it does * Checks if an error exists and throws it if it does
...@@ -67,7 +67,11 @@ const createMergeRequest = async ({ projectPath, sourceBranch, targetBranch }) = ...@@ -67,7 +67,11 @@ const createMergeRequest = async ({ projectPath, sourceBranch, targetBranch }) =
* @param {Object} payload contains the path to the project and the policy yaml value * @param {Object} payload contains the path to the project and the policy yaml value
* @returns {Object} contains the branch containing the updated policy file and any errors * @returns {Object} contains the branch containing the updated policy file and any errors
*/ */
const updatePolicy = async ({ projectPath, yamlEditorValue }) => { const updatePolicy = async ({
action = SECURITY_POLICY_ACTIONS.APPEND,
projectPath,
yamlEditorValue,
}) => {
const { const {
data: { data: {
scanExecutionPolicyCommit: { branch, errors }, scanExecutionPolicyCommit: { branch, errors },
...@@ -75,6 +79,7 @@ const updatePolicy = async ({ projectPath, yamlEditorValue }) => { ...@@ -75,6 +79,7 @@ const updatePolicy = async ({ projectPath, yamlEditorValue }) => {
} = await gqClient.mutate({ } = await gqClient.mutate({
mutation: createScanExecutionPolicy, mutation: createScanExecutionPolicy,
variables: { variables: {
mode: action,
projectPath, projectPath,
policyYaml: yamlEditorValue, policyYaml: yamlEditorValue,
}, },
...@@ -88,7 +93,12 @@ const updatePolicy = async ({ projectPath, yamlEditorValue }) => { ...@@ -88,7 +93,12 @@ const updatePolicy = async ({ projectPath, yamlEditorValue }) => {
* @param {Object} payload contains the currently assigned security policy project (if one exists), the path to the project, and the policy yaml value * @param {Object} payload contains the currently assigned security policy project (if one exists), the path to the project, and the policy yaml value
* @returns {Object} contains the currently assigned security policy project and the created merge request * @returns {Object} contains the currently assigned security policy project and the created merge request
*/ */
export const savePolicy = async ({ assignedPolicyProject, projectPath, yamlEditorValue }) => { export const modifyPolicy = async ({
action,
assignedPolicyProject,
projectPath,
yamlEditorValue,
}) => {
let currentAssignedPolicyProject = assignedPolicyProject; let currentAssignedPolicyProject = assignedPolicyProject;
if (!currentAssignedPolicyProject.fullPath) { if (!currentAssignedPolicyProject.fullPath) {
...@@ -98,6 +108,7 @@ export const savePolicy = async ({ assignedPolicyProject, projectPath, yamlEdito ...@@ -98,6 +108,7 @@ export const savePolicy = async ({ assignedPolicyProject, projectPath, yamlEdito
checkForErrors(currentAssignedPolicyProject); checkForErrors(currentAssignedPolicyProject);
const newPolicyCommitBranch = await updatePolicy({ const newPolicyCommitBranch = await updatePolicy({
action,
projectPath, projectPath,
yamlEditorValue, yamlEditorValue,
}); });
...@@ -112,5 +123,5 @@ export const savePolicy = async ({ assignedPolicyProject, projectPath, yamlEdito ...@@ -112,5 +123,5 @@ export const savePolicy = async ({ assignedPolicyProject, projectPath, yamlEdito
checkForErrors(mergeRequest); checkForErrors(mergeRequest);
return { currentAssignedPolicyProject, mergeRequest }; return { mergeRequest, policyProject: currentAssignedPolicyProject };
}; };
...@@ -4,9 +4,16 @@ import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; ...@@ -4,9 +4,16 @@ 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';
import PolicyEditorLayout from '../policy_editor_layout.vue'; import PolicyEditorLayout from '../policy_editor_layout.vue';
import { DEFAULT_SCAN_EXECUTION_POLICY, fromYaml, GRAPHQL_ERROR_MESSAGE, savePolicy } from './lib'; import {
DEFAULT_SCAN_EXECUTION_POLICY,
fromYaml,
GRAPHQL_ERROR_MESSAGE,
modifyPolicy,
} from './lib';
import { SECURITY_POLICY_ACTIONS } from './lib/constants';
export default { export default {
SECURITY_POLICY_ACTIONS,
DEFAULT_EDITOR_MODE: EDITOR_MODE_YAML, DEFAULT_EDITOR_MODE: EDITOR_MODE_YAML,
EDITOR_MODES: [EDITOR_MODES[1]], EDITOR_MODES: [EDITOR_MODES[1]],
i18n: { i18n: {
...@@ -35,6 +42,7 @@ export default { ...@@ -35,6 +42,7 @@ export default {
return { return {
error: '', error: '',
isCreatingMR: false, isCreatingMR: false,
isRemovingPolicy: false,
policy: fromYaml(yamlEditorValue), policy: fromYaml(yamlEditorValue),
yamlEditorValue, yamlEditorValue,
}; };
...@@ -45,34 +53,54 @@ export default { ...@@ -45,34 +53,54 @@ export default {
}, },
}, },
methods: { methods: {
async handleSavePolicy() { handleError(error) {
if (error.message.toLowerCase().includes('graphql')) {
this.$emit('error', GRAPHQL_ERROR_MESSAGE);
} else {
this.$emit('error', error.message);
}
},
async handleModifyPolicy(act) {
const action =
act ||
(this.isEditing
? this.$options.SECURITY_POLICY_ACTIONS.REPLACE
: this.$options.SECURITY_POLICY_ACTIONS.APPEND);
this.$emit('error', ''); this.$emit('error', '');
this.isCreatingMR = true; this.setLoadingFlag(action, true);
try { try {
const { currentAssignedPolicyProject, mergeRequest } = await savePolicy({ const { mergeRequest, policyProject } = await modifyPolicy({
action,
assignedPolicyProject: this.assignedPolicyProject, assignedPolicyProject: this.assignedPolicyProject,
projectPath: this.projectPath, projectPath: this.projectPath,
yamlEditorValue: this.yamlEditorValue, yamlEditorValue: this.yamlEditorValue,
}); });
visitUrl( this.redirectToMergeRequest({ mergeRequest, policyProject });
joinPaths(
gon.relative_url_root || '/',
currentAssignedPolicyProject.fullPath,
'/-/merge_requests',
mergeRequest.id,
),
);
} catch (e) { } catch (e) {
if (e.message.toLowerCase().includes('graphql')) { this.handleError(e);
this.$emit('error', GRAPHQL_ERROR_MESSAGE); this.setLoadingFlag(action, false);
} else {
this.$emit('error', e.message);
}
this.isCreatingMR = false;
} }
}, },
setLoadingFlag(action, val) {
if (action === SECURITY_POLICY_ACTIONS.REMOVE) {
this.isRemovingPolicy = val;
} else {
this.isCreatingMR = val;
}
},
redirectToMergeRequest({ mergeRequest, policyProject }) {
visitUrl(
joinPaths(
gon.relative_url_root || '/',
policyProject.fullPath,
'/-/merge_requests',
mergeRequest.id,
),
);
},
updateYaml(manifest) { updateYaml(manifest) {
this.yamlEditorValue = manifest; this.yamlEditorValue = manifest;
}, },
...@@ -86,10 +114,12 @@ export default { ...@@ -86,10 +114,12 @@ export default {
:disable-update="disableScanExecutionUpdate" :disable-update="disableScanExecutionUpdate"
:editor-modes="$options.EDITOR_MODES" :editor-modes="$options.EDITOR_MODES"
:is-editing="isEditing" :is-editing="isEditing"
:is-removing-policy="isRemovingPolicy"
:is-updating-policy="isCreatingMR" :is-updating-policy="isCreatingMR"
:policy-name="policy.name" :policy-name="policy.name"
:yaml-editor-value="yamlEditorValue" :yaml-editor-value="yamlEditorValue"
@save-policy="handleSavePolicy" @remove-policy="handleModifyPolicy($options.SECURITY_POLICY_ACTIONS.REMOVE)"
@save-policy="handleModifyPolicy()"
@update-yaml="updateYaml" @update-yaml="updateYaml"
> >
<template #save-button-text> <template #save-button-text>
......
import { savePolicy } from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib/utils'; import { modifyPolicy } from 'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib/utils';
import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants'; import { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants';
import createPolicyProject from 'ee/threat_monitoring/graphql/mutations/create_policy_project.mutation.graphql'; import createPolicyProject from 'ee/threat_monitoring/graphql/mutations/create_policy_project.mutation.graphql';
import createScanExecutionPolicy from 'ee/threat_monitoring/graphql/mutations/create_scan_execution_policy.mutation.graphql'; import createScanExecutionPolicy from 'ee/threat_monitoring/graphql/mutations/create_scan_execution_policy.mutation.graphql';
...@@ -15,7 +15,8 @@ const newAssignedPolicyProject = { ...@@ -15,7 +15,8 @@ const newAssignedPolicyProject = {
}; };
const projectPath = 'path/to/current-project'; const projectPath = 'path/to/current-project';
const yamlEditorValue = 'some yaml'; const yamlEditorValue = 'some yaml';
const createSavePolicyInput = (assignedPolicyProject = defaultAssignedPolicyProject) => ({ const createSavePolicyInput = (assignedPolicyProject = defaultAssignedPolicyProject, action) => ({
action,
assignedPolicyProject, assignedPolicyProject,
projectPath, projectPath,
yamlEditorValue, yamlEditorValue,
...@@ -52,15 +53,15 @@ const mockApolloResponses = (shouldReject) => { ...@@ -52,15 +53,15 @@ const mockApolloResponses = (shouldReject) => {
}; };
}; };
describe('savePolicy', () => { describe('modifyPolicy', () => {
it('returns the policy project and merge request on success when a policy project does not exist', async () => { it('returns the policy project and merge request on success when a policy project does not exist', async () => {
gqClient.mutate.mockImplementation(mockApolloResponses()); gqClient.mutate.mockImplementation(mockApolloResponses());
const { currentAssignedPolicyProject, mergeRequest } = await savePolicy( const { mergeRequest, policyProject } = await modifyPolicy(
createSavePolicyInput(DEFAULT_ASSIGNED_POLICY_PROJECT), createSavePolicyInput(DEFAULT_ASSIGNED_POLICY_PROJECT),
); );
expect(currentAssignedPolicyProject).toStrictEqual({ expect(policyProject).toStrictEqual({
id: newAssignedPolicyProject.id, id: newAssignedPolicyProject.id,
fullPath: newAssignedPolicyProject.fullPath, fullPath: newAssignedPolicyProject.fullPath,
branch: newAssignedPolicyProject.branch.rootRef, branch: newAssignedPolicyProject.branch.rootRef,
...@@ -72,17 +73,15 @@ describe('savePolicy', () => { ...@@ -72,17 +73,15 @@ describe('savePolicy', () => {
it('returns the policy project and merge request on success when a policy project does exist', async () => { it('returns the policy project and merge request on success when a policy project does exist', async () => {
gqClient.mutate.mockImplementation(mockApolloResponses()); gqClient.mutate.mockImplementation(mockApolloResponses());
const { currentAssignedPolicyProject, mergeRequest } = await savePolicy( const { mergeRequest, policyProject } = await modifyPolicy(createSavePolicyInput());
createSavePolicyInput(),
);
expect(currentAssignedPolicyProject).toStrictEqual(defaultAssignedPolicyProject);
expect(mergeRequest).toStrictEqual({ id: '01', errors: [] }); expect(mergeRequest).toStrictEqual({ id: '01', errors: [] });
expect(policyProject).toStrictEqual(defaultAssignedPolicyProject);
}); });
it('throws when an error is detected', async () => { it('throws when an error is detected', async () => {
gqClient.mutate.mockImplementation(mockApolloResponses(true)); gqClient.mutate.mockImplementation(mockApolloResponses(true));
await expect(savePolicy(createSavePolicyInput())).rejects.toThrowError(error); await expect(modifyPolicy(createSavePolicyInput())).rejects.toThrowError(error);
}); });
}); });
...@@ -2,8 +2,9 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,8 +2,9 @@ import { shallowMount } from '@vue/test-utils';
import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue'; import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue';
import { import {
DEFAULT_SCAN_EXECUTION_POLICY, DEFAULT_SCAN_EXECUTION_POLICY,
savePolicy, modifyPolicy,
} 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 { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
...@@ -20,9 +21,9 @@ jest.mock('ee/threat_monitoring/components/policy_editor/scan_execution_policy/l ...@@ -20,9 +21,9 @@ 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,
savePolicy: jest.fn().mockResolvedValue({ modifyPolicy: jest.fn().mockResolvedValue({
currentAssignedPolicyProject: { fullPath: 'tests' },
mergeRequest: { id: '2' }, mergeRequest: { id: '2' },
policyProject: { fullPath: 'tests' },
}), }),
})); }));
...@@ -46,15 +47,13 @@ describe('ScanExecutionPolicyEditor', () => { ...@@ -46,15 +47,13 @@ describe('ScanExecutionPolicyEditor', () => {
const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout); const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout);
beforeEach(() => {
factory();
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
it('updates the policy yaml when "update-yaml" is emitted', async () => { it('updates the policy yaml when "update-yaml" is emitted', async () => {
factory();
await wrapper.vm.$nextTick();
const newManifest = 'new yaml!'; const newManifest = 'new yaml!';
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe( expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(
DEFAULT_SCAN_EXECUTION_POLICY, DEFAULT_SCAN_EXECUTION_POLICY,
...@@ -63,17 +62,28 @@ describe('ScanExecutionPolicyEditor', () => { ...@@ -63,17 +62,28 @@ describe('ScanExecutionPolicyEditor', () => {
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest); expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest);
}); });
it('saves the policy when "savePolicy" is emitted', async () => { it.each`
findPolicyEditorLayout().vm.$emit('save-policy'); status | action | event | factoryFn
await wrapper.vm.$nextTick(); ${'to save a new policy'} | ${SECURITY_POLICY_ACTIONS.APPEND} | ${'save-policy'} | ${factory}
expect(savePolicy).toHaveBeenCalledTimes(1); ${'to update an existing policy'} | ${SECURITY_POLICY_ACTIONS.REPLACE} | ${'save-policy'} | ${() => factory({ propsData: { existingPolicy: { manifest: DEFAULT_SCAN_EXECUTION_POLICY } } })}
expect(savePolicy).toHaveBeenCalledWith({ ${'to delete an existing policy'} | ${SECURITY_POLICY_ACTIONS.REMOVE} | ${'remove-policy'} | ${() => factory({ propsData: { existingPolicy: { manifest: DEFAULT_SCAN_EXECUTION_POLICY } } })}
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT, `(
projectPath: defaultProjectPath, 'navigates to the new merge request when "modifyPolicy" is emitted $status',
yamlEditorValue: DEFAULT_SCAN_EXECUTION_POLICY, async ({ action, event, factoryFn }) => {
}); factoryFn();
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
expect(visitUrl).toHaveBeenCalled(); findPolicyEditorLayout().vm.$emit(event);
expect(visitUrl).toHaveBeenCalledWith('/tests/-/merge_requests/2'); await wrapper.vm.$nextTick();
}); expect(modifyPolicy).toHaveBeenCalledTimes(1);
expect(modifyPolicy).toHaveBeenCalledWith({
action,
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
projectPath: defaultProjectPath,
yamlEditorValue: DEFAULT_SCAN_EXECUTION_POLICY,
});
await wrapper.vm.$nextTick();
expect(visitUrl).toHaveBeenCalled();
expect(visitUrl).toHaveBeenCalledWith('/tests/-/merge_requests/2');
},
);
}); });
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