Commit 559d89cf authored by Tim Zallmann's avatar Tim Zallmann

Merge branch '2934-create-new-project-re-add-project-name-field' into 'master'

Resolve "Create new project: Re-add project name field"

Closes #2934

See merge request gitlab-org/gitlab-ce!21386
parents 1d69d26f 03ab130e
...@@ -48,6 +48,16 @@ export const dasherize = str => str.replace(/[_\s]+/g, '-'); ...@@ -48,6 +48,16 @@ export const dasherize = str => str.replace(/[_\s]+/g, '-');
*/ */
export const slugify = str => str.trim().toLowerCase(); export const slugify = str => str.trim().toLowerCase();
/**
* Replaces whitespaces with hyphens and converts to lower case
* @param {String} str
* @returns {String}
*/
export const slugifyWithHyphens = str => {
const regex = new RegExp(/\s+/, 'g');
return str.toLowerCase().replace(regex, '-');
};
/** /**
* Truncates given text * Truncates given text
* *
......
import $ from 'jquery'; import $ from 'jquery';
import { getParameterValues } from '../lib/utils/url_utility'; import { getParameterValues } from '../lib/utils/url_utility';
import projectNew from './project_new';
export default () => { export default () => {
const path = getParameterValues('path')[0]; const pathParam = getParameterValues('path')[0];
const nameParam = getParameterValues('name')[0];
const $projectPath = $('.js-path-name');
const $projectName = $('.js-project-name');
// get the path url and append it in the inputS // get the path url and append it in the input
$('.js-path-name').val(path); $projectPath.val(pathParam);
// get the project name from the URL and set it as input value
$projectName.val(nameParam);
// generate slug when project name changes
$projectName.keyup(() => projectNew.onProjectNameChange($projectName, $projectPath));
}; };
import $ from 'jquery'; import $ from 'jquery';
import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils'; import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils';
import { slugifyWithHyphens } from '../lib/utils/text_utility';
let hasUserDefinedProjectPath = false; let hasUserDefinedProjectPath = false;
...@@ -29,18 +30,23 @@ const deriveProjectPathFromUrl = ($projectImportUrl) => { ...@@ -29,18 +30,23 @@ const deriveProjectPathFromUrl = ($projectImportUrl) => {
} }
}; };
const onProjectNameChange = ($projectNameInput, $projectPathInput) => {
const slug = slugifyWithHyphens($projectNameInput.val());
$projectPathInput.val(slug);
};
const bindEvents = () => { const bindEvents = () => {
const $newProjectForm = $('#new_project'); const $newProjectForm = $('#new_project');
const $projectImportUrl = $('#project_import_url'); const $projectImportUrl = $('#project_import_url');
const $projectPath = $('#project_path'); const $projectPath = $('.tab-pane.active #project_path');
const $useTemplateBtn = $('.template-button > input'); const $useTemplateBtn = $('.template-button > input');
const $projectFieldsForm = $('.project-fields-form'); const $projectFieldsForm = $('.project-fields-form');
const $selectedTemplateText = $('.selected-template'); const $selectedTemplateText = $('.selected-template');
const $changeTemplateBtn = $('.change-template'); const $changeTemplateBtn = $('.change-template');
const $selectedIcon = $('.selected-icon'); const $selectedIcon = $('.selected-icon');
const $templateProjectNameInput = $('#template-project-name #project_path');
const $pushNewProjectTipTrigger = $('.push-new-project-tip'); const $pushNewProjectTipTrigger = $('.push-new-project-tip');
const $projectTemplateButtons = $('.project-templates-buttons'); const $projectTemplateButtons = $('.project-templates-buttons');
const $projectName = $('.tab-pane.active #project_name');
if ($newProjectForm.length !== 1) { if ($newProjectForm.length !== 1) {
return; return;
...@@ -57,7 +63,8 @@ const bindEvents = () => { ...@@ -57,7 +63,8 @@ const bindEvents = () => {
$('.btn_import_gitlab_project').on('click', () => { $('.btn_import_gitlab_project').on('click', () => {
const importHref = $('a.btn_import_gitlab_project').attr('href'); const importHref = $('a.btn_import_gitlab_project').attr('href');
$('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`); $('.btn_import_gitlab_project')
.attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&name=${$projectName.val()}&path=${$projectPath.val()}`);
}); });
if ($pushNewProjectTipTrigger) { if ($pushNewProjectTipTrigger) {
...@@ -111,7 +118,15 @@ const bindEvents = () => { ...@@ -111,7 +118,15 @@ const bindEvents = () => {
const selectedTemplate = templates[value]; const selectedTemplate = templates[value];
$selectedTemplateText.text(selectedTemplate.text); $selectedTemplateText.text(selectedTemplate.text);
$(selectedTemplate.icon).clone().addClass('d-block').appendTo($selectedIcon); $(selectedTemplate.icon).clone().addClass('d-block').appendTo($selectedIcon);
$templateProjectNameInput.focus();
const $activeTabProjectName = $('.tab-pane.active #project_name');
const $activeTabProjectPath = $('.tab-pane.active #project_path');
$activeTabProjectName.focus();
$activeTabProjectName
.keyup(() => {
onProjectNameChange($activeTabProjectName, $activeTabProjectPath);
hasUserDefinedProjectPath = $activeTabProjectPath.val().trim().length > 0;
});
} }
$useTemplateBtn.on('change', chooseTemplate); $useTemplateBtn.on('change', chooseTemplate);
...@@ -131,9 +146,15 @@ const bindEvents = () => { ...@@ -131,9 +146,15 @@ const bindEvents = () => {
}); });
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl)); $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
$projectName.keyup(() => {
onProjectNameChange($projectName, $projectPath);
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
});
}; };
export default { export default {
bindEvents, bindEvents,
deriveProjectPathFromUrl, deriveProjectPathFromUrl,
onProjectNameChange,
}; };
...@@ -8,8 +8,11 @@ ...@@ -8,8 +8,11 @@
= form_tag import_gitlab_project_path, class: 'new_project', multipart: true do = form_tag import_gitlab_project_path, class: 'new_project', multipart: true do
.row .row
.form-group.project-name.col-sm-12
= label_tag :name, _('Project name'), class: 'label-bold'
= text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control input-lg", autofocus: true, required: true
.form-group.col-12.col-sm-6 .form-group.col-12.col-sm-6
= label_tag :namespace_id, 'Project path', class: 'label-bold' = label_tag :namespace_id, _('Project URL'), class: 'label-bold'
.form-group .form-group
.input-group .input-group
- if current_user.can_select_namespace? - if current_user.can_select_namespace?
...@@ -24,8 +27,8 @@ ...@@ -24,8 +27,8 @@
#{user_url(current_user.username)}/ #{user_url(current_user.username)}/
= hidden_field_tag :namespace_id, value: current_user.namespace_id = hidden_field_tag :namespace_id, value: current_user.namespace_id
.form-group.col-12.col-sm-6.project-path .form-group.col-12.col-sm-6.project-path
= label_tag :path, _('Project name'), class: 'label-bold' = label_tag :path, _('Project slug'), class: 'label-bold'
= text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, required: true
.row .row
.form-group.col-md-12 .form-group.col-md-12
......
...@@ -3,10 +3,13 @@ ...@@ -3,10 +3,13 @@
.row{ id: project_name_id } .row{ id: project_name_id }
= f.hidden_field :ci_cd_only, value: ci_cd_only = f.hidden_field :ci_cd_only, value: ci_cd_only
.form-group.project-name.col-sm-12
= f.label :name, class: 'label-bold' do
%span= _("Project name")
= f.text_field :name, placeholder: "My awesome project", class: "form-control input-lg", autofocus: true, required: true
.form-group.project-path.col-sm-6 .form-group.project-path.col-sm-6
= f.label :namespace_id, class: 'label-bold' do = f.label :namespace_id, class: 'label-bold' do
%span %span= s_("Project URL")
Project path
.input-group .input-group
- if current_user.can_select_namespace? - if current_user.can_select_namespace?
.input-group-prepend.has-tooltip{ title: root_url } .input-group-prepend.has-tooltip{ title: root_url }
...@@ -27,13 +30,12 @@ ...@@ -27,13 +30,12 @@
= f.hidden_field :namespace_id, value: current_user.namespace_id = f.hidden_field :namespace_id, value: current_user.namespace_id
.form-group.project-path.col-sm-6 .form-group.project-path.col-sm-6
= f.label :path, class: 'label-bold' do = f.label :path, class: 'label-bold' do
%span %span= _("Project slug")
Project name = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, required: true
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
- if current_user.can_create_group? - if current_user.can_create_group?
.form-text.text-muted .form-text.text-muted
Want to house several dependent projects under the same namespace? Want to house several dependent projects under the same namespace?
= link_to "Create a group", new_group_path = link_to "Create a group.", new_group_path
.form-group .form-group
= f.label :description, class: 'label-bold' do = f.label :description, class: 'label-bold' do
......
---
title: Re-add project name field on "Create new project" page
merge_request: 21386
author:
type: other
...@@ -4681,6 +4681,9 @@ msgstr "" ...@@ -4681,6 +4681,9 @@ msgstr ""
msgid "Project Badges" msgid "Project Badges"
msgstr "" msgstr ""
msgid "Project URL"
msgstr ""
msgid "Project access must be granted explicitly to each user." msgid "Project access must be granted explicitly to each user."
msgstr "" msgstr ""
...@@ -4708,6 +4711,9 @@ msgstr "" ...@@ -4708,6 +4711,9 @@ msgstr ""
msgid "Project name" msgid "Project name"
msgstr "" msgstr ""
msgid "Project slug"
msgstr ""
msgid "ProjectActivityRSS|Subscribe" msgid "ProjectActivityRSS|Subscribe"
msgstr "" msgstr ""
......
...@@ -20,7 +20,7 @@ describe 'Top Plus Menu', :js do ...@@ -20,7 +20,7 @@ describe 'Top Plus Menu', :js do
click_topmenuitem("New project") click_topmenuitem("New project")
expect(page).to have_content('Project path') expect(page).to have_content('Project URL')
expect(page).to have_content('Project name') expect(page).to have_content('Project name')
end end
...@@ -92,7 +92,7 @@ describe 'Top Plus Menu', :js do ...@@ -92,7 +92,7 @@ describe 'Top Plus Menu', :js do
find('.header-new-group-project a').click find('.header-new-group-project a').click
end end
expect(page).to have_content('Project path') expect(page).to have_content('Project URL')
expect(page).to have_content('Project name') expect(page).to have_content('Project name')
end end
end end
......
...@@ -20,6 +20,7 @@ describe 'Import/Export - project import integration test', :js do ...@@ -20,6 +20,7 @@ describe 'Import/Export - project import integration test', :js do
context 'when selecting the namespace' do context 'when selecting the namespace' do
let(:user) { create(:admin) } let(:user) { create(:admin) }
let!(:namespace) { user.namespace } let!(:namespace) { user.namespace }
let(:project_name) { 'Test Project Name' + SecureRandom.hex }
let(:project_path) { 'test-project-path' + SecureRandom.hex } let(:project_path) { 'test-project-path' + SecureRandom.hex }
context 'prefilled the path' do context 'prefilled the path' do
...@@ -27,12 +28,13 @@ describe 'Import/Export - project import integration test', :js do ...@@ -27,12 +28,13 @@ describe 'Import/Export - project import integration test', :js do
visit new_project_path visit new_project_path
select2(namespace.id, from: '#project_namespace_id') select2(namespace.id, from: '#project_namespace_id')
fill_in :project_name, with: project_name, visible: true
fill_in :project_path, with: project_path, visible: true fill_in :project_path, with: project_path, visible: true
click_import_project_tab click_import_project_tab
click_link 'GitLab export' click_link 'GitLab export'
expect(page).to have_content('Import an exported GitLab project') expect(page).to have_content('Import an exported GitLab project')
expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}") expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&name=#{ERB::Util.url_encode(project_name)}&path=#{project_path}")
attach_file('file', file) attach_file('file', file)
click_on 'Import project' click_on 'Import project'
...@@ -56,6 +58,7 @@ describe 'Import/Export - project import integration test', :js do ...@@ -56,6 +58,7 @@ describe 'Import/Export - project import integration test', :js do
click_import_project_tab click_import_project_tab
click_link 'GitLab export' click_link 'GitLab export'
fill_in :name, with: 'Test Project Name', visible: true
fill_in :path, with: 'test-project-path', visible: true fill_in :path, with: 'test-project-path', visible: true
attach_file('file', file) attach_file('file', file)
...@@ -74,7 +77,8 @@ describe 'Import/Export - project import integration test', :js do ...@@ -74,7 +77,8 @@ describe 'Import/Export - project import integration test', :js do
visit new_project_path visit new_project_path
select2(user.namespace.id, from: '#project_namespace_id') select2(user.namespace.id, from: '#project_namespace_id')
fill_in :project_path, with: project.name, visible: true fill_in :project_name, with: project.name, visible: true
fill_in :project_path, with: project.path, visible: true
click_import_project_tab click_import_project_tab
click_link 'GitLab export' click_link 'GitLab export'
attach_file('file', file) attach_file('file', file)
......
...@@ -12,8 +12,9 @@ describe 'New project' do ...@@ -12,8 +12,9 @@ describe 'New project' do
it 'shows "New project" page', :js do it 'shows "New project" page', :js do
visit new_project_path visit new_project_path
expect(page).to have_content('Project path')
expect(page).to have_content('Project name') expect(page).to have_content('Project name')
expect(page).to have_content('Project URL')
expect(page).to have_content('Project slug')
find('#import-project-tab').click find('#import-project-tab').click
...@@ -187,7 +188,7 @@ describe 'New project' do ...@@ -187,7 +188,7 @@ describe 'New project' do
collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace) collision_project = create(:project, name: 'test-name-collision', namespace: user.namespace)
fill_in 'project_import_url', with: collision_project.http_url_to_repo fill_in 'project_import_url', with: collision_project.http_url_to_repo
fill_in 'project_path', with: collision_project.path fill_in 'project_name', with: collision_project.name
click_on 'Create project' click_on 'Create project'
......
...@@ -11,7 +11,7 @@ describe 'User creates a project', :js do ...@@ -11,7 +11,7 @@ describe 'User creates a project', :js do
it 'creates a new project' do it 'creates a new project' do
visit(new_project_path) visit(new_project_path)
fill_in(:project_path, with: 'Empty') fill_in(:project_name, with: 'Empty')
page.within('#content-body') do page.within('#content-body') do
click_button('Create project') click_button('Create project')
...@@ -37,6 +37,7 @@ describe 'User creates a project', :js do ...@@ -37,6 +37,7 @@ describe 'User creates a project', :js do
it 'creates a new project' do it 'creates a new project' do
visit(new_project_path) visit(new_project_path)
fill_in :project_name, with: 'A Subgroup Project'
fill_in :project_path, with: 'a-subgroup-project' fill_in :project_path, with: 'a-subgroup-project'
page.find('.js-select-namespace').click page.find('.js-select-namespace').click
...@@ -46,7 +47,7 @@ describe 'User creates a project', :js do ...@@ -46,7 +47,7 @@ describe 'User creates a project', :js do
click_button('Create project') click_button('Create project')
end end
expect(page).to have_content("Project 'a-subgroup-project' was successfully created") expect(page).to have_content("Project 'A Subgroup Project' was successfully created")
project = Project.last project = Project.last
......
...@@ -16,7 +16,7 @@ describe 'Project' do ...@@ -16,7 +16,7 @@ describe 'Project' do
it "allows creation from templates", :js do it "allows creation from templates", :js do
find('#create-from-template-tab').click find('#create-from-template-tab').click
find("label[for=#{template.name}]").click find("label[for=#{template.name}]").click
fill_in("project_path", with: template.name) fill_in("project_name", with: template.name)
page.within '#content-body' do page.within '#content-body' do
click_button "Create project" click_button "Create project"
......
...@@ -63,6 +63,12 @@ describe('text_utility', () => { ...@@ -63,6 +63,12 @@ describe('text_utility', () => {
}); });
}); });
describe('slugifyWithHyphens', () => {
it('should replaces whitespaces with hyphens and convert to lower case', () => {
expect(textUtils.slugifyWithHyphens('My Input String')).toEqual('my-input-string');
});
});
describe('stripHtml', () => { describe('stripHtml', () => {
it('replaces html tag with the default replacement', () => { it('replaces html tag with the default replacement', () => {
expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual( expect(textUtils.stripHtml('This is a text with <p>html</p>.')).toEqual(
......
...@@ -4,12 +4,14 @@ import projectNew from '~/projects/project_new'; ...@@ -4,12 +4,14 @@ import projectNew from '~/projects/project_new';
describe('New Project', () => { describe('New Project', () => {
let $projectImportUrl; let $projectImportUrl;
let $projectPath; let $projectPath;
let $projectName;
beforeEach(() => { beforeEach(() => {
setFixtures(` setFixtures(`
<div class='toggle-import-form'> <div class='toggle-import-form'>
<div class='import-url-data'> <div class='import-url-data'>
<input id="project_import_url" /> <input id="project_import_url" />
<input id="project_name" />
<input id="project_path" /> <input id="project_path" />
</div> </div>
</div> </div>
...@@ -17,6 +19,7 @@ describe('New Project', () => { ...@@ -17,6 +19,7 @@ describe('New Project', () => {
$projectImportUrl = $('#project_import_url'); $projectImportUrl = $('#project_import_url');
$projectPath = $('#project_path'); $projectPath = $('#project_path');
$projectName = $('#project_name');
}); });
describe('deriveProjectPathFromUrl', () => { describe('deriveProjectPathFromUrl', () => {
...@@ -129,4 +132,31 @@ describe('New Project', () => { ...@@ -129,4 +132,31 @@ describe('New Project', () => {
}); });
}); });
}); });
describe('deriveSlugFromProjectName', () => {
beforeEach(() => {
projectNew.bindEvents();
$projectName.val('').keyup();
});
it('converts project name to lower case and dash-limited slug', () => {
const dummyProjectName = 'My Awesome Project';
$projectName.val(dummyProjectName);
projectNew.onProjectNameChange($projectName, $projectPath);
expect($projectPath.val()).toEqual('my-awesome-project');
});
it('does not add additional dashes in the slug if the project name already contains dashes', () => {
const dummyProjectName = 'My-Dash-Delimited Awesome Project';
$projectName.val(dummyProjectName);
projectNew.onProjectNameChange($projectName, $projectPath);
expect($projectPath.val()).toEqual('my-dash-delimited-awesome-project');
});
});
}); });
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