Commit 14221fe5 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch '6860-FE-instance-level-project-templates-ee' into 'master'

Add Frontend for Instance-level project templates

Closes #6860

See merge request gitlab-org/gitlab-ee!6740
parents 86db303f 933eaca9
......@@ -12,6 +12,7 @@ export default function groupsSelect() {
const skipGroups = $select.data('skipGroups') || [];
$select.select2({
placeholder: 'Search for a group',
allowClear: $select.hasClass('allowClear'),
multiple: $select.hasClass('multiselect'),
minimumInputLength: 0,
ajax: {
......
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectVisibilitySelector from '../../../project_visibility';
import initProjectNew from '../../../projects/project_new';
document.addEventListener('DOMContentLoaded', () => {
initProjectLoadingSpinner();
initProjectVisibilitySelector();
initProjectNew.bindEvents();
});
......@@ -37,9 +37,10 @@ const bindEvents = () => {
const $projectFieldsForm = $('.project-fields-form');
const $selectedTemplateText = $('.selected-template');
const $changeTemplateBtn = $('.change-template');
const $selectedIcon = $('.selected-icon svg');
const $selectedIcon = $('.selected-icon');
const $templateProjectNameInput = $('#template-project-name #project_path');
const $pushNewProjectTipTrigger = $('.push-new-project-tip');
const $projectTemplateButtons = $('.project-templates-buttons');
if ($newProjectForm.length !== 1) {
return;
......@@ -88,35 +89,35 @@ const bindEvents = () => {
}
function chooseTemplate() {
$('.template-option').hide();
$projectTemplateButtons.addClass('hidden');
$projectFieldsForm.addClass('selected');
$selectedIcon.removeClass('d-block');
$selectedIcon.empty();
const value = $(this).val();
const templates = {
rails: {
text: 'Ruby on Rails',
icon: '.selected-icon .icon-rails',
icon: '.template-option svg.icon-rails',
},
express: {
text: 'NodeJS Express',
icon: '.selected-icon .icon-node-express',
icon: '.template-option svg.icon-node-express',
},
spring: {
text: 'Spring',
icon: '.selected-icon .icon-java-spring',
icon: '.template-option svg.icon-java-spring',
},
};
const selectedTemplate = templates[value];
$selectedTemplateText.text(selectedTemplate.text);
$(selectedTemplate.icon).addClass('d-block');
$(selectedTemplate.icon).clone().addClass('d-block').appendTo($selectedIcon);
$templateProjectNameInput.focus();
}
$useTemplateBtn.on('change', chooseTemplate);
$changeTemplateBtn.on('click', () => {
$('.template-option').show();
$projectTemplateButtons.removeClass('hidden');
$projectFieldsForm.removeClass('selected');
$useTemplateBtn.prop('checked', false);
});
......
......@@ -486,34 +486,36 @@
}
.project-template {
> .form-group {
margin-bottom: 0;
}
.tab-pane {
padding-top: 0;
padding-bottom: 0;
}
.template-option {
padding: $gl-padding $gl-padding $gl-padding ($gl-padding * 4);
position: relative;
.logo {
.btn-template-icon {
width: 40px !important;
}
}
padding: 16px 0;
&:not(:first-child) {
border-top: 1px solid $border-color;
}
.btn-template-icon {
position: absolute;
left: $gl-padding;
top: $gl-padding;
.controls {
margin-left: auto;
}
}
.template-title {
font-size: 16px;
}
.template-description {
margin: 6px 0 12px;
}
.template-button {
.choose-template {
input {
position: absolute;
clip: rect(0, 0, 0, 0);
......@@ -540,8 +542,6 @@
}
.selected-icon {
padding-right: $gl-padding;
svg {
display: none;
top: 7px;
......
......@@ -385,4 +385,6 @@
.settings-content
= render 'third_party_offers', application_setting: @application_setting
= render_if_exists 'admin/application_settings/custom_templates_form', expanded: expanded
= render_if_exists 'admin/application_settings/pseudonymizer_settings', expanded: expanded
.project-templates-buttons.import-buttons
- Gitlab::ProjectTemplate.all.each do |template|
.template-option
= custom_icon(template.logo)
.template-title= template.title
.template-description= template.description
%label.btn.btn-success.template-button.choose-template.append-right-10{ for: template.name }
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name }
%span Use template
%a.btn.btn-default{ href: template.preview, rel: 'noopener noreferrer', target: '_blank' } Preview
- f ||= local_assigns[:f]
.project-fields-form
.row
.form-group.col-sm-12
%label.label-bold
Template
.input-group.template-input-group
.input-group-prepend
.input-group-text
.selected-icon
- Gitlab::ProjectTemplate.all.each do |template|
= custom_icon(template.logo)
.selected-template
.input-group-append
%button.btn.btn-default.change-template{ type: "button" } Change template
.project-templates-buttons.import-buttons.col-sm-12
= render 'projects/project_templates/built_in_templates'
= render 'new_project_fields', f: f, project_name_id: "template-project-name"
.project-fields-form
= render 'projects/project_templates/project_fields_form'
= render 'projects/new_project_fields', f: f, project_name_id: "template-project-name"
- Gitlab::ProjectTemplate.all.each do |template|
.template-option.d-flex.align-items-center
.logo.append-right-10
= custom_icon(template.logo, size: 40)
.description
%strong
= template.title
%br
.text-muted
= template.description
.controls.d-flex.align-items-center
%label.btn.btn-success.template-button.choose-template.append-right-10.append-bottom-0{ for: template.name }
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name }
%span
= _("Use template")
%a.btn.btn-default{ href: template.preview, rel: 'noopener noreferrer', target: '_blank' }
= _("Preview")
.row
.form-group.col-sm-12
%label.label-bold
= _('Template')
.input-group.template-input-group
.input-group-prepend
.input-group-text
.selected-icon.append-right-10
.selected-template
.input-group-append
%button.btn.btn-default.change-template{ type: "button" }
= _('Change template')
......@@ -65,7 +65,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d
get '/', to: redirect('%{username}'), as: nil
## EE-specific
get :available_templates, format: :json
get :available_templates, format: :js
## EE-specific
end
......
......@@ -36,6 +36,16 @@
1. Click **Create project**.
## Custom project templates
> **Notes:**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in [GitLab Edition Premium][ee] 11.2
When you create a new project, creating it based on custom project templates is a convenient option to bootstrap an existing project boilerplate.
If a custom group is defined on the instance, projects within this group are available as project templates within the "Create from template" section. These templates are shown in the "Custom" tab. In addition, you can click on "Preview" to explore what this project templates includes.
See the [admin area documentation](../user/admin_area/custom_project_templates.md) on how to configure custom project templates.
## Push to create a new project
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26388) in GitLab 10.5.
......
import '~/pages/admin/application_settings/index';
import groupsSelect from '~/groups_select';
document.addEventListener('DOMContentLoaded', () => {
groupsSelect();
});
import '~/pages/projects/new/index';
import initCustomProjectTemplates from 'ee/projects/custom_project_templates';
document.addEventListener('DOMContentLoaded', initCustomProjectTemplates);
import $ from 'jquery';
const bindEvents = () => {
const $newProjectForm = $('#new_project');
const $useCustomTemplateBtn = $('.custom-template-button > input');
const $projectFieldsForm = $('.project-fields-form');
const $selectedIcon = $('.selected-icon');
const $selectedTemplateText = $('.selected-template');
const $templateProjectNameInput = $('#template-project-name #project_path');
const $changeTemplateBtn = $('.change-template');
const $projectTemplateButtons = $('.project-templates-buttons');
const $projectFieldsFormInput = $('.project-fields-form input#project_use_custom_template');
if ($newProjectForm.length !== 1 || $useCustomTemplateBtn.length === 0) {
return;
}
function enableCustomTemplate() {
$projectFieldsFormInput.val(true);
}
function disableCustomTemplate() {
$projectFieldsFormInput.val(false);
}
function chooseTemplate() {
$projectTemplateButtons.addClass('hidden');
$projectFieldsForm.addClass('selected');
$selectedIcon.empty();
const value = $(this).val();
$selectedTemplateText.text(value);
$(this)
.parents('.template-option')
.find('.avatar')
.clone()
.addClass('d-block')
.removeClass('s40')
.appendTo($selectedIcon);
$templateProjectNameInput.focus();
enableCustomTemplate();
}
$useCustomTemplateBtn.on('change', chooseTemplate);
$changeTemplateBtn.on('click', () => {
$projectTemplateButtons.removeClass('hidden');
$useCustomTemplateBtn.prop('checked', false);
disableCustomTemplate();
});
};
export default () => {
const $navElement = $('.nav-link[href="#custom-templates"]');
const $tabContent = $('.project-templates-buttons#custom-templates');
$tabContent.on('ajax:success', bindEvents);
$navElement.one('click', () => {
$.get($tabContent.data('initialTemplates'));
});
bindEvents();
};
......@@ -157,5 +157,18 @@
.btn-blank {
padding: 6px 10px;
}
}
.project-template {
.template-input-group {
.selected-icon {
.avatar {
width: 20px;
height: 20px;
line-height: 20px;
font-size: $gl-font-size;
margin-right: 0;
}
}
}
}
module EE
module UsersController
def available_templates
render json: load_custom_project_templates
load_custom_project_templates
end
private
......
- if ::Gitlab::CurrentSettings.custom_project_templates_enabled?
%section.settings.as-custom-project-templates.no-animate#js-mirror-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
= _('Custom project templates')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
= _('Select the custom project template source group.')
.settings-content
= form_for @application_setting, url: admin_application_settings_path do |f|
= form_errors(@application_setting)
%fieldset
.form-group
= groups_select_tag('application_setting[custom_project_templates_group_id]', selected: @application_setting.custom_project_templates_group_id, class: 'input-clamp allowClear', multiple: false)
= f.submit _('Save changes'), class: "btn btn-success"
- project_template_count = current_user.available_custom_project_templates.count
- if ::Gitlab::CurrentSettings.custom_project_templates_enabled? && project_template_count > 0
.project-templates-buttons.col-sm-12
%ul.nav-tabs.nav-links.nav.scrolling-tabs
%li.built-in-tab
%a.nav-link.active{ href: "#built-in", data: { toggle: 'tab'} }
= _('Built-In')
%span.badge.badge-pill= Gitlab::ProjectTemplate.all.count
%li.custom-templates-tab
%a.nav-link{ href: "#custom-templates", data: { toggle: 'tab'} }
= _('Custom')
%span.badge.badge-pill= project_template_count
.tab-content
.project-templates-buttons.import-buttons.tab-pane.active#built-in
= render 'projects/project_templates/built_in_templates'
.project-templates-buttons.import-buttons.tab-pane#custom-templates{ data: {initial_templates: user_available_templates_path(current_user)} }
= icon("spin spinner 2x")
.project-fields-form
= render 'projects/project_templates/project_fields_form'
= f.hidden_field(:use_custom_template, value: false)
= render 'projects/new_project_fields', f: f, project_name_id: "template-project-name"
- else
= render_ce 'projects/project_templates', f: f
.custom-project-templates
- custom_project_templates.each do |template|
.template-option.d-flex.align-items-center
.avatar-container.s40
= project_icon(template, alt: template.name, class: 'btn-template-icon avatar s40 avatar-tile', lazy: false)
.description
%strong
= template.title
%br
.text-muted
= template.description
.controls.d-flex.align-items-baseline
%label.btn.btn-success.custom-template-button.choose-template.append-right-10.append-bottom-0{ for: template.name }
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name }
%span
= _('Use template')
%a.btn.btn-default{ href: project_path(template), rel: 'noopener noreferrer', target: '_blank' } Preview
= paginate custom_project_templates, params: {controller: 'users', action: 'available_templates', username: current_user.username}, theme: 'gitlab', remote: true
:plain
var target = $(".project-templates-buttons#custom-templates");
target.empty();
target.html("#{escape_javascript(render 'custom_project_templates', custom_project_templates: @custom_project_templates)}");
target.trigger("ajax:success");
---
title: Add Frontend for Instance-level project templates
merge_request: 6740
author:
type: added
# frozen_string_literal: true
require 'spec_helper'
describe 'Project' do
describe 'Custom projects templates' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let!(:projects) { create_list(:project, 3, :public, namespace: group) }
before do
stub_ee_application_setting(custom_project_templates_group_id: group.id)
end
describe 'when feature custom_project_templates is enabled' do
before do
stub_licensed_features(custom_project_templates: true)
allow(Project).to receive(:default_per_page).and_return(2)
sign_in user
visit new_project_path
end
it 'shows built-in templates tab' do
page.within '.project-template .built-in-tab' do
expect(page).to have_content 'Built-In'
end
end
it 'shows custom projects templates tab' do
page.within '.project-template .custom-templates-tab' do
expect(page).to have_content 'Custom'
end
end
it 'displays the number of projects templates available to the user' do
page.within '.project-template .custom-templates-tab span.badge' do
expect(page).to have_content '3'
end
end
it 'allows creation from custom project template', :js do
new_name = 'example_custom_project_template'
find('#create-from-template-tab').click
find('.project-template .custom-templates-tab').click
find("label[for='#{projects.first.name}']").click
page.within '.project-fields-form' do
fill_in("project_path", with: new_name)
Sidekiq::Testing.inline! do
click_button "Create project"
end
end
expect(page).to have_content new_name
expect(Project.last.name).to eq new_name
end
it 'has a working pagination', :js do
last_project = "label[for='#{projects.last.name}']"
find('#create-from-template-tab').click
find('.project-template .custom-templates-tab').click
expect(page).to have_css('.custom-project-templates .gl-pagination')
expect(page).not_to have_css(last_project)
find('.js-next-button a').click
expect(page).to have_css(last_project)
end
end
describe 'when feature custom_project_templates is disabled' do
it 'does not show custom project templates tab' do
expect(page).not_to have_css('.project-template .nav-tabs')
end
end
end
end
......@@ -1157,6 +1157,9 @@ msgstr ""
msgid "Browse files"
msgstr ""
msgid "Built-In"
msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
......@@ -1256,6 +1259,9 @@ msgstr ""
msgid "Change Weight"
msgstr ""
msgid "Change template"
msgstr ""
msgid "Change this value to influence how frequently the GitLab UI polls for updates."
msgstr ""
......@@ -2232,6 +2238,9 @@ msgstr ""
msgid "CurrentUser|Settings"
msgstr ""
msgid "Custom"
msgstr ""
msgid "Custom CI config path"
msgstr ""
......@@ -2241,6 +2250,9 @@ msgstr ""
msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
msgstr ""
msgid "Custom project templates"
msgstr ""
msgid "Customize colors"
msgstr ""
......@@ -4923,6 +4935,9 @@ msgstr ""
msgid "Preferences|Navigation theme"
msgstr ""
msgid "Preview"
msgstr ""
msgid "Primary"
msgstr ""
......@@ -5708,6 +5723,9 @@ msgstr ""
msgid "Select target branch"
msgstr ""
msgid "Select the custom project template source group."
msgstr ""
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
......@@ -6222,6 +6240,9 @@ msgstr ""
msgid "Team"
msgstr ""
msgid "Template"
msgstr ""
msgid "Terms of Service Agreement and Privacy Policy"
msgstr ""
......@@ -6892,6 +6913,9 @@ msgstr ""
msgid "Use one line per URI"
msgstr ""
msgid "Use template"
msgstr ""
msgid "Use the following registration token during setup:"
msgstr ""
......
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