Commit 32ad2cbf authored by Phil Hughes's avatar Phil Hughes

Merge branch '329575-support-restricted-visibility-level' into 'master'

Support restricted visibility level in fork form

See merge request gitlab-org/gitlab!62340
parents bd7147d6 379a1af4
...@@ -38,6 +38,10 @@ export default { ...@@ -38,6 +38,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
restrictedVisibilityLevels: {
type: Array,
required: true,
},
}, },
}; };
</script> </script>
...@@ -66,6 +70,7 @@ export default { ...@@ -66,6 +70,7 @@ export default {
:project-path="projectPath" :project-path="projectPath"
:project-description="projectDescription" :project-description="projectDescription"
:project-visibility="projectVisibility" :project-visibility="projectVisibility"
:restricted-visibility-levels="restrictedVisibilityLevels"
/> />
</div> </div>
</div> </div>
......
...@@ -95,6 +95,10 @@ export default { ...@@ -95,6 +95,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
restrictedVisibilityLevels: {
type: Array,
required: true,
},
}, },
data() { data() {
const form = { const form = {
...@@ -111,7 +115,7 @@ export default { ...@@ -111,7 +115,7 @@ export default {
required: false, required: false,
skipValidation: true, skipValidation: true,
}), }),
visibility: initFormField({ value: this.projectVisibility }), visibility: initFormField({ value: this.getInitialVisibilityValue() }),
}, },
}; };
return { return {
...@@ -134,13 +138,28 @@ export default { ...@@ -134,13 +138,28 @@ export default {
visibilityLevelCap() { visibilityLevelCap() {
return Math.min(this.projectVisibilityLevel, this.namespaceVisibilityLevel); return Math.min(this.projectVisibilityLevel, this.namespaceVisibilityLevel);
}, },
restrictedVisibilityLevelsSet() {
return new Set(this.restrictedVisibilityLevels);
},
allowedVisibilityLevels() { allowedVisibilityLevels() {
return Object.entries(VISIBILITY_LEVEL).reduce((levels, [levelName, levelValue]) => { const allowedLevels = Object.entries(VISIBILITY_LEVEL).reduce(
if (levelValue <= this.visibilityLevelCap) { (levels, [levelName, levelValue]) => {
levels.push(levelName); if (
} !this.restrictedVisibilityLevelsSet.has(levelValue) &&
return levels; levelValue <= this.visibilityLevelCap
}, []); ) {
levels.push(levelName);
}
return levels;
},
[],
);
if (!allowedLevels.length) {
return [PRIVATE_VISIBILITY];
}
return allowedLevels;
}, },
visibilityLevels() { visibilityLevels() {
return [ return [
...@@ -173,7 +192,8 @@ export default { ...@@ -173,7 +192,8 @@ export default {
watch: { watch: {
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
'form.fields.namespace.value': function () { 'form.fields.namespace.value': function () {
this.form.fields.visibility.value = PRIVATE_VISIBILITY; this.form.fields.visibility.value =
this.restrictedVisibilityLevels.length !== 0 ? null : PRIVATE_VISIBILITY;
}, },
// eslint-disable-next-line func-names // eslint-disable-next-line func-names
'form.fields.name.value': function (newVal) { 'form.fields.name.value': function (newVal) {
...@@ -191,6 +211,9 @@ export default { ...@@ -191,6 +211,9 @@ export default {
isVisibilityLevelDisabled(visibility) { isVisibilityLevelDisabled(visibility) {
return !this.allowedVisibilityLevels.includes(visibility); return !this.allowedVisibilityLevels.includes(visibility);
}, },
getInitialVisibilityValue() {
return this.restrictedVisibilityLevels.length !== 0 ? null : this.projectVisibility;
},
async onSubmit() { async onSubmit() {
this.form.showValidation = true; this.form.showValidation = true;
...@@ -340,6 +363,7 @@ export default { ...@@ -340,6 +363,7 @@ export default {
v-model="form.fields.visibility.value" v-model="form.fields.visibility.value"
data-testid="fork-visibility-radio-group" data-testid="fork-visibility-radio-group"
name="visibility" name="visibility"
:aria-label="__('visibility')"
required required
> >
<gl-form-radio <gl-form-radio
......
...@@ -16,6 +16,7 @@ if (gon.features.forkProjectForm) { ...@@ -16,6 +16,7 @@ if (gon.features.forkProjectForm) {
projectPath, projectPath,
projectDescription, projectDescription,
projectVisibility, projectVisibility,
restrictedVisibilityLevels,
} = mountElement.dataset; } = mountElement.dataset;
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
...@@ -38,6 +39,7 @@ if (gon.features.forkProjectForm) { ...@@ -38,6 +39,7 @@ if (gon.features.forkProjectForm) {
projectPath, projectPath,
projectDescription, projectDescription,
projectVisibility, projectVisibility,
restrictedVisibilityLevels: JSON.parse(restrictedVisibilityLevels),
}, },
}); });
}, },
......
...@@ -10,7 +10,8 @@ ...@@ -10,7 +10,8 @@
project_name: @project.name, project_name: @project.name,
project_path: @project.path, project_path: @project.path,
project_description: @project.description, project_description: @project.description,
project_visibility: @project.visibility } } project_visibility: @project.visibility,
restricted_visibility_levels: Gitlab::CurrentSettings.restricted_visibility_levels.to_json } }
- else - else
.row.gl-mt-3 .row.gl-mt-3
.col-lg-3 .col-lg-3
......
...@@ -39511,6 +39511,9 @@ msgstr "" ...@@ -39511,6 +39511,9 @@ msgstr ""
msgid "view the source" msgid "view the source"
msgstr "" msgstr ""
msgid "visibility"
msgstr ""
msgid "vulnerability" msgid "vulnerability"
msgid_plural "vulnerabilities" msgid_plural "vulnerabilities"
msgstr[0] "" msgstr[0] ""
......
...@@ -13,6 +13,7 @@ describe('App component', () => { ...@@ -13,6 +13,7 @@ describe('App component', () => {
projectPath: 'project-name', projectPath: 'project-name',
projectDescription: 'some project description', projectDescription: 'some project description',
projectVisibility: 'private', projectVisibility: 'private',
restrictedVisibilityLevels: [],
}; };
const createComponent = (props = {}) => { const createComponent = (props = {}) => {
......
import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadio, GlFormSelect } from '@gitlab/ui'; import { GlFormInputGroup, GlFormInput, GlForm, GlFormRadioGroup, GlFormRadio } from '@gitlab/ui';
import { getByRole, getAllByRole } from '@testing-library/dom';
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios'; import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter'; import AxiosMockAdapter from 'axios-mock-adapter';
...@@ -44,6 +45,7 @@ describe('ForkForm component', () => { ...@@ -44,6 +45,7 @@ describe('ForkForm component', () => {
projectPath: 'project-name', projectPath: 'project-name',
projectDescription: 'some project description', projectDescription: 'some project description',
projectVisibility: 'private', projectVisibility: 'private',
restrictedVisibilityLevels: [],
}; };
const mockGetRequest = (data = {}, statusCode = httpStatus.OK) => { const mockGetRequest = (data = {}, statusCode = httpStatus.OK) => {
...@@ -68,6 +70,7 @@ describe('ForkForm component', () => { ...@@ -68,6 +70,7 @@ describe('ForkForm component', () => {
stubs: { stubs: {
GlFormInputGroup, GlFormInputGroup,
GlFormInput, GlFormInput,
GlFormRadioGroup,
GlFormRadio, GlFormRadio,
}, },
}); });
...@@ -89,7 +92,7 @@ describe('ForkForm component', () => { ...@@ -89,7 +92,7 @@ describe('ForkForm component', () => {
axiosMock.restore(); axiosMock.restore();
}); });
const findFormSelect = () => wrapper.find(GlFormSelect); const findFormSelectOptions = () => wrapper.find('select[name="namespace"]').findAll('option');
const findPrivateRadio = () => wrapper.find('[data-testid="radio-private"]'); const findPrivateRadio = () => wrapper.find('[data-testid="radio-private"]');
const findInternalRadio = () => wrapper.find('[data-testid="radio-internal"]'); const findInternalRadio = () => wrapper.find('[data-testid="radio-internal"]');
const findPublicRadio = () => wrapper.find('[data-testid="radio-public"]'); const findPublicRadio = () => wrapper.find('[data-testid="radio-public"]');
...@@ -230,7 +233,7 @@ describe('ForkForm component', () => { ...@@ -230,7 +233,7 @@ describe('ForkForm component', () => {
expect(wrapper.findAll(GlFormRadio)).toHaveLength(3); expect(wrapper.findAll(GlFormRadio)).toHaveLength(3);
}); });
it('resets the visibility to default "private" when the namespace is changed', async () => { describe('when the namespace is changed', () => {
const namespaces = [ const namespaces = [
{ {
visibility: 'private', visibility: 'private',
...@@ -243,42 +246,114 @@ describe('ForkForm component', () => { ...@@ -243,42 +246,114 @@ describe('ForkForm component', () => {
}, },
]; ];
mockGetRequest(); beforeEach(() => {
createComponent( mockGetRequest();
{ });
projectVisibility: 'public',
},
{
namespaces,
},
);
expect(wrapper.vm.form.fields.visibility.value).toBe('public'); it('resets the visibility to default "private"', async () => {
findFormSelect().vm.$emit('input', namespaces[1]); createFullComponent({ projectVisibility: 'public' }, { namespaces });
await wrapper.vm.$nextTick(); expect(wrapper.vm.form.fields.visibility.value).toBe('public');
await findFormSelectOptions().at(1).setSelected();
await wrapper.vm.$nextTick();
expect(getByRole(wrapper.element, 'radio', { name: /private/i }).checked).toBe(true);
});
it('sets the visibility to be null when restrictedVisibilityLevels is set', async () => {
createFullComponent({ restrictedVisibilityLevels: [10] }, { namespaces });
await findFormSelectOptions().at(1).setSelected();
await wrapper.vm.$nextTick();
const container = getByRole(wrapper.element, 'radiogroup', { name: /visibility/i });
const visibilityRadios = getAllByRole(container, 'radio');
expect(visibilityRadios.filter((e) => e.checked)).toHaveLength(0);
});
});
it.each`
project | restrictedVisibilityLevels
${'private'} | ${[]}
${'internal'} | ${[]}
${'public'} | ${[]}
${'private'} | ${[0]}
${'private'} | ${[10]}
${'private'} | ${[20]}
${'private'} | ${[0, 10]}
${'private'} | ${[0, 20]}
${'private'} | ${[10, 20]}
${'private'} | ${[0, 10, 20]}
${'internal'} | ${[0]}
${'internal'} | ${[10]}
${'internal'} | ${[20]}
${'internal'} | ${[0, 10]}
${'internal'} | ${[0, 20]}
${'internal'} | ${[10, 20]}
${'internal'} | ${[0, 10, 20]}
${'public'} | ${[0]}
${'public'} | ${[10]}
${'public'} | ${[0, 10]}
${'public'} | ${[0, 20]}
${'public'} | ${[10, 20]}
${'public'} | ${[0, 10, 20]}
`('checks the correct radio button', async ({ project, restrictedVisibilityLevels }) => {
mockGetRequest();
createFullComponent({
projectVisibility: project,
restrictedVisibilityLevels,
});
expect(wrapper.vm.form.fields.visibility.value).toBe('private'); if (restrictedVisibilityLevels.length === 0) {
expect(wrapper.find('[name="visibility"]:checked').attributes('value')).toBe(project);
} else {
expect(wrapper.find('[name="visibility"]:checked').exists()).toBe(false);
}
}); });
it.each` it.each`
project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled project | namespace | privateIsDisabled | internalIsDisabled | publicIsDisabled | restrictedVisibilityLevels
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} ${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'private'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} ${'private'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'private'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} ${'private'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'internal'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} ${'internal'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} ${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[]}
${'internal'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} ${'internal'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} | ${[]}
${'public'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} ${'public'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[]}
${'public'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} ${'public'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[]}
${'public'} | ${'public'} | ${undefined} | ${undefined} | ${undefined} ${'public'} | ${'public'} | ${undefined} | ${undefined} | ${undefined} | ${[]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[0]}
${'internal'} | ${'internal'} | ${'true'} | ${undefined} | ${'true'} | ${[0]}
${'public'} | ${'public'} | ${'true'} | ${undefined} | ${undefined} | ${[0]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[10]}
${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[10]}
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${undefined} | ${[10]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[20]}
${'internal'} | ${'internal'} | ${undefined} | ${undefined} | ${'true'} | ${[20]}
${'public'} | ${'public'} | ${undefined} | ${undefined} | ${'true'} | ${[20]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]}
${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]}
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[10, 20]}
${'private'} | ${'private'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
${'internal'} | ${'internal'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
${'public'} | ${'public'} | ${undefined} | ${'true'} | ${'true'} | ${[0, 10, 20]}
`( `(
'sets appropriate radio button disabled state', 'sets appropriate radio button disabled state',
async ({ project, namespace, privateIsDisabled, internalIsDisabled, publicIsDisabled }) => { async ({
project,
namespace,
privateIsDisabled,
internalIsDisabled,
publicIsDisabled,
restrictedVisibilityLevels,
}) => {
mockGetRequest(); mockGetRequest();
createComponent( createComponent(
{ {
projectVisibility: project, projectVisibility: project,
restrictedVisibilityLevels,
}, },
{ {
form: { fields: { namespace: { value: { visibility: namespace } } } }, form: { fields: { namespace: { value: { visibility: namespace } } } },
......
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