Commit 3c037436 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch '287827-create-compliance-framework-form' into 'master'

Create the creation form for compliance frameworks

See merge request gitlab-org/gitlab!52476
parents 7816858d 6568f474
<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 = {
color: '#1aaa55',
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 ""
msgid "ComplianceFrameworks|There are no compliance frameworks set up yet"
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})"
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