Commit 78e72982 authored by Robert Hunt's avatar Robert Hunt

Add pipeline configuration location to compliance framework forms

- Created new pipeline input field
- Added validation to check the value matches our required format
- Added validation to check the file exists using getRawFile API
- Added pipeline configuration location to Get query and mutation
parent 0f81dd98
......@@ -21,6 +21,11 @@ export default {
type: String,
required: true,
},
pipelineConfigurationFullPathEnabled: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
......@@ -44,7 +49,7 @@ export default {
this.errorMessage = '';
try {
const { name, description, color } = this.formData;
const { name, description, pipelineConfigurationFullPath, color } = this.formData;
const { data } = await this.$apollo.mutate({
mutation: createComplianceFrameworkMutation,
variables: {
......@@ -53,6 +58,7 @@ export default {
params: {
name,
description,
pipelineConfigurationFullPath,
color,
},
},
......@@ -80,8 +86,10 @@ export default {
<form-status :loading="isLoading" :error="errorMessage">
<shared-form
:group-edit-path="groupEditPath"
:pipeline-configuration-full-path-enabled="pipelineConfigurationFullPathEnabled"
:name.sync="formData.name"
:description.sync="formData.description"
:pipeline-configuration-full-path.sync="formData.pipelineConfigurationFullPath"
:color.sync="formData.color"
@submit="onSubmit"
/>
......
......@@ -33,6 +33,11 @@ export default {
required: false,
default: null,
},
pipelineConfigurationFullPathEnabled: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
......@@ -85,11 +90,12 @@ export default {
return initialiseFormData();
}
const { name, description, color } = complianceFrameworks[0];
const { name, description, pipelineConfigurationFullPath, color } = complianceFrameworks[0];
return {
name,
description,
pipelineConfigurationFullPath,
color,
};
},
......@@ -106,7 +112,7 @@ export default {
this.saveErrorMessage = '';
try {
const { name, description, color } = this.formData;
const { name, description, pipelineConfigurationFullPath, color } = this.formData;
const { data } = await this.$apollo.mutate({
mutation: updateComplianceFrameworkMutation,
variables: {
......@@ -115,6 +121,7 @@ export default {
params: {
name,
description,
pipelineConfigurationFullPath,
color,
},
},
......@@ -143,8 +150,10 @@ export default {
<shared-form
v-if="showForm"
:group-edit-path="groupEditPath"
:pipeline-configuration-full-path-enabled="pipelineConfigurationFullPathEnabled"
:name.sync="formData.name"
:description.sync="formData.description"
:pipeline-configuration-full-path.sync="formData.pipelineConfigurationFullPath"
:color.sync="formData.color"
@submit="onSubmit"
/>
......
......@@ -5,6 +5,7 @@ import { helpPagePath } from '~/helpers/help_page_helper';
import { validateHexColor } from '~/lib/utils/color_utils';
import { __, s__ } from '~/locale';
import ColorPicker from '~/vue_shared/components/color_picker/color_picker.vue';
import { fetchPipelineConfigurationFileExists, validatePipelineConfirmationFormat } from '../utils';
export default {
components: {
......@@ -36,6 +37,21 @@ export default {
required: false,
default: null,
},
pipelineConfigurationFullPathEnabled: {
type: Boolean,
required: false,
default: false,
},
pipelineConfigurationFullPath: {
type: String,
required: false,
default: null,
},
},
data() {
return {
pipelineConfigurationFileExists: true,
};
},
computed: {
isValidColor() {
......@@ -55,18 +71,50 @@ export default {
return Boolean(this.description);
},
isValidPipelineConfiguration() {
if (!this.pipelineConfigurationFullPath) {
return null;
}
return this.isValidPipelineConfigurationFormat && this.pipelineConfigurationFileExists;
},
isValidPipelineConfigurationFormat() {
return validatePipelineConfirmationFormat(this.pipelineConfigurationFullPath);
},
disableSubmitBtn() {
return !this.isValidName || !this.isValidDescription || !this.isValidColor;
return (
!this.isValidName ||
!this.isValidDescription ||
!this.isValidColor ||
this.isValidPipelineConfiguration === false
);
},
pipelineConfigurationFeedbackMessage() {
if (!this.isValidPipelineConfigurationFormat) {
return this.$options.i18n.pipelineConfigurationInputInvalidFormat;
}
return this.$options.i18n.pipelineConfigurationInputUnknownFile;
},
scopedLabelsHelpPath() {
return helpPagePath('user/project/labels.md', { anchor: 'scoped-labels' });
},
},
async created() {
if (this.pipelineConfigurationFullPath) {
this.pipelineConfigurationFileExists = await fetchPipelineConfigurationFileExists(
this.pipelineConfigurationFullPath,
);
}
},
methods: {
onSubmit() {
const { name, description, color } = this;
this.$emit('submit');
},
async updatePipelineConfiguration(path) {
this.pipelineConfigurationFileExists = await fetchPipelineConfigurationFileExists(path);
this.$emit('submit', { name, description, color });
this.$emit('update:pipelineConfigurationFullPath', path);
},
},
i18n: {
......@@ -77,6 +125,21 @@ export default {
titleInputInvalid: __('A title is required'),
descriptionInputLabel: __('Description'),
descriptionInputInvalid: __('A description is required'),
pipelineConfigurationInputLabel: s__(
'ComplianceFrameworks|Compliance pipeline configuration location (optional)',
),
pipelineConfigurationInputSubLabel: s__(
'ComplianceFrameworks|Combines with the CI configuration at runtime.',
),
pipelineConfigurationInputDescription: s__(
'ComplianceFrameworks|e.g. include-gitlab.ci.yml@group-name/project-name',
),
pipelineConfigurationInputInvalidFormat: s__(
'ComplianceFrameworks|Invalid format: it should follow the format [PATH].y(a)ml@[GROUP]/[PROJECT]',
),
pipelineConfigurationInputUnknownFile: s__(
'ComplianceFrameworks|Could not find this configuration location, please try a different location',
),
colorInputLabel: __('Background color'),
submitBtnText: __('Save changes'),
cancelBtnText: __('Cancel'),
......@@ -125,6 +188,25 @@ export default {
/>
</gl-form-group>
<gl-form-group
v-if="pipelineConfigurationFullPathEnabled"
:label="$options.i18n.pipelineConfigurationInputLabel"
:description="$options.i18n.pipelineConfigurationInputDescription"
:invalid-feedback="pipelineConfigurationFeedbackMessage"
:state="isValidPipelineConfiguration"
data-testid="pipeline-configuration-input-group"
>
<p class="col-form-label gl-font-weight-normal!">
{{ $options.i18n.pipelineConfigurationInputSubLabel }}
</p>
<gl-form-input
:value="pipelineConfigurationFullPath"
:state="isValidPipelineConfiguration"
data-testid="pipeline-configuration-input"
@input="updatePipelineConfiguration"
/>
</gl-form-group>
<color-picker
:value="color"
:label="$options.i18n.colorInputLabel"
......
......@@ -7,3 +7,6 @@ export const FETCH_ERROR = s__(
export const SAVE_ERROR = s__(
'ComplianceFrameworks|Unable to save this compliance framework. Please try again',
);
// Check that it matches the format [FILE].y(a)ml@[GROUP]/[PROJECT]
export const PIPELINE_CONFIGURATION_PATH_FORMAT = /^([^@]*\.ya?ml)@([^/]*)\/(.*)$/;
......@@ -11,6 +11,7 @@ query getComplianceFramework(
name
description
color
pipelineConfigurationFullPath
}
}
}
......
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import CreateForm from './components/create_form.vue';
import EditForm from './components/edit_form.vue';
......@@ -16,14 +17,24 @@ const createComplianceFrameworksFormApp = (el) => {
return false;
}
const { groupEditPath, groupPath, graphqlFieldName = null, frameworkId: id = null } = el.dataset;
const {
groupEditPath,
groupPath,
pipelineConfigurationFullPathEnabled,
graphqlFieldName = null,
frameworkId: id = null,
} = el.dataset;
return new Vue({
el,
apolloProvider,
render(createElement) {
let element = CreateForm;
let props = { groupEditPath, groupPath };
let props = {
groupEditPath,
groupPath,
pipelineConfigurationFullPathEnabled: parseBoolean(pipelineConfigurationFullPathEnabled),
};
if (id) {
element = EditForm;
......
import httpStatus from '~/lib/utils/http_status';
import Api from '~/api';
import { PIPELINE_CONFIGURATION_PATH_FORMAT } from './constants';
export const initialiseFormData = () => ({
name: null,
description: null,
pipelineConfigurationFullPath: null,
color: null,
});
export const getPipelineConfigurationPathParts = (path) => {
const [, file, group, project] = path.match(PIPELINE_CONFIGURATION_PATH_FORMAT) || [];
return { file, group, project };
};
export const validatePipelineConfirmationFormat = (path) =>
PIPELINE_CONFIGURATION_PATH_FORMAT.test(path);
export const fetchPipelineConfigurationFileExists = async (path) => {
const { file, group, project } = getPipelineConfigurationPathParts(path);
if (!file || !group || !project) {
return false;
}
try {
const { status } = await Api.getRawFile(`${group}/${project}`, file);
return status === httpStatus.OK;
} catch (e) {
return false;
}
};
......@@ -24,6 +24,7 @@ describe('CreateForm', () => {
const propsData = {
groupPath: 'group-1',
groupEditPath: 'group-1/edit',
pipelineConfigurationFullPathEnabled: true,
};
const sentryError = new Error('Network error');
......@@ -50,11 +51,12 @@ describe('CreateForm', () => {
});
}
async function submitForm(name, description, color) {
async function submitForm(name, description, pipelineConfiguration, color) {
await waitForPromises();
findForm().vm.$emit('update:name', name);
findForm().vm.$emit('update:description', description);
findForm().vm.$emit('update:pipelineConfigurationFullPath', pipelineConfiguration);
findForm().vm.$emit('update:color', color);
findForm().vm.$emit('submit');
......@@ -78,6 +80,7 @@ describe('CreateForm', () => {
describe('onSubmit', () => {
const name = 'Test';
const description = 'Test description';
const pipelineConfigurationFullPath = 'file.yml@group/project';
const color = '#000000';
const creationProps = {
input: {
......@@ -85,6 +88,7 @@ describe('CreateForm', () => {
params: {
name,
description,
pipelineConfigurationFullPath,
color,
},
},
......@@ -94,7 +98,7 @@ describe('CreateForm', () => {
jest.spyOn(Sentry, 'captureException');
wrapper = createComponent([[createComplianceFrameworkMutation, createWithNetworkErrors]]);
await submitForm(name, description, color);
await submitForm(name, description, pipelineConfigurationFullPath, color);
expect(createWithNetworkErrors).toHaveBeenCalledWith(creationProps);
expect(findFormStatus().props('loading')).toBe(false);
......@@ -107,7 +111,7 @@ describe('CreateForm', () => {
jest.spyOn(Sentry, 'captureException');
wrapper = createComponent([[createComplianceFrameworkMutation, createWithErrors]]);
await submitForm(name, description, color);
await submitForm(name, description, pipelineConfigurationFullPath, color);
expect(createWithErrors).toHaveBeenCalledWith(creationProps);
expect(findFormStatus().props('loading')).toBe(false);
......@@ -119,7 +123,7 @@ describe('CreateForm', () => {
it('saves inputted values and redirects', async () => {
wrapper = createComponent([[createComplianceFrameworkMutation, create]]);
await submitForm(name, description, color);
await submitForm(name, description, pipelineConfigurationFullPath, color);
expect(create).toHaveBeenCalledWith(creationProps);
expect(findFormStatus().props('loading')).toBe(false);
......
......@@ -32,6 +32,7 @@ describe('EditForm', () => {
groupEditPath: 'group-1/edit',
groupPath: 'group-1',
id: '1',
pipelineConfigurationFullPathEnabled: true,
};
const sentryError = new Error('Network error');
......@@ -63,11 +64,12 @@ describe('EditForm', () => {
});
}
async function submitForm(name, description, color) {
async function submitForm(name, description, pipelineConfiguration, color) {
await waitForPromises();
findForm().vm.$emit('update:name', name);
findForm().vm.$emit('update:description', description);
findForm().vm.$emit('update:pipelineConfigurationFullPath', pipelineConfiguration);
findForm().vm.$emit('update:color', color);
findForm().vm.$emit('submit');
......@@ -96,10 +98,12 @@ describe('EditForm', () => {
expect(fetchOne).toHaveBeenCalledTimes(1);
expect(findForm().props()).toStrictEqual({
name: frameworkFoundResponse.name,
description: frameworkFoundResponse.description,
color: frameworkFoundResponse.color,
description: frameworkFoundResponse.description,
groupEditPath: propsData.groupEditPath,
name: frameworkFoundResponse.name,
pipelineConfigurationFullPath: frameworkFoundResponse.pipelineConfigurationFullPath,
pipelineConfigurationFullPathEnabled: true,
});
expect(findForm().exists()).toBe(true);
});
......@@ -133,6 +137,7 @@ describe('EditForm', () => {
describe('onSubmit', () => {
const name = 'Test';
const description = 'Test description';
const pipelineConfigurationFullPath = 'file.yml@group/project';
const color = '#000000';
const updateProps = {
input: {
......@@ -140,6 +145,7 @@ describe('EditForm', () => {
params: {
name,
description,
pipelineConfigurationFullPath,
color,
},
},
......@@ -152,7 +158,7 @@ describe('EditForm', () => {
[updateComplianceFrameworkMutation, updateWithNetworkErrors],
]);
await submitForm(name, description, color);
await submitForm(name, description, pipelineConfigurationFullPath, color);
expect(updateWithNetworkErrors).toHaveBeenCalledWith(updateProps);
expect(findFormStatus().props('loading')).toBe(false);
......@@ -168,7 +174,7 @@ describe('EditForm', () => {
[updateComplianceFrameworkMutation, updateWithErrors],
]);
await submitForm(name, description, color);
await submitForm(name, description, pipelineConfigurationFullPath, color);
expect(updateWithErrors).toHaveBeenCalledWith(updateProps);
expect(findFormStatus().props('loading')).toBe(false);
......@@ -183,7 +189,7 @@ describe('EditForm', () => {
[updateComplianceFrameworkMutation, update],
]);
await submitForm(name, description, color);
await submitForm(name, description, pipelineConfigurationFullPath, color);
expect(update).toHaveBeenCalledWith(updateProps);
expect(findFormStatus().props('loading')).toBe(false);
......
......@@ -3,11 +3,12 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import List from 'ee/groups/settings/compliance_frameworks/components/list.vue';
import EmptyState from 'ee/groups/settings/compliance_frameworks/components/list_empty_state.vue';
import ListItem from 'ee/groups/settings/compliance_frameworks/components/list_item.vue';
import getComplianceFrameworkQuery from 'ee/groups/settings/compliance_frameworks/graphql/queries/get_compliance_framework.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { PIPELINE_CONFIGURATION_PATH_FORMAT } from 'ee/groups/settings/compliance_frameworks/constants';
import EmptyState from 'ee/groups/settings/compliance_frameworks/components/list_empty_state.vue';
import * as Sentry from '~/sentry/wrapper';
import { validFetchResponse, emptyFetchResponse } from '../mock_data';
......@@ -160,6 +161,9 @@ describe('List', () => {
parsedId: expect.any(Number),
name: expect.any(String),
description: expect.any(String),
pipelineConfigurationFullPath: expect.stringMatching(
PIPELINE_CONFIGURATION_PATH_FORMAT,
),
color: expect.stringMatching(/^#([0-9A-F]{3}){1,2}$/i),
},
}),
......
import { GlForm, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SharedForm from 'ee/groups/settings/compliance_frameworks/components/shared_form.vue';
import { GlFormGroup } from 'jest/registry/shared/stubs';
import SharedForm from 'ee/groups/settings/compliance_frameworks/components/shared_form.vue';
import waitForPromises from 'helpers/wait_for_promises';
import ColorPicker from '~/vue_shared/components/color_picker/color_picker.vue';
import * as Utils from 'ee/groups/settings/compliance_frameworks/utils';
import { GlFormGroup, GlFormInput } from '../stubs';
import { frameworkFoundResponse, suggestedLabelColors } from '../mock_data';
describe('SharedForm', () => {
let wrapper;
const defaultPropsData = { groupEditPath: 'group-1' };
const defaultPropsData = { groupEditPath: 'group-1', pipelineConfigurationFullPathEnabled: true };
const findForm = () => wrapper.findComponent(GlForm);
const findNameGroup = () => wrapper.find('[data-testid="name-input-group"]');
const findNameInput = () => wrapper.find('[data-testid="name-input"]');
const findDescriptionGroup = () => wrapper.find('[data-testid="description-input-group"]');
const findDescriptionInput = () => wrapper.find('[data-testid="description-input"]');
const findPipelineConfigurationGroup = () =>
wrapper.find('[data-testid="pipeline-configuration-input-group"]');
const findPipelineConfigurationInput = () =>
wrapper.find('[data-testid="pipeline-configuration-input"]');
const findColorPicker = () => wrapper.findComponent(ColorPicker);
const findSubmitBtn = () => wrapper.find('[data-testid="submit-btn"]');
const findCancelBtn = () => wrapper.find('[data-testid="cancel-btn"]');
......@@ -28,15 +34,7 @@ describe('SharedForm', () => {
},
stubs: {
GlFormGroup,
GlFormInput: {
name: 'gl-form-input-stub',
props: ['state'],
template: `
<div>
<slot></slot>
</div>
`,
},
GlFormInput,
GlSprintf,
},
});
......@@ -47,9 +45,7 @@ describe('SharedForm', () => {
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
wrapper.destroy();
});
describe('Fields', () => {
......@@ -58,6 +54,7 @@ describe('SharedForm', () => {
expect(findNameInput()).toExist();
expect(findDescriptionInput()).toExist();
expect(findPipelineConfigurationInput()).toExist();
expect(findColorPicker()).toExist();
expect(findSubmitBtn()).toExist();
expect(findCancelBtn()).toExist();
......@@ -68,6 +65,15 @@ describe('SharedForm', () => {
expect(findNameGroup().text()).toContain('Use :: to create a scoped set (eg. SOX::AWS)');
});
it.each([true, false])(
'renders the pipeline configuration correctly when enabled is %s',
(enabled) => {
wrapper = createComponent({ pipelineConfigurationFullPathEnabled: enabled });
expect(findPipelineConfigurationGroup().exists()).toBe(enabled);
},
);
});
describe('Validation', () => {
......@@ -95,6 +101,47 @@ describe('SharedForm', () => {
expect(findDescriptionInput().props('state')).toBe(validity);
});
it.each`
pipelineConfigurationFullPath | message
${'foobar'} | ${'Invalid format: it should follow the format [PATH].y(a)ml@[GROUP]/[PROJECT]'}
${'foo.yml@bar/baz'} | ${'Could not find this configuration location, please try a different location'}
`(
'sets the correct invalid message to the group',
async ({ pipelineConfigurationFullPath, message }) => {
jest.spyOn(Utils, 'fetchPipelineConfigurationFileExists').mockReturnValue(false);
wrapper = createComponent({ pipelineConfigurationFullPath });
await waitForPromises();
expect(findPipelineConfigurationGroup().attributes('invalid-feedback')).toBe(message);
},
);
it.each`
pipelineConfigurationFullPath | validity
${null} | ${null}
${''} | ${null}
${'foobar'} | ${false}
${'foo.yml@bar/zab'} | ${false}
${'foo.yaml@bar/baz'} | ${true}
${'foo.yml@bar/baz'} | ${true}
`(
'sets the correct state for the input and group when pipeline configuration is $pipelineConfigurationFullPath',
async ({ pipelineConfigurationFullPath, validity }) => {
jest
.spyOn(Utils, 'fetchPipelineConfigurationFileExists')
.mockReturnValue(Boolean(validity));
wrapper = createComponent({ pipelineConfigurationFullPath });
await waitForPromises();
expect(findPipelineConfigurationGroup().props('state')).toBe(validity);
expect(findPipelineConfigurationInput().props('state')).toBe(validity);
},
);
it.each`
color | validity
${null} | ${null}
......@@ -110,17 +157,27 @@ describe('SharedForm', () => {
});
it.each`
name | description | color | disabled
${null} | ${null} | ${null} | ${'true'}
${''} | ${null} | ${null} | ${'true'}
${null} | ${''} | ${null} | ${'true'}
${null} | ${null} | ${''} | ${'true'}
${'Foo'} | ${null} | ${''} | ${'true'}
${'Foo'} | ${'Bar'} | ${'#000'} | ${undefined}
name | description | color | pipelineConfigurationFullPath | disabled
${null} | ${null} | ${null} | ${null} | ${'true'}
${'Foo'} | ${null} | ${null} | ${null} | ${'true'}
${null} | ${'Bar'} | ${null} | ${null} | ${'true'}
${null} | ${null} | ${'#000'} | ${null} | ${'true'}
${null} | ${null} | ${null} | ${'foo.yml@bar/zab'} | ${'true'}
${'Foo'} | ${''} | ${''} | ${''} | ${'true'}
${'Foo'} | ${'Bar'} | ${'#000'} | ${''} | ${undefined}
${'Foo'} | ${'Bar'} | ${'#000'} | ${'foo.yml@bar/baz'} | ${undefined}
`(
'should set the submit buttons disabled attribute to $disabled',
({ name, description, color, disabled }) => {
wrapper = createComponent({ name, description, color });
'should set the submit buttons disabled attribute to $disabled when name: $name, description: $description, color: $color, pipelineConfigurationFullPath: $pipelineConfigurationFullPath',
async ({ name, description, color, pipelineConfigurationFullPath, disabled }) => {
if (pipelineConfigurationFullPath?.includes('zab')) {
jest.spyOn(Utils, 'fetchPipelineConfigurationFileExists').mockReturnValue(false);
} else {
jest.spyOn(Utils, 'fetchPipelineConfigurationFileExists').mockReturnValue(true);
}
wrapper = createComponent({ name, description, color, pipelineConfigurationFullPath });
await waitForPromises();
expect(findSubmitBtn().attributes('disabled')).toBe(disabled);
},
......@@ -129,30 +186,33 @@ describe('SharedForm', () => {
describe('Updating data', () => {
it('updates the initial form data when the props are updated', async () => {
const { name, description, color } = frameworkFoundResponse;
const { name, description, pipelineConfigurationFullPath, color } = frameworkFoundResponse;
wrapper = createComponent();
expect(findNameInput().attributes('value')).toBe(undefined);
expect(findDescriptionInput().attributes('value')).toBe(undefined);
expect(findColorPicker().attributes('value')).toBe(undefined);
expect(findNameInput().props('value')).toBe(null);
expect(findDescriptionInput().props('value')).toBe(null);
expect(findPipelineConfigurationInput().props('value')).toBe(null);
expect(findColorPicker().props('value')).toBe(null);
await wrapper.setProps({ name, description, color });
await wrapper.setProps({ name, description, pipelineConfigurationFullPath, color });
expect(findNameInput().attributes('value')).toBe(name);
expect(findDescriptionInput().attributes('value')).toBe(description);
expect(findColorPicker().attributes('value')).toBe(color);
expect(findNameInput().props('value')).toBe(name);
expect(findDescriptionInput().props('value')).toBe(description);
expect(findPipelineConfigurationInput().props('value')).toBe(pipelineConfigurationFullPath);
expect(findColorPicker().props('value')).toBe(color);
});
});
describe('On form submission', () => {
it('emits a submit event', async () => {
const { name, description, color } = frameworkFoundResponse;
wrapper = createComponent({ name, description, color });
jest.spyOn(Utils, 'fetchPipelineConfigurationFileExists').mockReturnValue(true);
const { name, description, pipelineConfigurationFullPath, color } = frameworkFoundResponse;
wrapper = createComponent({ name, description, pipelineConfigurationFullPath, color });
await findForm().vm.$emit('submit', { preventDefault: () => {} });
expect(wrapper.emitted('submit')).toHaveLength(1);
expect(wrapper.emitted('submit')[0]).toEqual([{ name, description, color }]);
});
});
});
......@@ -11,6 +11,7 @@ describe('createComplianceFrameworksFormApp', () => {
const groupEditPath = 'group-1/edit';
const groupPath = 'group-1';
const pipelineConfigurationFullPathEnabled = true;
const graphqlFieldName = 'field';
const testId = '1';
......@@ -20,6 +21,7 @@ describe('createComplianceFrameworksFormApp', () => {
el = document.createElement('div');
el.setAttribute('data-group-edit-path', groupEditPath);
el.setAttribute('data-group-path', groupPath);
el.setAttribute('data-pipeline-configuration-full-path-enabled', 'true');
if (id) {
el.setAttribute('data-graphql-field-name', graphqlFieldName);
......@@ -51,6 +53,7 @@ describe('createComplianceFrameworksFormApp', () => {
expect(findFormApp(CreateForm).props()).toStrictEqual({
groupEditPath,
groupPath,
pipelineConfigurationFullPathEnabled,
});
});
});
......@@ -66,6 +69,7 @@ describe('createComplianceFrameworksFormApp', () => {
groupEditPath,
groupPath,
id: testId,
pipelineConfigurationFullPathEnabled,
});
});
});
......
......@@ -16,6 +16,7 @@ export const validFetchResponse = {
id: 'gid://gitlab/ComplianceManagement::Framework/1',
name: 'GDPR',
description: 'General Data Protection Regulation',
pipelineConfigurationFullPath: 'file.yml@group/project',
color: '#1aaa55',
__typename: 'ComplianceFramework',
},
......@@ -23,6 +24,7 @@ export const validFetchResponse = {
id: 'gid://gitlab/ComplianceManagement::Framework/2',
name: 'PCI-DSS',
description: 'Payment Card Industry-Data Security Standard',
pipelineConfigurationFullPath: 'file.yml@group/project',
color: '#6666c4',
__typename: 'ComplianceFramework',
},
......@@ -52,6 +54,7 @@ export const frameworkFoundResponse = {
id: 'gid://gitlab/ComplianceManagement::Framework/1',
name: 'GDPR',
description: 'General Data Protection Regulation',
pipelineConfigurationFullPath: 'file.yml@group/project',
color: '#1aaa55',
};
......@@ -66,6 +69,7 @@ export const validFetchOneResponse = {
id: 'gid://gitlab/ComplianceManagement::Framework/1',
name: 'GDPR',
description: 'General Data Protection Regulation',
pipelineConfigurationFullPath: 'file.yml@group/project',
color: '#1aaa55',
__typename: 'ComplianceFramework',
},
......@@ -84,6 +88,7 @@ export const validCreateResponse = {
id: 'gid://gitlab/ComplianceManagement::Framework/1',
name: 'GDPR',
description: 'General Data Protection Regulation',
pipelineConfigurationFullPath: 'file.yml@group/project',
color: '#1aaa55',
__typename: 'ComplianceFramework',
},
......
export const GlFormGroup = {
name: 'gl-form-group-stub',
props: ['state'],
template: `
<div>
<slot name="label"></slot>
<slot></slot>
<slot name="description"></slot>
</div>`,
};
export const GlFormInput = {
name: 'gl-form-input-stub',
props: ['state', 'disabled', 'value'],
template: `
<div>
<slot></slot>
</div>`,
};
import MockAdapter from 'axios-mock-adapter';
import httpStatus from '~/lib/utils/http_status';
import * as Utils from 'ee/groups/settings/compliance_frameworks/utils';
import axios from '~/lib/utils/axios_utils';
const GET_RAW_FILE_ENDPOINT = /\/api\/(.*)\/projects\/bar%2Fbaz\/repository\/files\/foo\.ya?ml\/raw/;
describe('Utils', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('initialiseFormData', () => {
it('returns the initial form data object', () => {
expect(Utils.initialiseFormData()).toStrictEqual({
......@@ -11,4 +26,59 @@ describe('Utils', () => {
});
});
});
describe('getPipelineConfigurationPathParts', () => {
it.each`
path | parts
${''} | ${{ file: undefined, group: undefined, project: undefined }}
${'abc'} | ${{ file: undefined, group: undefined, project: undefined }}
${'foo@bar/baz'} | ${{ file: undefined, group: undefined, project: undefined }}
${'foo.pdf@bar/baz'} | ${{ file: undefined, group: undefined, project: undefined }}
${'foo.yml@bar/baz'} | ${{ file: 'foo.yml', group: 'bar', project: 'baz' }}
`('should return the correct object when $path is given', ({ path, parts }) => {
expect(Utils.getPipelineConfigurationPathParts(path)).toStrictEqual(parts);
});
});
describe('validatePipelineConfirmationFormat', () => {
it.each`
path | valid
${null} | ${false}
${''} | ${false}
${'abc'} | ${false}
${'foo@bar/baz'} | ${false}
${'foo.pdf@bar/baz'} | ${false}
${'foo.yaml@bar/baz'} | ${true}
${'foo.yml@bar/baz'} | ${true}
`('should validate to $valid when path is $path', ({ path, valid }) => {
expect(Utils.validatePipelineConfirmationFormat(path)).toBe(valid);
});
});
describe('fetchPipelineConfigurationFileExists', () => {
it.each`
path | returns
${''} | ${false}
${'abc'} | ${false}
${'foo@bar/baz'} | ${false}
${'foo.pdf@bar/baz'} | ${false}
${'foo.yaml@bar/baz'} | ${true}
${'foo.yml@bar/baz'} | ${true}
`('should return $returns when the path is $path', async ({ path, returns }) => {
mock.onGet(GET_RAW_FILE_ENDPOINT).reply(returns ? httpStatus.OK : httpStatus.NOT_FOUND, {});
expect(await Utils.fetchPipelineConfigurationFileExists(path)).toBe(returns);
});
it.each`
response | returns
${httpStatus.OK} | ${true}
${httpStatus.NO_CONTENT} | ${false}
${httpStatus.NOT_FOUND} | ${false}
`('should return $returns when the response is $response', async ({ response, returns }) => {
mock.onGet(GET_RAW_FILE_ENDPOINT).reply(response, {});
expect(await Utils.fetchPipelineConfigurationFileExists('foo.yml@bar/baz')).toBe(returns);
});
});
});
......@@ -7525,9 +7525,21 @@ msgstr ""
msgid "ComplianceFrameworks|All"
msgstr ""
msgid "ComplianceFrameworks|Combines with the CI configuration at runtime."
msgstr ""
msgid "ComplianceFrameworks|Compliance pipeline configuration location (optional)"
msgstr ""
msgid "ComplianceFrameworks|Could not find this configuration location, please try a different location"
msgstr ""
msgid "ComplianceFrameworks|Error fetching compliance frameworks data. Please refresh the page"
msgstr ""
msgid "ComplianceFrameworks|Invalid format: it should follow the format [PATH].y(a)ml@[GROUP]/[PROJECT]"
msgstr ""
msgid "ComplianceFrameworks|Once you have created a compliance framework it will appear here."
msgstr ""
......@@ -7543,6 +7555,9 @@ msgstr ""
msgid "ComplianceFrameworks|Use %{codeStart}::%{codeEnd} to create a %{linkStart}scoped set%{linkEnd} (eg. %{codeStart}SOX::AWS%{codeEnd})"
msgstr ""
msgid "ComplianceFrameworks|e.g. include-gitlab.ci.yml@group-name/project-name"
msgstr ""
msgid "ComplianceFramework|GDPR"
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