Commit 6568f474 authored by Robert Hunt's avatar Robert Hunt

Created the creation form for compliance frameworks

This form contains all the processes which are necessary to create
a compliance framework. This form makes use of the shared form to show
the necessary fields

This form saves the compliance framework via GraphQL. On success it
redirects to the general settings page. On failure, it shows an error
alert
parent ed29856f
<script>
import { visitUrl } from '~/lib/utils/url_utility';
import { s__ } from '~/locale';
import * as Sentry from '~/sentry/wrapper';
import SharedForm from './shared_form.vue';
import createComplianceFrameworkMutation from '../graphql/queries/create_compliance_framework.mutation.graphql';
export default {
components: {
SharedForm,
},
props: {
groupEditPath: {
type: String,
required: true,
},
groupPath: {
type: String,
required: true,
},
},
data() {
return {
errorMessage: '',
};
},
computed: {
isLoading() {
return this.$apollo.loading;
},
},
methods: {
setError(error, userFriendlyText) {
this.errorMessage = userFriendlyText;
Sentry.captureException(error);
},
async onSubmit(formData) {
try {
const { data } = await this.$apollo.mutate({
mutation: createComplianceFrameworkMutation,
variables: {
input: {
namespacePath: this.groupPath,
params: {
name: formData.name,
description: formData.description,
color: formData.color,
},
},
},
});
const [error] = data?.createComplianceFramework?.errors || [];
if (error) {
this.setError(new Error(error), error);
} else {
visitUrl(this.groupEditPath);
}
} catch (e) {
this.setError(e, this.$options.i18n.saveError);
}
},
},
i18n: {
saveError: s__(
'ComplianceFrameworks|Unable to save this compliance framework. Please try again',
),
},
};
</script>
<template>
<shared-form
:group-edit-path="groupEditPath"
:loading="isLoading"
:render-form="!isLoading"
:error="errorMessage"
@submit="onSubmit"
/>
</template>
mutation createComplianceFramework($input: CreateComplianceFrameworkInput!) {
createComplianceFramework(input: $input) {
framework {
id
name
description
color
}
errors
}
}
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import CreateForm from './components/create_form.vue';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient({}, { assumeImmutableResults: true }),
});
const createComplianceFrameworksFormApp = (el) => {
if (!el) {
return false;
}
const { groupEditPath, groupPath } = el.dataset;
return new Vue({
el,
apolloProvider,
render(createElement) {
const element = CreateForm;
const props = { groupEditPath, groupPath };
return createElement(element, {
props,
});
},
});
};
export { createComplianceFrameworksFormApp };
import VueApollo from 'vue-apollo';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import createComplianceFrameworkMutation from 'ee/groups/settings/compliance_frameworks/graphql/queries/create_compliance_framework.mutation.graphql';
import CreateForm from 'ee/groups/settings/compliance_frameworks/components/create_form.vue';
import SharedForm from 'ee/groups/settings/compliance_frameworks/components/shared_form.vue';
import { visitUrl } from '~/lib/utils/url_utility';
import { validCreateResponse, errorCreateResponse } from '../mock_data';
import * as Sentry from '~/sentry/wrapper';
const localVue = createLocalVue();
localVue.use(VueApollo);
jest.mock('~/lib/utils/url_utility');
describe('Form', () => {
let wrapper;
const sentryError = new Error('Network error');
const sentrySaveError = new Error('Invalid values given');
const propsData = {
groupPath: 'group-1',
groupEditPath: 'group-1/edit',
scopedLabelsHelpPath: 'help/scoped-labels',
};
const create = jest.fn().mockResolvedValue(validCreateResponse);
const createWithNetworkErrors = jest.fn().mockRejectedValue(sentryError);
const createWithErrors = jest.fn().mockResolvedValue(errorCreateResponse);
const findForm = () => wrapper.findComponent(SharedForm);
function createMockApolloProvider(requestHandlers) {
localVue.use(VueApollo);
return createMockApollo(requestHandlers);
}
function createComponent(requestHandlers = []) {
return shallowMount(CreateForm, {
localVue,
apolloProvider: createMockApolloProvider(requestHandlers),
propsData,
});
}
afterEach(() => {
wrapper.destroy();
});
describe('loading', () => {
beforeEach(() => {
wrapper = createComponent();
});
it('passes the loading state to the form', () => {
expect(findForm().props('loading')).toBe(false);
expect(findForm().props('renderForm')).toBe(true);
});
});
describe('onSubmit', () => {
const name = 'Test';
const description = 'Test description';
const color = '#000000';
const creationProps = {
input: {
namespacePath: 'group-1',
params: {
name,
description,
color,
},
},
};
it('passes the error to the form when saving causes an exception and does not redirect', async () => {
jest.spyOn(Sentry, 'captureException');
wrapper = createComponent([[createComplianceFrameworkMutation, createWithNetworkErrors]]);
await waitForPromises();
findForm().vm.$emit('submit', { name, description, color });
await waitForPromises();
expect(createWithNetworkErrors).toHaveBeenCalledWith(creationProps);
expect(findForm().props('loading')).toBe(false);
expect(findForm().props('renderForm')).toBe(true);
expect(visitUrl).not.toHaveBeenCalled();
expect(findForm().props('error')).toBe(
'Unable to save this compliance framework. Please try again',
);
expect(Sentry.captureException.mock.calls[0][0].networkError).toStrictEqual(sentryError);
});
it('passes the errors to the form when saving fails and does not redirect', async () => {
jest.spyOn(Sentry, 'captureException');
wrapper = createComponent([[createComplianceFrameworkMutation, createWithErrors]]);
await waitForPromises();
findForm().vm.$emit('submit', { name, description, color });
await waitForPromises();
expect(createWithErrors).toHaveBeenCalledWith(creationProps);
expect(findForm().props('loading')).toBe(false);
expect(findForm().props('renderForm')).toBe(true);
expect(visitUrl).not.toHaveBeenCalled();
expect(findForm().props('error')).toBe('Invalid values given');
expect(Sentry.captureException.mock.calls[0][0]).toStrictEqual(sentrySaveError);
});
it('saves inputted values and redirects', async () => {
wrapper = createComponent([[createComplianceFrameworkMutation, create]]);
await waitForPromises();
findForm().vm.$emit('submit', { name, description, color });
await waitForPromises();
expect(create).toHaveBeenCalledWith(creationProps);
expect(findForm().props('loading')).toBe(false);
expect(findForm().props('renderForm')).toBe(true);
expect(visitUrl).toHaveBeenCalledWith(propsData.groupEditPath);
});
});
});
...@@ -48,3 +48,29 @@ export const frameworkFoundResponse = { ...@@ -48,3 +48,29 @@ export const frameworkFoundResponse = {
color: '#1aaa55', color: '#1aaa55',
parsedId: 1, parsedId: 1,
}; };
export const validCreateResponse = {
data: {
createComplianceFramework: {
framework: {
id: 'gid://gitlab/ComplianceManagement::Framework/1',
name: 'GDPR',
description: 'General Data Protection Regulation',
color: '#1aaa55',
__typename: 'ComplianceFramework',
},
errors: [],
__typename: 'CreateComplianceFrameworkPayload',
},
},
};
export const errorCreateResponse = {
data: {
createComplianceFramework: {
framework: null,
errors: ['Invalid values given'],
__typename: 'CreateComplianceFrameworkPayload',
},
},
};
...@@ -7335,6 +7335,9 @@ msgstr "" ...@@ -7335,6 +7335,9 @@ msgstr ""
msgid "ComplianceFrameworks|There are no compliance frameworks set up yet" msgid "ComplianceFrameworks|There are no compliance frameworks set up yet"
msgstr "" msgstr ""
msgid "ComplianceFrameworks|Unable to save this compliance framework. Please try again"
msgstr ""
msgid "ComplianceFrameworks|Use %{codeStart}::%{codeEnd} to create a %{linkStart}scoped set%{linkEnd} (eg. %{codeStart}SOX::AWS%{codeEnd})" msgid "ComplianceFrameworks|Use %{codeStart}::%{codeEnd} to create a %{linkStart}scoped set%{linkEnd} (eg. %{codeStart}SOX::AWS%{codeEnd})"
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