Skip to content
Snippets Groups Projects
Unverified Commit 1d3815f8 authored by Z.J. van de Weg's avatar Z.J. van de Weg
Browse files

Allow projects to be started from a template

Started implementation for the first iteration of
gitlab-org/gitlab-ce#32420. This will allow users to select a template
to start with, instead of an empty repository in the project just
created.

Internally this is basically a small extension of the ImportExport
GitLab projects we already support. We just import a certain import
tar archive. This commits includes the first one: Ruby on Rails. In the
future more will be added.
parent d964816b
No related branches found
No related tags found
No related merge requests found
Showing
with 238 additions and 37 deletions
app/assets/images/project_templates/rails.png

4.12 KiB

Loading
Loading
@@ -12,15 +12,7 @@ class Import::GitlabProjectsController < Import::BaseController
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end
 
import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename)
FileUtils.mkdir_p(File.dirname(import_upload_path))
FileUtils.copy_entry(project_params[:file].path, import_upload_path)
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user,
import_upload_path,
project_params[:path]).execute
@project = ::Projects::GitlabProjectsImporterService.new(current_user, project_params).execute
 
if @project.saved?
redirect_to(
Loading
Loading
Loading
Loading
@@ -27,7 +27,12 @@ class ProjectsController < Projects::ApplicationController
end
 
def create
@project = ::Projects::CreateService.new(current_user, project_params).execute
@project =
if project_from_template?
::Projects::CreateFromTemplateService.new(current_user, project_params).execute
else
::Projects::CreateService.new(current_user, project_params).execute
end
 
if @project.saved?
cookies[:issue_board_welcome_hidden] = { path: project_path(@project), value: nil, expires: Time.at(0) }
Loading
Loading
@@ -324,6 +329,7 @@ class ProjectsController < Projects::ApplicationController
:runners_token,
:tag_list,
:visibility_level,
:template_title,
 
project_feature_attributes: %i[
builds_access_level
Loading
Loading
@@ -345,6 +351,10 @@ class ProjectsController < Projects::ApplicationController
false
end
 
def project_from_template?
project_params[:template_title]&.present?
end
def project_view_files?
if current_user
current_user.project_view == 'files'
Loading
Loading
Loading
Loading
@@ -71,6 +71,7 @@ class Project < ActiveRecord::Base
 
attr_accessor :new_default_branch
attr_accessor :old_path_with_namespace
attr_accessor :template_title
attr_writer :pipeline_status
 
alias_attribute :title, :name
Loading
Loading
module Projects
class CreateFromTemplateService < BaseService
def initialize(user, params)
@current_user, @params = user, params.dup
end
def execute
params[:file] = Gitlab::ProjectTemplate.find(params[:template_title]).file
@params[:from_template] = true
GitlabProjectsImporterService.new(@current_user, @params).execute
end
end
end
# This service is an adapter used to for the GitLab Import feature, and
# creating a project from a template.
# The latter will under the hood just import an archive supplied by GitLab.
module Projects
class GitlabProjectsImporterService
attr_reader :current_user, :params
def initialize(user, params)
@current_user, @params = user, params.dup
end
def execute
FileUtils.mkdir_p(File.dirname(import_upload_path))
FileUtils.copy_entry(file.path, import_upload_path)
Gitlab::ImportExport::ProjectCreator.new(params[:namespace_id],
current_user,
import_upload_path,
params[:path]).execute
end
private
def import_upload_path
@import_upload_path ||= Gitlab::ImportExport
.import_upload_path(filename: "#{params[:namespace_id]}-#{params[:path]}")
end
def file
params[:file]
end
end
end
.col-sm-12.template-buttons
- Gitlab::ProjectTemplate.all.each do |template|
-# The title should be the value posted to the controller, a pretty name to print would be
-# template.name
= template.title
= image_tag(template.logo_path)
= f.text_field :template_title, placeholder: "rails", class: "form-control", tabindex: 2, autofocus: true, required: true
Loading
Loading
@@ -17,36 +17,17 @@
Create or Import your project from popular Git services
.col-lg-9
= form_for @project, html: { class: 'new_project' } do |f|
.row
.form-group.col-xs-12.col-sm-6
= f.label :namespace_id, class: 'label-light' do
%span
Project path
.form-group
.input-group
- if current_user.can_select_namespace?
.input-group-addon
= root_url
= f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1}
- else
.input-group-addon.static-namespace
#{root_url}#{current_user.username}/
= f.hidden_field :namespace_id, value: current_user.namespace_id
.form-group.col-xs-12.col-sm-6.project-path
= f.label :path, class: 'label-light' do
%span
Project name
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
- if current_user.can_create_group?
.help-block
Want to house several dependent projects under the same namespace?
= link_to "Create a group", new_group_path
.project-template.js-toggle-container
.form_group.clearfix
= f.label :template_project, class: 'label-light' do
Start from template
.col-sm-12.import-buttons
= render 'project_templates', f: f
 
- if import_sources_enabled?
.project-import.js-toggle-container
.form-group.clearfix
= f.label :visibility_level, class: 'label-light' do
= f.label :visibility_level, class: 'label-light' do #the label here seems wrong
Import project from
.col-sm-12.import-buttons
%div
Loading
Loading
@@ -90,6 +71,32 @@
.js-toggle-content.hide
= render "shared/import_form", f: f
 
.row
.form-group.col-xs-12.col-sm-6
= f.label :namespace_id, class: 'label-light' do
%span
Project path
.form-group
.input-group
- if current_user.can_select_namespace?
.input-group-addon
= root_url
= f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace', tabindex: 1}
- else
.input-group-addon.static-namespace
#{root_url}#{current_user.username}/
= f.hidden_field :namespace_id, value: current_user.namespace_id
.form-group.col-xs-12.col-sm-6.project-path
= f.label :path, class: 'label-light' do
%span
Project name
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true
- if current_user.can_create_group?
.help-block
Want to house several dependent projects under the same namespace?
= link_to "Create a group", new_group_path
.form-group
= f.label :description, class: 'label-light' do
Project description
Loading
Loading
Loading
Loading
@@ -15,7 +15,9 @@ module Gitlab
end
 
def import_upload_path(filename:)
File.join(storage_path, 'uploads', filename)
milliseconds = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
File.join(storage_path, 'uploads', "#{millisecond}-#{filename}")
end
 
def project_filename
Loading
Loading
module Gitlab
class ProjectTemplate
attr_reader :title, :name
def initialize(name, title)
@name, @title = name, title
end
def logo_path
"project_templates/#{name}.png"
end
def file
template_archive.open
end
def template_archive
Rails.root.join("vendor/project_templates/#{name}.tar.gz")
end
def ==(other)
name == other.name && title == other.title
end
TemplatesTable = [
ProjectTemplate.new('rails', 'Ruby on Rails')
].freeze
class << self
def all
TemplatesTable
end
def find(name)
all.find { |template| template.name == name.to_s }
end
end
end
end
require 'spec_helper'
 
feature 'Project', feature: true do
describe 'creating from template' do
let(:user) { create(:user) }
let(:template) { Gitlab::ProjectTemplate.find(:rails) }
before do
sign_in user
visit new_project_path
end
it "allows creation from the #{template.name} template" do
fill_in("project_template_title", with: template.title)
fill_in("project_path", with: template.name)
page.within '#content-body' do
click_button "Create project"
end
expect(page).to have_content 'Import'
end
end
describe 'description' do
let(:project) { create(:project, :repository) }
let(:path) { project_path(project) }
Loading
Loading
require 'spec_helper'
describe Gitlab::ProjectTemplate do
describe '.all' do
it 'returns a all templates' do
expected = [
described_class.new('rails', 'Ruby on Rails')
]
expect(described_class.all).to be_an(Array)
expect(described_class.all).to eq(expected)
end
end
describe '.find' do
subject { described_class.find(query) }
context 'when there is a match' do
let(:query) { :rails }
it { is_expected.to be_a(described_class) }
end
context 'when there is no match' do
let(:query) { 'no-match' }
it { is_expected.to be(nil) }
end
end
describe 'instance methods' do
subject { described_class.new('phoenix', 'Phoenix Framework') }
it { is_expected.to respond_to(:logo_path, :file, :template_archive) }
end
describe 'validate all templates' do
described_class.all.each do |template|
it "#{template.name} has a valid archive" do
archive = template.template_archive
logo = Rails.root.join("app/assets/images/#{template.logo_path}")
expect(File.exist?(archive)).to be(true)
expect(File.exist?(logo)).to be(true)
end
end
end
end
require 'spec_helper'
describe Projects::CreateFromTemplateService do
let(:user) { create(:user) }
let(:project_params) do
{
path: user.to_param,
template_title: 'rails'
}
end
subject { described_class.new(user, project_params) }
it 'calls the importer service' do
expect_any_instance_of(Projects::GitlabProjectsImporterService).to receive(:execute)
subject.execute
end
it 'returns the project thats created' do
project = subject.execute
expect(project).to be_saved
expect(project.import_status).to eq('scheduled')
end
end
File added
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment