Commit 19a3f70a authored by Denys Mishunov's avatar Denys Mishunov Committed by Enrique Alcántara

Restricted visibility levels setting

Make sure the visibility options on the snippet edit form
follow the restrictions set in "Restricted visibility levels"
setting
parent 63db21c1
......@@ -6,19 +6,21 @@ import { __, sprintf } from '~/locale';
import TitleField from '~/vue_shared/components/form/title.vue';
import { redirectTo, joinPaths } from '~/lib/utils/url_utility';
import FormFooterActions from '~/vue_shared/components/form/form_footer_actions.vue';
import { SNIPPET_MARK_EDIT_APP_START } from '~/performance_constants';
import UpdateSnippetMutation from '../mutations/updateSnippet.mutation.graphql';
import CreateSnippetMutation from '../mutations/createSnippet.mutation.graphql';
import { getSnippetMixin } from '../mixins/snippets';
import {
SNIPPET_VISIBILITY_PRIVATE,
SNIPPET_CREATE_MUTATION_ERROR,
SNIPPET_UPDATE_MUTATION_ERROR,
SNIPPET_VISIBILITY_PRIVATE,
} from '../constants';
import defaultVisibilityQuery from '../queries/snippet_visibility.query.graphql';
import SnippetBlobActionsEdit from './snippet_blob_actions_edit.vue';
import SnippetVisibilityEdit from './snippet_visibility_edit.vue';
import SnippetDescriptionEdit from './snippet_description_edit.vue';
import { SNIPPET_MARK_EDIT_APP_START } from '~/performance_constants';
export default {
components: {
......@@ -31,6 +33,15 @@ export default {
GlLoadingIcon,
},
mixins: [getSnippetMixin],
apollo: {
defaultVisibility: {
query: defaultVisibilityQuery,
manual: true,
result({ data: { selectedLevel } }) {
this.selectedLevelDefault = selectedLevel;
},
},
},
props: {
markdownPreviewPath: {
type: String,
......@@ -56,6 +67,7 @@ export default {
isUpdating: false,
newSnippet: false,
actions: [],
selectedLevelDefault: SNIPPET_VISIBILITY_PRIVATE,
};
},
computed: {
......@@ -98,6 +110,13 @@ export default {
descriptionFieldId() {
return `${this.isProjectSnippet ? 'project' : 'personal'}_snippet_description`;
},
newSnippetSchema() {
return {
title: '',
description: '',
visibilityLevel: this.selectedLevelDefault,
};
},
},
beforeCreate() {
performance.mark(SNIPPET_MARK_EDIT_APP_START);
......@@ -126,7 +145,7 @@ export default {
},
onNewSnippetFetched() {
this.newSnippet = true;
this.snippet = this.$options.newSnippetSchema;
this.snippet = this.newSnippetSchema;
},
onExistingSnippetFetched() {
this.newSnippet = false;
......@@ -184,11 +203,6 @@ export default {
this.actions = actions;
},
},
newSnippetSchema: {
title: '',
description: '',
visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
},
};
</script>
<template>
......
<script>
import { GlIcon, GlFormGroup, GlFormRadio, GlFormRadioGroup, GlLink } from '@gitlab/ui';
import {
SNIPPET_VISIBILITY,
SNIPPET_VISIBILITY_PRIVATE,
SNIPPET_VISIBILITY_INTERNAL,
SNIPPET_VISIBILITY_PUBLIC,
} from '~/snippets/constants';
import defaultVisibilityQuery from '../queries/snippet_visibility.query.graphql';
import { defaultSnippetVisibilityLevels } from '../utils/blob';
import { SNIPPET_LEVELS_RESTRICTED, SNIPPET_LEVELS_DISABLED } from '~/snippets/constants';
export default {
components: {
......@@ -15,6 +12,16 @@ export default {
GlFormRadioGroup,
GlLink,
},
apollo: {
defaultVisibility: {
query: defaultVisibilityQuery,
manual: true,
result({ data: { visibilityLevels, multipleLevelsRestricted } }) {
this.visibilityLevels = defaultSnippetVisibilityLevels(visibilityLevels);
this.multipleLevelsRestricted = multipleLevelsRestricted;
},
},
},
props: {
helpLink: {
type: String,
......@@ -28,19 +35,17 @@ export default {
},
value: {
type: String,
required: false,
default: SNIPPET_VISIBILITY_PRIVATE,
required: true,
},
},
computed: {
visibilityOptions() {
return [
SNIPPET_VISIBILITY_PRIVATE,
SNIPPET_VISIBILITY_INTERNAL,
SNIPPET_VISIBILITY_PUBLIC,
].map(key => ({ value: key, ...SNIPPET_VISIBILITY[key] }));
},
data() {
return {
visibilityLevels: [],
multipleLevelsRestricted: false,
};
},
SNIPPET_LEVELS_DISABLED,
SNIPPET_LEVELS_RESTRICTED,
};
</script>
<template>
......@@ -51,10 +56,10 @@ export default {
><gl-icon :size="12" name="question"
/></gl-link>
</label>
<gl-form-group id="visibility-level-setting">
<gl-form-radio-group v-bind="$attrs" :checked="value" stacked v-on="$listeners">
<gl-form-group id="visibility-level-setting" class="gl-mb-0">
<gl-form-radio-group :checked="value" stacked v-bind="$attrs" v-on="$listeners">
<gl-form-radio
v-for="option in visibilityOptions"
v-for="option in visibilityLevels"
:key="option.value"
:value="option.value"
class="mb-3"
......@@ -71,5 +76,12 @@ export default {
</gl-form-radio>
</gl-form-radio-group>
</gl-form-group>
<div class="text-muted" data-testid="restricted-levels-info">
<template v-if="!visibilityLevels.length">{{ $options.SNIPPET_LEVELS_DISABLED }}</template>
<template v-else-if="multipleLevelsRestricted">{{
$options.SNIPPET_LEVELS_RESTRICTED
}}</template>
</div>
</div>
</template>
......@@ -33,3 +33,15 @@ export const SNIPPET_BLOB_ACTION_MOVE = 'move';
export const SNIPPET_BLOB_ACTION_DELETE = 'delete';
export const SNIPPET_MAX_BLOBS = 10;
export const SNIPPET_LEVELS_MAP = {
0: SNIPPET_VISIBILITY_PRIVATE,
10: SNIPPET_VISIBILITY_INTERNAL,
20: SNIPPET_VISIBILITY_PUBLIC,
};
export const SNIPPET_LEVELS_RESTRICTED = __(
'Other visibility settings have been disabled by the administrator.',
);
export const SNIPPET_LEVELS_DISABLED = __(
'Visibility settings have been disabled by the administrator.',
);
......@@ -5,6 +5,7 @@ import createDefaultClient from '~/lib/graphql';
import SnippetsShow from './components/show.vue';
import SnippetsEdit from './components/edit.vue';
import { SNIPPET_LEVELS_MAP, SNIPPET_VISIBILITY_PRIVATE } from '~/snippets/constants';
Vue.use(VueApollo);
Vue.use(Translate);
......@@ -18,13 +19,28 @@ function appFactory(el, Component) {
defaultClient: createDefaultClient(),
});
const {
visibilityLevels = '[]',
selectedLevel,
multipleLevelsRestricted,
...restDataset
} = el.dataset;
apolloProvider.clients.defaultClient.cache.writeData({
data: {
visibilityLevels: JSON.parse(visibilityLevels),
selectedLevel: SNIPPET_LEVELS_MAP[selectedLevel] ?? SNIPPET_VISIBILITY_PRIVATE,
multipleLevelsRestricted: 'multipleLevelsRestricted' in el.dataset,
},
});
return new Vue({
el,
apolloProvider,
render(createElement) {
return createElement(Component, {
props: {
...el.dataset,
...restDataset,
},
});
},
......
query defaultSnippetVisibility {
visibilityLevels @client
selectedLevel @client
multipleLevelsRestricted @client
}
......@@ -4,6 +4,8 @@ import {
SNIPPET_BLOB_ACTION_UPDATE,
SNIPPET_BLOB_ACTION_MOVE,
SNIPPET_BLOB_ACTION_DELETE,
SNIPPET_LEVELS_MAP,
SNIPPET_VISIBILITY,
} from '../constants';
const createLocalId = () => uniqueId('blob_local_');
......@@ -64,3 +66,16 @@ export const diffAll = (blobs, origBlobs) => {
return [...deletedEntries, ...newEntries];
};
export const defaultSnippetVisibilityLevels = arr => {
if (Array.isArray(arr)) {
return arr.map(l => {
const translatedLevel = SNIPPET_LEVELS_MAP[l];
return {
value: translatedLevel,
...SNIPPET_VISIBILITY[translatedLevel],
};
});
}
return [];
};
- if Feature.enabled?(:snippets_edit_vue)
#js-snippet-edit.snippet-form{ data: {'project_path': @snippet.project&.full_path, 'snippet-gid': @snippet.new_record? ? '' : @snippet.to_global_id, 'markdown-preview-path': preview_markdown_path(parent), 'markdown-docs-path': help_page_path('user/markdown'), 'visibility-help-link': help_page_path("public_access/public_access") } }
- available_visibility_levels = available_visibility_levels(@snippet)
#js-snippet-edit.snippet-form{ data: {'project_path': @snippet.project&.full_path, 'snippet-gid': @snippet.new_record? ? '' : @snippet.to_global_id, 'markdown-preview-path': preview_markdown_path(parent), 'markdown-docs-path': help_page_path('user/markdown'), 'visibility-help-link': help_page_path("public_access/public_access"), 'visibility_levels': available_visibility_levels, 'selected_level': snippets_selected_visibility_level(available_visibility_levels, @snippet.visibility_level), 'multiple_levels_restricted': multiple_visibility_levels_restricted? } }
- else
.snippet-form-holder
= form_for @snippet, url: url,
......
......@@ -20,6 +20,7 @@ exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] =
</label>
<gl-form-group-stub
class="gl-mb-0"
id="visibility-level-setting"
>
<gl-form-radio-group-stub
......@@ -90,5 +91,12 @@ exports[`Snippet Visibility Edit component rendering matches the snapshot 1`] =
</gl-form-radio-stub>
</gl-form-radio-group-stub>
</gl-form-group-stub>
<div
class="text-muted"
data-testid="restricted-levels-info"
>
<!---->
</div>
</div>
`;
......@@ -102,6 +102,13 @@ describe('Snippet Edit app', () => {
markdownDocsPath: 'http://docs.foo.bar',
...props,
},
data() {
return {
snippet: {
visibilityLevel: SNIPPET_VISIBILITY_PRIVATE,
},
};
},
});
}
......
import { GlFormRadio, GlIcon, GlFormRadioGroup, GlLink } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import SnippetVisibilityEdit from '~/snippets/components/snippet_visibility_edit.vue';
import { defaultSnippetVisibilityLevels } from '~/snippets/utils/blob';
import {
SNIPPET_VISIBILITY,
SNIPPET_VISIBILITY_PRIVATE,
SNIPPET_VISIBILITY_INTERNAL,
SNIPPET_VISIBILITY_PUBLIC,
SNIPPET_LEVELS_RESTRICTED,
SNIPPET_LEVELS_DISABLED,
} from '~/snippets/constants';
describe('Snippet Visibility Edit component', () => {
let wrapper;
const defaultHelpLink = '/foo/bar';
const defaultVisibilityLevel = 'private';
function createComponent(propsData = {}, deep = false) {
const defaultVisibility = defaultSnippetVisibilityLevels([0, 10, 20]);
function createComponent({
propsData = {},
visibilityLevels = defaultVisibility,
multipleLevelsRestricted = false,
deep = false,
} = {}) {
const method = deep ? mount : shallowMount;
const $apollo = {
queries: {
defaultVisibility: {
loading: false,
},
},
};
wrapper = method.call(this, SnippetVisibilityEdit, {
mock: { $apollo },
propsData: {
helpLink: defaultHelpLink,
isProjectSnippet: false,
value: defaultVisibilityLevel,
...propsData,
},
data() {
return {
visibilityLevels,
multipleLevelsRestricted,
};
},
});
}
const findLabel = () => wrapper.find('label');
const findLink = () => wrapper.find('label').find(GlLink);
const findRadios = () => wrapper.find(GlFormRadioGroup).findAll(GlFormRadio);
const findRadiosData = () =>
findRadios().wrappers.map(x => {
......@@ -47,60 +71,84 @@ describe('Snippet Visibility Edit component', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('renders visibility options', () => {
createComponent({}, true);
it('renders label help link', () => {
createComponent();
expect(findLink().attributes('href')).toBe(defaultHelpLink);
});
it('when helpLink is not defined, does not render label help link', () => {
createComponent({ propsData: { helpLink: null } });
expect(findRadiosData()).toEqual([
{
expect(findLink().exists()).toBe(false);
});
describe('Visibility options', () => {
const findRestrictedInfo = () => wrapper.find('[data-testid="restricted-levels-info"]');
const RESULTING_OPTIONS = {
0: {
value: SNIPPET_VISIBILITY_PRIVATE,
icon: SNIPPET_VISIBILITY.private.icon,
text: SNIPPET_VISIBILITY.private.label,
description: SNIPPET_VISIBILITY.private.description,
},
{
10: {
value: SNIPPET_VISIBILITY_INTERNAL,
icon: SNIPPET_VISIBILITY.internal.icon,
text: SNIPPET_VISIBILITY.internal.label,
description: SNIPPET_VISIBILITY.internal.description,
},
{
20: {
value: SNIPPET_VISIBILITY_PUBLIC,
icon: SNIPPET_VISIBILITY.public.icon,
text: SNIPPET_VISIBILITY.public.label,
description: SNIPPET_VISIBILITY.public.description,
},
]);
});
it('when project snippet, renders special private description', () => {
createComponent({ isProjectSnippet: true }, true);
};
expect(findRadiosData()[0]).toEqual({
value: SNIPPET_VISIBILITY_PRIVATE,
icon: SNIPPET_VISIBILITY.private.icon,
text: SNIPPET_VISIBILITY.private.label,
description: SNIPPET_VISIBILITY.private.description_project,
it.each`
levels | resultOptions
${undefined} | ${[]}
${''} | ${[]}
${[]} | ${[]}
${[0]} | ${[RESULTING_OPTIONS[0]]}
${[0, 10]} | ${[RESULTING_OPTIONS[0], RESULTING_OPTIONS[10]]}
${[0, 10, 20]} | ${[RESULTING_OPTIONS[0], RESULTING_OPTIONS[10], RESULTING_OPTIONS[20]]}
${[0, 20]} | ${[RESULTING_OPTIONS[0], RESULTING_OPTIONS[20]]}
${[10, 20]} | ${[RESULTING_OPTIONS[10], RESULTING_OPTIONS[20]]}
`('renders correct visibility options for $levels', ({ levels, resultOptions }) => {
createComponent({ visibilityLevels: defaultSnippetVisibilityLevels(levels), deep: true });
expect(findRadiosData()).toEqual(resultOptions);
});
});
it('renders label help link', () => {
createComponent();
expect(
findLabel()
.find(GlLink)
.attributes('href'),
).toBe(defaultHelpLink);
});
it.each`
levels | levelsRestricted | resultText
${[]} | ${false} | ${SNIPPET_LEVELS_DISABLED}
${[]} | ${true} | ${SNIPPET_LEVELS_DISABLED}
${[0]} | ${true} | ${SNIPPET_LEVELS_RESTRICTED}
${[0]} | ${false} | ${''}
${[0, 10, 20]} | ${false} | ${''}
`(
'renders correct information about restricted visibility levels for $levels',
({ levels, levelsRestricted, resultText }) => {
createComponent({
visibilityLevels: defaultSnippetVisibilityLevels(levels),
multipleLevelsRestricted: levelsRestricted,
});
expect(findRestrictedInfo().text()).toBe(resultText);
},
);
it('when helpLink is not defined, does not render label help link', () => {
createComponent({ helpLink: null });
it('when project snippet, renders special private description', () => {
createComponent({ propsData: { isProjectSnippet: true }, deep: true });
expect(
findLabel()
.find(GlLink)
.exists(),
).toBe(false);
expect(findRadiosData()[0]).toEqual({
value: SNIPPET_VISIBILITY_PRIVATE,
icon: SNIPPET_VISIBILITY.private.icon,
text: SNIPPET_VISIBILITY.private.label,
description: SNIPPET_VISIBILITY.private.description_project,
});
});
});
});
......@@ -108,7 +156,7 @@ describe('Snippet Visibility Edit component', () => {
it('pre-selects correct option in the list', () => {
const value = SNIPPET_VISIBILITY_INTERNAL;
createComponent({ value });
createComponent({ propsData: { value } });
expect(wrapper.find(GlFormRadioGroup).attributes('checked')).toBe(value);
});
......
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