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