Commit 5c6b1b47 authored by Thong Kuah's avatar Thong Kuah

Merge branch 'sh-use-template-project-id-backend' into 'master'

Add backend support for selecting custom templates by ID

See merge request gitlab-org/gitlab!18178
parents e526ee6e f7952cf7
......@@ -376,6 +376,7 @@ class ProjectsController < Projects::ApplicationController
:tag_list,
:visibility_level,
:template_name,
:template_project_id,
:merge_method,
:initialize_with_readme,
......
......@@ -4,8 +4,11 @@ module Projects
class CreateFromTemplateService < BaseService
include Gitlab::Utils::StrongMemoize
attr_reader :template_name
def initialize(user, params)
@current_user, @params = user, params.to_h.dup
@template_name = @params.delete(:template_name).presence
end
def execute
......@@ -21,12 +24,6 @@ module Projects
file&.close
end
def template_name
strong_memoize(:template_name) do
params.delete(:template_name).presence
end
end
private
def validate_template!
......
......@@ -13,7 +13,7 @@ module Projects
end
def execute
if @params[:template_name].present?
if create_from_template?
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
......@@ -184,6 +184,10 @@ module Projects
private
def create_from_template?
@params[:template_name].present? || @params[:template_project_id].present?
end
def import_schedule
if @project.errors.empty?
@project.import_state.schedule if @project.import? && !@project.bare_repository_import?
......
---
title: Add backend support for selecting custom templates by ID
merge_request: 18178
author:
type: fixed
......@@ -182,12 +182,20 @@ module EE
email_opted_in_source_id == EMAIL_OPT_IN_SOURCE_ID_GITLAB_COM ? 'GitLab.com' : ''
end
def available_custom_project_templates(search: nil, subgroup_id: nil)
def available_custom_project_templates(search: nil, subgroup_id: nil, project_id: nil)
templates = ::Gitlab::CurrentSettings.available_custom_project_templates(subgroup_id)
params = {}
if project_id
templates = templates.where(id: project_id)
else
params = { search: search, sort: 'name_asc' }
end
::ProjectsFinder.new(current_user: self,
project_ids_relation: templates,
params: { search: search, sort: 'name_asc' })
params: params)
.execute
end
......
......@@ -6,6 +6,16 @@ module EE
extend ::Gitlab::Utils::Override
include ::Gitlab::Utils::StrongMemoize
attr_reader :template_project_id, :subgroup_id
override :initialize
def initialize(user, params)
super
@template_project_id = @params.delete(:template_project_id).to_i if @params[:template_project_id].present?
@subgroup_id = @params.delete(:group_with_project_templates_id).presence
end
override :execute
def execute
return super unless use_custom_template?
......@@ -27,13 +37,19 @@ module EE
return true if template_project.present?
project.errors.add(:template_name, _("'%{template_name}' is unknown or invalid" % { template_name: template_name }))
if template_project_id.present?
project.errors.add(:template_project_id,
_("%{template_project_id} is unknown or invalid" % { template_project_id: template_project_id }))
else
project.errors.add(:template_name, _("'%{template_name}' is unknown or invalid" % { template_name: template_name }))
end
false
end
def use_custom_template?
strong_memoize(:use_custom_template) do
template_name &&
template_requested? &&
::Gitlab::Utils.to_boolean(params.delete(:use_custom_template)) &&
::Gitlab::CurrentSettings.custom_project_templates_enabled?
end
......@@ -41,13 +57,19 @@ module EE
def template_project
strong_memoize(:template_project) do
current_user.available_custom_project_templates(search: template_name, subgroup_id: subgroup_id)
.first
templates =
if template_project_id.present?
current_user.available_custom_project_templates(project_id: template_project_id, subgroup_id: subgroup_id)
else
current_user.available_custom_project_templates(search: template_name, subgroup_id: subgroup_id)
end
templates.first
end
end
def subgroup_id
@subgroup_id ||= params.delete(:group_with_project_templates_id).presence
def template_requested?
template_name.present? || template_project_id.is_a?(Integer)
end
# rubocop: disable CodeReuse/ActiveRecord
......
......@@ -12,7 +12,7 @@ module EE
mirror_user_id = current_user.id if mirror
mirror_trigger_builds = params.delete(:mirror_trigger_builds)
ci_cd_only = ::Gitlab::Utils.to_boolean(params.delete(:ci_cd_only))
group_with_project_templates_id = params.delete(:group_with_project_templates_id) if params[:template_name].blank?
group_with_project_templates_id = params.delete(:group_with_project_templates_id) if params[:template_name].blank? && params[:template_project_id].blank?
project = super do |project|
# Repository size limit comes as MB from the view
......
......@@ -302,6 +302,7 @@ describe User do
let!(:private_project) { create :project, :private, namespace: group, name: 'private_project' }
let!(:internal_project) { create :project, :internal, namespace: group, name: 'internal_project' }
let!(:public_project) { create :project, :public, namespace: group, name: 'public_project' }
let!(:public_project_two) { create :project, :public, namespace: group, name: 'public_project_second' }
it 'returns public projects' do
expect(user.available_custom_project_templates).to include public_project
......@@ -332,9 +333,27 @@ describe User do
it 'allows to search available project templates by name' do
projects = user.available_custom_project_templates(search: 'publi')
expect(projects.count).to eq 1
expect(projects.count).to eq 2
expect(projects.first).to eq public_project
end
it 'filters by project ID' do
projects = user.available_custom_project_templates(project_id: public_project.id)
expect(projects.count).to eq 1
expect(projects).to match_array([public_project])
projects = user.available_custom_project_templates(project_id: [public_project.id, public_project_two.id])
expect(projects.count).to eq 2
expect(projects).to match_array([public_project, public_project_two])
end
it 'does not return inaccessible projects' do
projects = user.available_custom_project_templates(project_id: private_project.id)
expect(projects.count).to eq 0
end
end
end
end
......
......@@ -16,8 +16,10 @@ require 'spec_helper'
# Group 2
describe Projects::CreateFromTemplateService do
using RSpec::Parameterized::TableSyntax
let(:group) { create(:group) }
let(:project) { create(:project, :public, namespace: group) }
let!(:project) { create(:project, :public, namespace: group) }
let(:user) { create(:user) }
let(:project_name) { project.name }
let(:use_custom_template) { true }
......@@ -30,13 +32,14 @@ describe Projects::CreateFromTemplateService do
let(:subgroup_2) { create(:group, parent: group) }
let(:subgroup_2_1) { create(:group, parent: subgroup_2) }
let(:project_template) { create(:project, :public, namespace: subgroup_1_2) }
let(:template_name) { project_template.name }
let(:namespace_id) { nil }
let(:group_with_project_templates_id) { nil }
let(:project_params) do
{
path: user.to_param,
template_name: project_name,
template_name: template_name,
description: 'project description',
visibility_level: Gitlab::VisibilityLevel::PUBLIC,
use_custom_template: use_custom_template,
......@@ -49,7 +52,7 @@ describe Projects::CreateFromTemplateService do
before do
stub_licensed_features(custom_project_templates: true)
stub_ee_application_setting(custom_project_templates_group_id: group.id)
stub_ee_application_setting(custom_project_templates_group_id: subgroup_1_2.id)
end
context '#execute' do
......@@ -80,7 +83,7 @@ describe Projects::CreateFromTemplateService do
end
context 'when custom_project_template does not exist' do
let(:project_name) { 'whatever' }
let(:template_name) { 'whatever' }
it 'does not attempt to import a project' do
expect(::Projects::GitlabProjectsImportService).not_to receive(:new)
......@@ -88,10 +91,18 @@ describe Projects::CreateFromTemplateService do
end
end
shared_examples 'creates project from custom template' do |subgroup_id|
# If we move the project inside a let block it throws a SEGFAULT error
where(:use_template_name) { [true, false] }
with_them do
before do
project_params[:group_with_project_templates_id] = subgroup_id
if use_template_name
project_params[:template_name] = template_name
project_params.delete(:template_project_id)
else
project_params.delete(:template_name)
project_params[:template_project_id] = project_template.id
end
@project = subject.execute
end
......@@ -109,97 +120,94 @@ describe Projects::CreateFromTemplateService do
expect(@project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC)
end
end
end
it_behaves_like 'creates project from custom template', nil
it_behaves_like 'creates project from custom template', ''
describe 'creating project from a Group project template' do
let(:project_name) { project_template.name }
let(:group_with_project_templates_id) { subgroup_1_2.id }
let(:group2) { create(:group) }
describe 'creating project from a Group project template' do
let(:project_name) { project_template.name }
let(:group_with_project_templates_id) { subgroup_1_2.id }
let(:group2) { create(:group) }
before do
subgroup_1.update!(custom_project_templates_group_id: subgroup_1_2.id)
group.add_maintainer(user)
group2.add_maintainer(user)
end
before do
subgroup_1.update!(custom_project_templates_group_id: subgroup_1_2.id)
group.add_maintainer(user)
group2.add_maintainer(user)
end
shared_examples 'a persisted project' do
it "is persisted" do
project = subject.execute
shared_examples 'a persisted project' do
it "is persisted" do
project = subject.execute
expect(project).to be_saved
expect(project.import_scheduled?).to be(true)
expect(project).to be_saved
expect(project.import_scheduled?).to be(true)
end
end
end
shared_examples 'a project that isn\'t persisted' do
it "isn't persisted" do
project = subject.execute
shared_examples 'a project that isn\'t persisted' do
it "isn't persisted" do
project = subject.execute
expect(project).not_to be_saved
expect(project.repository.empty?).to eq(true)
expect(project).not_to be_saved
expect(project.repository.empty?).to eq(true)
end
end
end
context 'when the namespace is not a descendant of the Group owning the template' do
context 'when project is created under a group that is outside the hierarchy its root ancestor group' do
let(:namespace_id) { group2.id }
context 'when the namespace is not a descendant of the Group owning the template' do
context 'when project is created under a group that is outside the hierarchy its root ancestor group' do
let(:namespace_id) { group2.id }
it_behaves_like 'a project that isn\'t persisted'
end
it_behaves_like 'a project that isn\'t persisted'
end
context 'when project is created under a group that is a descendant of its root ancestor group' do
let(:namespace_id) { subgroup_2.id }
context 'when project is created under a group that is a descendant of its root ancestor group' do
let(:namespace_id) { subgroup_2.id }
it_behaves_like 'a project that isn\'t persisted'
end
it_behaves_like 'a project that isn\'t persisted'
end
context 'when project is created under a subgroup that is a descendant of its root ancestor group' do
let(:namespace_id) { subgroup_2_1.id }
context 'when project is created under a subgroup that is a descendant of its root ancestor group' do
let(:namespace_id) { subgroup_2_1.id }
it_behaves_like 'a project that isn\'t persisted'
end
it_behaves_like 'a project that isn\'t persisted'
end
context 'when project is created outside of group hierarchy' do
let(:user) { create(:user) }
let(:project) { create(:project, :public, namespace: user.namespace) }
let(:namespace_id) { user.namespace_id }
context 'when project is created outside of group hierarchy' do
let(:user) { create(:user) }
let(:project) { create(:project, :public, namespace: user.namespace) }
let(:namespace_id) { user.namespace_id }
it_behaves_like 'a project that isn\'t persisted'
it_behaves_like 'a project that isn\'t persisted'
end
end
end
context 'when the namespace is inside the hierarchy of the Group owning the template' do
context 'when project is created under its parent group' do
let(:namespace_id) { subgroup_1.id }
context 'when the namespace is inside the hierarchy of the Group owning the template' do
context 'when project is created under its parent group' do
let(:namespace_id) { subgroup_1.id }
it_behaves_like 'a persisted project'
end
it_behaves_like 'a persisted project'
end
context 'when project is created under the same group' do
let(:namespace_id) { subgroup_1_2.id }
context 'when project is created under the same group' do
let(:namespace_id) { subgroup_1_2.id }
it_behaves_like 'a persisted project'
end
it_behaves_like 'a persisted project'
end
context 'when project is created under its descendant group' do
let(:namespace_id) { subgroup_1_2_1.id }
context 'when project is created under its descendant group' do
let(:namespace_id) { subgroup_1_2_1.id }
it_behaves_like 'a persisted project'
end
it_behaves_like 'a persisted project'
end
context 'when project is created under a group that is a descendant of its parent group' do
let(:namespace_id) { subgroup_1_1.id }
context 'when project is created under a group that is a descendant of its parent group' do
let(:namespace_id) { subgroup_1_1.id }
it_behaves_like 'a persisted project'
end
it_behaves_like 'a persisted project'
end
context 'when project is created under a subgroup that is a descendant of its parent group' do
let(:namespace_id) { subgroup_1_1_1.id }
context 'when project is created under a subgroup that is a descendant of its parent group' do
let(:namespace_id) { subgroup_1_1_1.id }
it_behaves_like 'a persisted project'
it_behaves_like 'a persisted project'
end
end
end
end
......
......@@ -13,6 +13,20 @@ describe Projects::CreateService, '#execute' do
}
end
context 'with a template project ID' do
before do
opts.merge!(
template_project_id: 1
)
end
it 'creates a project using the template service' do
expect(::Projects::CreateFromTemplateService).to receive_message_chain(:new, :execute)
create_project(user, opts)
end
end
context 'with a CI/CD only project' do
before do
opts.merge!(
......
......@@ -358,6 +358,9 @@ msgstr[1] ""
msgid "%{tabname} changed"
msgstr ""
msgid "%{template_project_id} is unknown or invalid"
msgstr ""
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
msgstr[0] ""
......
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