Skip to content
代码片段 群组 项目
未验证 提交 093a6f2f 编辑于 作者: Justin Ho Tuan Duong's avatar Justin Ho Tuan Duong 提交者: GitLab
浏览文件

Merge branch 'new-project-option-validation' into 'master'

No related branches found
No related tags found
无相关合并请求
<script> <script>
import { GlButton, GlButtonGroup, GlFormGroup, GlIcon } from '@gitlab/ui'; import { GlButton, GlButtonGroup, GlFormGroup, GlIcon, GlAlert } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__, sprintf } from '~/locale';
import SafeHtml from '~/vue_shared/directives/safe_html'; import SafeHtml from '~/vue_shared/directives/safe_html';
import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue';
import SingleChoiceSelector from '~/vue_shared/components/single_choice_selector.vue'; import SingleChoiceSelector from '~/vue_shared/components/single_choice_selector.vue';
...@@ -63,6 +63,7 @@ export default { ...@@ -63,6 +63,7 @@ export default {
GlButtonGroup, GlButtonGroup,
GlFormGroup, GlFormGroup,
GlIcon, GlIcon,
GlAlert,
MultiStepFormTemplate, MultiStepFormTemplate,
SingleChoiceSelector, SingleChoiceSelector,
SingleChoiceSelectorItem, SingleChoiceSelectorItem,
...@@ -72,6 +73,7 @@ export default { ...@@ -72,6 +73,7 @@ export default {
directives: { directives: {
SafeHtml, SafeHtml,
}, },
inject: ['userNamespaceId', 'canCreateProject'],
props: { props: {
rootPath: { rootPath: {
type: String, type: String,
...@@ -106,24 +108,24 @@ export default { ...@@ -106,24 +108,24 @@ export default {
canImportProjects: { canImportProjects: {
type: Boolean, type: Boolean,
required: false, required: false,
default: true, default: false,
}, },
importSourcesEnabled: { importSourcesEnabled: {
type: Boolean, type: Boolean,
required: false, required: false,
default: true, default: false,
}, },
namespaceFullPath: { canSelectNamespace: {
type: String, type: Boolean,
required: false, required: false,
default: null, default: false,
}, },
namespaceId: { namespaceFullPath: {
type: String, type: String,
required: false, required: false,
default: null, default: null,
}, },
userNamespaceId: { namespaceId: {
type: String, type: String,
required: false, required: false,
default: null, default: null,
...@@ -133,6 +135,10 @@ export default { ...@@ -133,6 +135,10 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
userProjectLimit: {
type: Number,
required: true,
},
newProjectGuidelines: { newProjectGuidelines: {
type: String, type: String,
required: false, required: false,
...@@ -141,7 +147,8 @@ export default { ...@@ -141,7 +147,8 @@ export default {
}, },
data() { data() {
return { return {
selectedNamespace: this.namespaceId ? this.namespaceId : this.userNamespaceId, selectedNamespace:
this.namespaceId && this.canSelectNamespace ? this.namespaceId : this.userNamespaceId,
rootUrl: this.rootPath, rootUrl: this.rootPath,
}; };
}, },
...@@ -149,6 +156,25 @@ export default { ...@@ -149,6 +156,25 @@ export default {
isPersonalProject() { isPersonalProject() {
return this.selectedNamespace === this.userNamespaceId; return this.selectedNamespace === this.userNamespaceId;
}, },
canChooseOption() {
if (!this.isPersonalProject) return true;
return this.canCreateProject && this.userProjectLimit > 0;
},
errorMessage() {
if (this.userProjectLimit === 0) {
return s__(
'ProjectsNew|You cannot create projects in your personal namespace. Contact your GitLab administrator.',
);
}
return sprintf(
s__(
"ProjectsNew|You've reached your limit of %{limit} projects created. Contact your GitLab administrator.",
),
{
limit: this.userProjectLimit,
},
);
},
}, },
methods: { methods: {
choosePersonalNamespace() { choosePersonalNamespace() {
...@@ -165,7 +191,10 @@ export default { ...@@ -165,7 +191,10 @@ export default {
<template> <template>
<multi-step-form-template :title="__('Create new project')" :current-step="1"> <multi-step-form-template :title="__('Create new project')" :current-step="1">
<template #form> <template #form>
<gl-form-group :label="s__('ProjectNew|What do you want to create?')"> <gl-form-group
v-if="canSelectNamespace"
:label="s__('ProjectNew|What do you want to create?')"
>
<gl-button-group class="gl-w-full"> <gl-button-group class="gl-w-full">
<gl-button <gl-button
category="primary" category="primary"
...@@ -196,14 +225,14 @@ export default { ...@@ -196,14 +225,14 @@ export default {
<new-project-destination-select <new-project-destination-select
:namespace-full-path="namespaceFullPath" :namespace-full-path="namespaceFullPath"
:namespace-id="namespaceId" :namespace-id="namespaceId"
:user-namespace-id="userNamespaceId"
:track-label="trackLabel" :track-label="trackLabel"
:root-url="rootUrl" :root-url="rootUrl"
:groups-only="true"
data-testid="group-selector" data-testid="group-selector"
/> />
</gl-form-group> </gl-form-group>
<single-choice-selector checked="blank_project"> <single-choice-selector v-if="canChooseOption" checked="blank_project">
<single-choice-selector-item v-bind="$options.OPTIONS.blank" /> <single-choice-selector-item v-bind="$options.OPTIONS.blank" />
<single-choice-selector-item v-bind="$options.OPTIONS.template" /> <single-choice-selector-item v-bind="$options.OPTIONS.template" />
<single-choice-selector-item <single-choice-selector-item
...@@ -221,6 +250,9 @@ export default { ...@@ -221,6 +250,9 @@ export default {
</single-choice-selector-item> </single-choice-selector-item>
<single-choice-selector-item v-bind="$options.OPTIONS.transfer" :disabled="true" /> <single-choice-selector-item v-bind="$options.OPTIONS.transfer" :disabled="true" />
</single-choice-selector> </single-choice-selector>
<gl-alert v-else variant="danger" :dismissible="false">
{{ errorMessage }}
</gl-alert>
</template> </template>
<template #footer> <template #footer>
<div v-if="newProjectGuidelines" v-safe-html="newProjectGuidelines" class="gl-mb-6"></div> <div v-if="newProjectGuidelines" v-safe-html="newProjectGuidelines" class="gl-mb-6"></div>
......
...@@ -32,6 +32,7 @@ export default { ...@@ -32,6 +32,7 @@ export default {
debounce: DEBOUNCE_DELAY, debounce: DEBOUNCE_DELAY,
}, },
}, },
inject: ['userNamespaceId', 'canCreateProject'],
props: { props: {
namespaceFullPath: { namespaceFullPath: {
type: String, type: String,
...@@ -43,15 +44,15 @@ export default { ...@@ -43,15 +44,15 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
userNamespaceId: { trackLabel: {
type: String, type: String,
required: false, required: false,
default: '', default: '',
}, },
trackLabel: { groupsOnly: {
type: String, type: Boolean,
required: false, required: false,
default: '', default: false,
}, },
}, },
data() { data() {
...@@ -91,7 +92,9 @@ export default { ...@@ -91,7 +92,9 @@ export default {
); );
}, },
items() { items() {
return this.namespaceItems.concat(this.groupsItems); if (this.groupsOnly) return this.groupsItems;
if (this.canCreateProject) return this.namespaceItems.concat(this.groupsItems);
return this.groupsItems;
}, },
groupsItems() { groupsItems() {
if (this.hasGroupMatches) { if (this.hasGroupMatches) {
......
...@@ -26,6 +26,9 @@ export function initNewProjectForm() { ...@@ -26,6 +26,9 @@ export function initNewProjectForm() {
namespaceId, namespaceId,
userNamespaceId, userNamespaceId,
trackLabel, trackLabel,
canSelectNamespace,
canCreateProject,
userProjectLimit,
newProjectGuidelines, newProjectGuidelines,
} = el.dataset; } = el.dataset;
...@@ -40,12 +43,15 @@ export function initNewProjectForm() { ...@@ -40,12 +43,15 @@ export function initNewProjectForm() {
importSourcesEnabled: parseBoolean(importSourcesEnabled), importSourcesEnabled: parseBoolean(importSourcesEnabled),
namespaceFullPath, namespaceFullPath,
namespaceId, namespaceId,
userNamespaceId,
trackLabel, trackLabel,
canSelectNamespace: parseBoolean(canSelectNamespace),
userProjectLimit: parseInt(userProjectLimit, 10),
newProjectGuidelines, newProjectGuidelines,
}; };
const provide = { const provide = {
userNamespaceId,
canCreateProject: parseBoolean(canCreateProject),
projectHelpPath, projectHelpPath,
pushToCreateProjectCommand, pushToCreateProjectCommand,
}; };
......
...@@ -21,7 +21,10 @@ ...@@ -21,7 +21,10 @@
is_ci_cd_available: remote_mirror_setting_enabled?.to_s, is_ci_cd_available: remote_mirror_setting_enabled?.to_s,
can_import_projects: params[:namespace_id].presence ? current_user.can?(:import_projects, @namespace).to_s : 'true', can_import_projects: params[:namespace_id].presence ? current_user.can?(:import_projects, @namespace).to_s : 'true',
import_sources_enabled: import_sources_enabled?.to_s, import_sources_enabled: import_sources_enabled?.to_s,
track_label: local_assigns.fetch(:track_label, 'blank_project') } } track_label: local_assigns.fetch(:track_label, 'blank_project'),
can_select_namespace: current_user.can_select_namespace?.to_s,
can_create_project: current_user.can_create_project?.to_s,
user_project_limit: current_user.projects_limit } }
- else - else
.project-edit-container .project-edit-container
.project-edit-errors .project-edit-errors
......
...@@ -45781,6 +45781,12 @@ msgstr "" ...@@ -45781,6 +45781,12 @@ msgstr ""
msgid "ProjectsNew|You can always change your URL later" msgid "ProjectsNew|You can always change your URL later"
msgstr "" msgstr ""
   
msgid "ProjectsNew|You cannot create projects in your personal namespace. Contact your GitLab administrator."
msgstr ""
msgid "ProjectsNew|You've reached your limit of %{limit} projects created. Contact your GitLab administrator."
msgstr ""
msgid "ProjectsNew|Your project will be created at:" msgid "ProjectsNew|Your project will be created at:"
msgstr "" msgstr ""
   
import { GlAlert } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import App from '~/projects/new_v2/components/app.vue'; import App from '~/projects/new_v2/components/app.vue';
import CommandLine from '~/projects/new_v2/components/command_line.vue'; import CommandLine from '~/projects/new_v2/components/command_line.vue';
import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue';
import SingleChoiceSelector from '~/vue_shared/components/single_choice_selector.vue';
describe('New project creation app', () => { describe('New project creation app', () => {
let wrapper; let wrapper;
const createComponent = (props = {}) => { const createComponent = (props = {}, provide = {}) => {
wrapper = shallowMountExtended(App, { wrapper = shallowMountExtended(App, {
propsData: { propsData: {
rootPath: '/', rootPath: '/',
projectsUrl: '/dashboard/projects', projectsUrl: '/dashboard/projects',
userNamespaceId: '1', userProjectLimit: 10000,
canSelectNamespace: true,
...props, ...props,
}, },
provide: {
userNamespaceId: '1',
canCreateProject: true,
...provide,
},
}); });
}; };
const findMultyStepForm = () => wrapper.findComponent(MultiStepFormTemplate); const findMultiStepForm = () => wrapper.findComponent(MultiStepFormTemplate);
const findSingleChoiceSelector = () => wrapper.findComponent(SingleChoiceSelector);
const findAlert = () => wrapper.findComponent(GlAlert);
const findCommandLine = () => wrapper.findComponent(CommandLine); const findCommandLine = () => wrapper.findComponent(CommandLine);
it('renders a form', () => { it('renders a form', () => {
createComponent(); createComponent();
expect(findMultyStepForm().exists()).toBe(true); expect(findMultiStepForm().exists()).toBe(true);
expect(findAlert().exists()).toBe(false);
}); });
describe('personal namespace project', () => { describe('personal namespace project', () => {
beforeEach(() => { it('starts with personal namespace when no namespaceId provided', () => {
createComponent(); createComponent();
});
it('starts with personal namespace when no namespaceId provided', () => {
expect(wrapper.findByTestId('personal-namespace-button').props('selected')).toBe(true); expect(wrapper.findByTestId('personal-namespace-button').props('selected')).toBe(true);
expect(wrapper.findByTestId('group-namespace-button').props('selected')).toBe(false); expect(wrapper.findByTestId('group-namespace-button').props('selected')).toBe(false);
}); });
it('does not renders a group select', () => { it('does not renders a group select', () => {
createComponent();
expect(wrapper.findByTestId('group-selector').exists()).toBe(false); expect(wrapper.findByTestId('group-selector').exists()).toBe(false);
}); });
it('renders error when user reached a limit of projects', () => {
createComponent({}, { canCreateProject: false });
expect(findSingleChoiceSelector().exists()).toBe(false);
expect(findAlert().text()).toBe(
"You've reached your limit of 10000 projects created. Contact your GitLab administrator.",
);
});
it('renders error when user can not create personal projects', () => {
createComponent({ userProjectLimit: 0 }, { canCreateProject: false });
expect(findSingleChoiceSelector().exists()).toBe(false);
expect(findAlert().text()).toBe(
'You cannot create projects in your personal namespace. Contact your GitLab administrator.',
);
});
}); });
describe('with command line', () => { describe('with command line', () => {
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册