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
export const GRAPHQL_ERROR_MESSAGE = s__(
'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
import createScanExecutionPolicy from 'ee/threat_monitoring/graphql/mutations/create_scan_execution_policy.mutation.graphql';
import { gqClient } from 'ee/threat_monitoring/utils';
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
......@@ -67,7 +67,11 @@ const createMergeRequest = async ({ projectPath, sourceBranch, targetBranch }) =
* @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
*/
const updatePolicy = async ({ projectPath, yamlEditorValue }) => {
const updatePolicy = async ({
action = SECURITY_POLICY_ACTIONS.APPEND,
projectPath,
yamlEditorValue,
}) => {
const {
data: {
scanExecutionPolicyCommit: { branch, errors },
......@@ -75,6 +79,7 @@ const updatePolicy = async ({ projectPath, yamlEditorValue }) => {
} = await gqClient.mutate({
mutation: createScanExecutionPolicy,
variables: {
mode: action,
projectPath,
policyYaml: 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
* @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;
if (!currentAssignedPolicyProject.fullPath) {
......@@ -98,6 +108,7 @@ export const savePolicy = async ({ assignedPolicyProject, projectPath, yamlEdito
checkForErrors(currentAssignedPolicyProject);
const newPolicyCommitBranch = await updatePolicy({
action,
projectPath,
yamlEditorValue,
});
......@@ -112,5 +123,5 @@ export const savePolicy = async ({ assignedPolicyProject, projectPath, yamlEdito
checkForErrors(mergeRequest);
return { currentAssignedPolicyProject, mergeRequest };
return { mergeRequest, policyProject: currentAssignedPolicyProject };
};
......@@ -4,9 +4,16 @@ import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { EDITOR_MODES, EDITOR_MODE_YAML } from '../constants';
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 {
SECURITY_POLICY_ACTIONS,
DEFAULT_EDITOR_MODE: EDITOR_MODE_YAML,
EDITOR_MODES: [EDITOR_MODES[1]],
i18n: {
......@@ -35,6 +42,7 @@ export default {
return {
error: '',
isCreatingMR: false,
isRemovingPolicy: false,
policy: fromYaml(yamlEditorValue),
yamlEditorValue,
};
......@@ -45,33 +53,53 @@ export default {
},
},
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.isCreatingMR = true;
this.setLoadingFlag(action, true);
try {
const { currentAssignedPolicyProject, mergeRequest } = await savePolicy({
const { mergeRequest, policyProject } = await modifyPolicy({
action,
assignedPolicyProject: this.assignedPolicyProject,
projectPath: this.projectPath,
yamlEditorValue: this.yamlEditorValue,
});
this.redirectToMergeRequest({ mergeRequest, policyProject });
} catch (e) {
this.handleError(e);
this.setLoadingFlag(action, 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 || '/',
currentAssignedPolicyProject.fullPath,
policyProject.fullPath,
'/-/merge_requests',
mergeRequest.id,
),
);
} catch (e) {
if (e.message.toLowerCase().includes('graphql')) {
this.$emit('error', GRAPHQL_ERROR_MESSAGE);
} else {
this.$emit('error', e.message);
}
this.isCreatingMR = false;
}
},
updateYaml(manifest) {
this.yamlEditorValue = manifest;
......@@ -86,10 +114,12 @@ export default {
:disable-update="disableScanExecutionUpdate"
:editor-modes="$options.EDITOR_MODES"
:is-editing="isEditing"
:is-removing-policy="isRemovingPolicy"
:is-updating-policy="isCreatingMR"
:policy-name="policy.name"
:yaml-editor-value="yamlEditorValue"
@save-policy="handleSavePolicy"
@remove-policy="handleModifyPolicy($options.SECURITY_POLICY_ACTIONS.REMOVE)"
@save-policy="handleModifyPolicy()"
@update-yaml="updateYaml"
>
<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 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';
......@@ -15,7 +15,8 @@ const newAssignedPolicyProject = {
};
const projectPath = 'path/to/current-project';
const yamlEditorValue = 'some yaml';
const createSavePolicyInput = (assignedPolicyProject = defaultAssignedPolicyProject) => ({
const createSavePolicyInput = (assignedPolicyProject = defaultAssignedPolicyProject, action) => ({
action,
assignedPolicyProject,
projectPath,
yamlEditorValue,
......@@ -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 () => {
gqClient.mutate.mockImplementation(mockApolloResponses());
const { currentAssignedPolicyProject, mergeRequest } = await savePolicy(
const { mergeRequest, policyProject } = await modifyPolicy(
createSavePolicyInput(DEFAULT_ASSIGNED_POLICY_PROJECT),
);
expect(currentAssignedPolicyProject).toStrictEqual({
expect(policyProject).toStrictEqual({
id: newAssignedPolicyProject.id,
fullPath: newAssignedPolicyProject.fullPath,
branch: newAssignedPolicyProject.branch.rootRef,
......@@ -72,17 +73,15 @@ describe('savePolicy', () => {
it('returns the policy project and merge request on success when a policy project does exist', async () => {
gqClient.mutate.mockImplementation(mockApolloResponses());
const { currentAssignedPolicyProject, mergeRequest } = await savePolicy(
createSavePolicyInput(),
);
const { mergeRequest, policyProject } = await modifyPolicy(createSavePolicyInput());
expect(currentAssignedPolicyProject).toStrictEqual(defaultAssignedPolicyProject);
expect(mergeRequest).toStrictEqual({ id: '01', errors: [] });
expect(policyProject).toStrictEqual(defaultAssignedPolicyProject);
});
it('throws when an error is detected', async () => {
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';
import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue';
import {
DEFAULT_SCAN_EXECUTION_POLICY,
savePolicy,
modifyPolicy,
} 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 { DEFAULT_ASSIGNED_POLICY_PROJECT } from 'ee/threat_monitoring/constants';
import { visitUrl } from '~/lib/utils/url_utility';
......@@ -20,9 +21,9 @@ jest.mock('ee/threat_monitoring/components/policy_editor/scan_execution_policy/l
fromYaml: jest.requireActual(
'ee/threat_monitoring/components/policy_editor/scan_execution_policy/lib',
).fromYaml,
savePolicy: jest.fn().mockResolvedValue({
currentAssignedPolicyProject: { fullPath: 'tests' },
modifyPolicy: jest.fn().mockResolvedValue({
mergeRequest: { id: '2' },
policyProject: { fullPath: 'tests' },
}),
}));
......@@ -46,15 +47,13 @@ describe('ScanExecutionPolicyEditor', () => {
const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout);
beforeEach(() => {
factory();
});
afterEach(() => {
wrapper.destroy();
});
it('updates the policy yaml when "update-yaml" is emitted', async () => {
factory();
await wrapper.vm.$nextTick();
const newManifest = 'new yaml!';
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(
DEFAULT_SCAN_EXECUTION_POLICY,
......@@ -63,11 +62,21 @@ describe('ScanExecutionPolicyEditor', () => {
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest);
});
it('saves the policy when "savePolicy" is emitted', async () => {
findPolicyEditorLayout().vm.$emit('save-policy');
it.each`
status | action | event | factoryFn
${'to save a new policy'} | ${SECURITY_POLICY_ACTIONS.APPEND} | ${'save-policy'} | ${factory}
${'to update an existing policy'} | ${SECURITY_POLICY_ACTIONS.REPLACE} | ${'save-policy'} | ${() => factory({ propsData: { existingPolicy: { manifest: DEFAULT_SCAN_EXECUTION_POLICY } } })}
${'to delete an existing policy'} | ${SECURITY_POLICY_ACTIONS.REMOVE} | ${'remove-policy'} | ${() => factory({ propsData: { existingPolicy: { manifest: DEFAULT_SCAN_EXECUTION_POLICY } } })}
`(
'navigates to the new merge request when "modifyPolicy" is emitted $status',
async ({ action, event, factoryFn }) => {
factoryFn();
await wrapper.vm.$nextTick();
expect(savePolicy).toHaveBeenCalledTimes(1);
expect(savePolicy).toHaveBeenCalledWith({
findPolicyEditorLayout().vm.$emit(event);
await wrapper.vm.$nextTick();
expect(modifyPolicy).toHaveBeenCalledTimes(1);
expect(modifyPolicy).toHaveBeenCalledWith({
action,
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
projectPath: defaultProjectPath,
yamlEditorValue: DEFAULT_SCAN_EXECUTION_POLICY,
......@@ -75,5 +84,6 @@ describe('ScanExecutionPolicyEditor', () => {
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