Commit 4d256697 authored by Phil Hughes's avatar Phil Hughes

Merge branch '5940-manual-license-management' into 'master'

Add form to enter licenses manually

Closes #5940

See merge request gitlab-org/gitlab-ee!7603
parents 98fee756 a4b559aa
<script>
import { Button } from '@gitlab-org/gitlab-ui';
import { LICENSE_APPROVAL_STATUS } from '../constants';
import AddLicenseFormDropdown from './add_license_form_dropdown.vue';
import { s__ } from '~/locale';
export default {
name: 'AddLicenseForm',
components: {
AddLicenseFormDropdown,
glButton: Button,
},
LICENSE_APPROVAL_STATUS,
approvalStatusOptions: [
{ value: LICENSE_APPROVAL_STATUS.APPROVED, label: s__('LicenseManagement|Approve') },
{ value: LICENSE_APPROVAL_STATUS.BLACKLISTED, label: s__('LicenseManagement|Blacklist') },
],
props: {
managedLicenses: {
type: Array,
required: false,
default: () => [],
},
},
data() {
return {
approvalStatus: '',
licenseName: '',
};
},
computed: {
isInvalidLicense() {
return this.managedLicenses.some(({ name }) => name === this.licenseName);
},
submitDisabled() {
return this.isInvalidLicense || this.licenseName.trim() === '' || this.approvalStatus === '';
},
},
methods: {
addLicense() {
this.$emit('addLicense', {
newStatus: this.approvalStatus,
license: { name: this.licenseName },
});
this.closeForm();
},
closeForm() {
this.$emit('closeForm');
},
},
};
</script>
<template>
<div class="col-sm-6 js-add-license-form">
<div class="form-group">
<label
class="label-bold"
for="js-license-dropdown"
>
{{ s__('LicenseManagement|Add licenses manually to approve or blacklist') }}
</label>
<add-license-form-dropdown
id="js-license-dropdown"
v-model="licenseName"
:placeholder="s__('LicenseManagement|License name')"
/>
<div
class="invalid-feedback"
:class="{'d-block': isInvalidLicense}"
>
{{ s__('LicenseManagement|This license already exists in this project.') }}
</div>
</div>
<div class="form-group">
<div
v-for="option in $options.approvalStatusOptions"
:key="option.value"
class="form-check"
>
<input
:id="`js-${option.value}-license-radio`"
v-model="approvalStatus"
class="form-check-input"
type="radio"
:value="option.value"
/>
<label
:for="`js-${option.value}-license-radio`"
class="form-check-label"
>
{{ option.label }}
</label>
</div>
</div>
<gl-button
class="js-submit"
variant="default"
:disabled="submitDisabled"
@click="addLicense"
>
{{ s__('LicenseManagement|Submit') }}
</gl-button>
<gl-button
class="js-cancel"
variant="default"
@click="closeForm"
>
{{ s__('LicenseManagement|Cancel') }}
</gl-button>
</div>
</template>
<script>
import $ from 'jquery';
import { KNOWN_LICENSES } from '../constants';
export default {
name: 'AddLicenseFormDropdown',
props: {
placeholder: {
type: String,
required: false,
default: '',
},
value: {
type: String,
required: false,
default: '',
},
},
mounted() {
$(this.$refs.dropdownInput)
.val(this.value)
.select2({
allowClear: true,
placeholder: this.placeholder,
createSearchChoice: term => ({ id: term, text: term }),
createSearchChoicePosition: 'bottom',
data: KNOWN_LICENSES.map(license => ({ id: license, text: license })),
})
.on('change', e => {
this.$emit('input', e.target.value);
});
},
beforeDestroy() {
$(this.$refs.dropdownInput).select2('destroy');
},
};
</script>
<template>
<input
ref="dropdownInput"
type="hidden"
/>
</template>
// eslint-disable-next-line import/prefer-default-export
export const LICENSE_APPROVAL_STATUS = {
APPROVED: 'approved',
BLACKLISTED: 'blacklisted',
};
export const KNOWN_LICENSES = [
'AGPL-1.0',
'AGPL-3.0',
'Apache 2.0',
'Artistic-2.0',
'BSD',
'CC0 1.0 Universal',
'CDDL-1.0',
'CDDL-1.1',
'EPL-1.0',
'EPL-2.0',
'GPLv2',
'GPLv3',
'ISC',
'LGPL',
'LGPL-2.1',
'MIT',
'Mozilla Public License 2.0',
'MS-PL',
'MS-RL',
'New BSD',
'Python Software Foundation License',
'ruby',
'Simplified BSD',
'WTFPL',
'Zlib',
];
<script>
import { mapState, mapActions } from 'vuex';
import { Button } from '@gitlab-org/gitlab-ui';
import { s__ } from '~/locale';
import AddLicenseForm from './components/add_license_form.vue';
import LicenseManagementRow from './components/license_management_row.vue';
import DeleteConfirmationModal from './components/delete_confirmation_modal.vue';
import createStore from './store/index';
......@@ -10,8 +12,10 @@ const store = createStore();
export default {
name: 'LicenseManagement',
components: {
AddLicenseForm,
DeleteConfirmationModal,
LicenseManagementRow,
glButton: Button,
},
props: {
apiUrl: {
......@@ -19,6 +23,9 @@ export default {
required: true,
},
},
data() {
return { formIsOpen: false };
},
store,
emptyMessage: s__(
'LicenseManagement|There are currently no approved or blacklisted licenses in this project.',
......@@ -33,7 +40,13 @@ export default {
this.loadManagedLicenses();
},
methods: {
...mapActions(['setAPISettings', 'loadManagedLicenses']),
...mapActions(['loadManagedLicenses', 'setAPISettings', 'setLicenseApproval']),
openAddLicenseForm() {
this.formIsOpen = true;
},
closeAddLicenseForm() {
this.formIsOpen = false;
},
},
};
</script>
......@@ -57,5 +70,21 @@ export default {
>
{{ $options.emptyMessage }}
</div>
<div class="prepend-top-default">
<add-license-form
v-if="formIsOpen"
:managed-licenses="managedLicenses"
@addLicense="setLicenseApproval"
@closeForm="closeAddLicenseForm"
/>
<gl-button
v-else
class="js-open-form"
variant="default"
@click="openAddLicenseForm"
>
{{ s__('LicenseManagement|Add a license') }}
</gl-button>
</div>
</div>
</template>
---
title: Add form to enter licenses manually
merge_request: 7603
author:
type: added
import Vue from 'vue';
import $ from 'jquery';
import Dropdown from 'ee/vue_shared/license_management/components/add_license_form_dropdown.vue';
import { KNOWN_LICENSES } from 'ee/vue_shared/license_management/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('AddLicenseFormDropdown', () => {
const Component = Vue.extend(Dropdown);
let vm;
afterEach(() => {
vm.$destroy();
});
it('emits `input` invent on change', () => {
vm = mountComponent(Component);
spyOn(vm, '$emit');
$(vm.$el)
.val('LGPL')
.trigger('change');
expect(vm.$emit).toHaveBeenCalledWith('input', 'LGPL');
});
it('sets the placeholder appropriately', () => {
const placeholder = 'Select a license';
vm = mountComponent(Component, { placeholder });
const dropdownContainer = $(vm.$el).select2('container')[0];
expect(dropdownContainer.textContent).toContain(placeholder);
});
it('sets the initial value correctly', () => {
const value = 'AWESOME_LICENSE';
vm = mountComponent(Component, { value });
expect(vm.$el.value).toContain(value);
});
it('shows all pre-defined licenses', done => {
vm = mountComponent(Component);
const element = $(vm.$el);
element.on('select2-open', () => {
const options = $('.select2-drop .select2-result');
expect(KNOWN_LICENSES.length).toEqual(options.length);
options.each(function() {
expect(KNOWN_LICENSES).toContain($(this).text());
});
done();
});
element.select2('open');
});
});
import Vue from 'vue';
import LicenseIssueBody from 'ee/vue_shared/license_management/components/add_license_form.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { LICENSE_APPROVAL_STATUS } from 'ee/vue_shared/license_management/constants';
describe('AddLicenseForm', () => {
const Component = Vue.extend(LicenseIssueBody);
let vm;
beforeEach(() => {
vm = mountComponent(Component);
});
afterEach(() => {
vm.$destroy();
});
describe('interaction', () => {
it('clicking the Submit button submits the data and closes the form', done => {
const name = 'LICENSE_TEST';
spyOn(vm, '$emit');
vm.approvalStatus = LICENSE_APPROVAL_STATUS.APPROVED;
vm.licenseName = name;
Vue.nextTick(() => {
const linkEl = vm.$el.querySelector('.js-submit');
linkEl.click();
expect(vm.$emit).toHaveBeenCalledWith('addLicense', {
newStatus: LICENSE_APPROVAL_STATUS.APPROVED,
license: { name },
});
expect(vm.$emit).toHaveBeenCalledWith('closeForm');
done();
});
});
it('clicking the Cancel button closes the form', () => {
const linkEl = vm.$el.querySelector('.js-cancel');
spyOn(vm, '$emit');
linkEl.click();
expect(vm.$emit).toHaveBeenCalledWith('closeForm');
});
});
describe('computed', () => {
describe('submitDisabled', () => {
it('is true if the approvalStatus is empty', () => {
vm.licenseName = 'FOO';
vm.approvalStatus = '';
expect(vm.submitDisabled).toBe(true);
});
it('is true if the licenseName is empty', () => {
vm.licenseName = '';
vm.approvalStatus = LICENSE_APPROVAL_STATUS.APPROVED;
expect(vm.submitDisabled).toBe(true);
});
it('is true if the entered license is duplicated', () => {
vm = mountComponent(Component, { managedLicenses: [{ name: 'FOO' }] });
vm.licenseName = 'FOO';
vm.approvalStatus = LICENSE_APPROVAL_STATUS.APPROVED;
expect(vm.submitDisabled).toBe(true);
});
});
describe('isInvalidLicense', () => {
it('is true if the entered license is duplicated', () => {
vm = mountComponent(Component, { managedLicenses: [{ name: 'FOO' }] });
vm.licenseName = 'FOO';
expect(vm.isInvalidLicense).toBe(true);
});
it('is false if the entered license is unique', () => {
vm = mountComponent(Component, { managedLicenses: [{ name: 'FOO' }] });
vm.licenseName = 'FOO2';
expect(vm.isInvalidLicense).toBe(false);
});
});
});
describe('template', () => {
it('renders the license select dropdown', () => {
const dropdownElement = vm.$el.querySelector('#js-license-dropdown');
expect(dropdownElement).not.toBeNull();
});
it('renders the license approval radio buttons dropdown', () => {
const radioButtonParents = vm.$el.querySelectorAll('.form-check');
expect(radioButtonParents.length).toBe(2);
expect(radioButtonParents[0].innerText.trim()).toBe('Approve');
expect(radioButtonParents[0].querySelector('.form-check-input')).not.toBeNull();
expect(radioButtonParents[1].innerText.trim()).toBe('Blacklist');
expect(radioButtonParents[1].querySelector('.form-check-input')).not.toBeNull();
});
it('renders error text, if there is a duplicate license', done => {
vm = mountComponent(Component, { managedLicenses: [{ name: 'FOO' }] });
vm.licenseName = 'FOO';
Vue.nextTick(() => {
const feedbackElement = vm.$el.querySelector('.invalid-feedback');
expect(feedbackElement).not.toBeNull();
expect(feedbackElement.classList).toContain('d-block');
expect(feedbackElement.innerText.trim()).toBe(
'This license already exists in this project.',
);
done();
});
});
it('disables submit, if the form is invalid', done => {
vm.licenseName = '';
Vue.nextTick(() => {
expect(vm.submitDisabled).toBe(true);
const submitButton = vm.$el.querySelector('.js-submit');
expect(submitButton).not.toBeNull();
expect(submitButton.disabled).toBe(true);
done();
});
});
});
});
import Vue from 'vue';
import Vuex from 'vuex';
import LicenseManagement from 'ee/vue_shared/license_management/license_management.vue';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { trimText } from 'spec/helpers/vue_component_helper';
import { TEST_HOST } from 'spec/test_constants';
import { approvedLicense, blacklistedLicense } from 'ee_spec/license_management/mock_data';
......@@ -36,6 +34,41 @@ describe('LicenseManagement', () => {
vm.$destroy();
});
describe('License Form', () => {
it('should render the form if the form is open', done => {
vm.formIsOpen = true;
return Vue.nextTick().then(() => {
const formEl = vm.$el.querySelector('.js-add-license-form');
expect(formEl).not.toBeNull();
const buttonEl = vm.$el.querySelector('.js-open-form');
expect(buttonEl).toBeNull();
done();
});
});
it('should render the button if the form is closed', done => {
vm.formIsOpen = false;
return Vue.nextTick().then(() => {
const formEl = vm.$el.querySelector('.js-add-license-form');
expect(formEl).toBeNull();
const buttonEl = vm.$el.querySelector('.js-open-form');
expect(buttonEl).not.toBeNull();
done();
});
});
it('clicking the Add a license button opens the form', () => {
const linkEl = vm.$el.querySelector('.js-open-form');
expect(vm.formIsOpen).toBe(false);
linkEl.click();
expect(vm.formIsOpen).toBe(true);
});
});
it('should render loading icon', done => {
store.replaceState({ ...store.state, isLoadingManagedLicenses: true });
......
......@@ -4522,6 +4522,15 @@ msgstr ""
msgid "License"
msgstr ""
msgid "LicenseManagement|Add a license"
msgstr ""
msgid "LicenseManagement|Add licenses manually to approve or blacklist"
msgstr ""
msgid "LicenseManagement|Approve"
msgstr ""
msgid "LicenseManagement|Approve license"
msgstr ""
......@@ -4531,6 +4540,9 @@ msgstr ""
msgid "LicenseManagement|Approved"
msgstr ""
msgid "LicenseManagement|Blacklist"
msgstr ""
msgid "LicenseManagement|Blacklist license"
msgstr ""
......@@ -4540,6 +4552,9 @@ msgstr ""
msgid "LicenseManagement|Blacklisted"
msgstr ""
msgid "LicenseManagement|Cancel"
msgstr ""
msgid "LicenseManagement|License"
msgstr ""
......@@ -4549,6 +4564,9 @@ msgstr ""
msgid "LicenseManagement|License details"
msgstr ""
msgid "LicenseManagement|License name"
msgstr ""
msgid "LicenseManagement|Manage approved and blacklisted licenses for this project."
msgstr ""
......@@ -4561,9 +4579,15 @@ msgstr ""
msgid "LicenseManagement|Remove license?"
msgstr ""
msgid "LicenseManagement|Submit"
msgstr ""
msgid "LicenseManagement|There are currently no approved or blacklisted licenses in this project."
msgstr ""
msgid "LicenseManagement|This license already exists in this project."
msgstr ""
msgid "LicenseManagement|URL"
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