Commit 5f1b71d1 authored by Phil Hughes's avatar Phil Hughes

Merge branch '6861-group-level-project-templates-ce' into 'master'

Backport from gitlab-org/gitlab-ee!6878

See merge request gitlab-org/gitlab-ce!23391
parents 93d6f569 fa7acd99
......@@ -5,6 +5,7 @@ import axios from './lib/utils/axios_utils';
const Api = {
groupsPath: '/api/:version/groups.json',
groupPath: '/api/:version/groups/:id',
subgroupsPath: '/api/:version/groups/:id/subgroups',
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json',
......
......@@ -10,13 +10,18 @@ export default function groupsSelect() {
const $select = $(this);
const allAvailable = $select.data('allAvailable');
const skipGroups = $select.data('skipGroups') || [];
const parentGroupID = $select.data('parentId');
const groupsPath = parentGroupID
? Api.subgroupsPath.replace(':id', parentGroupID)
: Api.groupsPath;
$select.select2({
placeholder: 'Search for a group',
allowClear: $select.hasClass('allowClear'),
multiple: $select.hasClass('multiselect'),
minimumInputLength: 0,
ajax: {
url: Api.buildUrl(Api.groupsPath),
url: Api.buildUrl(groupsPath),
dataType: 'json',
quietMillis: 250,
transport(params) {
......
......@@ -5,6 +5,7 @@ import initSettingsPanels from '~/settings_panels';
import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import { GROUP_BADGE } from '~/badges/constants';
import groupsSelect from '~/groups_select';
import projectSelect from '~/project_select';
document.addEventListener('DOMContentLoaded', () => {
......@@ -17,5 +18,8 @@ document.addEventListener('DOMContentLoaded', () => {
);
mountBadgeSettings(GROUP_BADGE);
// Initialize Subgroups selector
groupsSelect();
projectSelect();
});
......@@ -33,7 +33,11 @@
.bs-callout-warning {
background-color: $orange-100;
border-color: $orange-200;
color: $orange-700;
color: $orange-900;
a {
color: $orange-900;
}
}
.bs-callout-info {
......
......@@ -9,7 +9,7 @@ def initialize(user, params)
end
def execute
if @params[:template_name]&.present?
if @params[:template_name].present?
return ::Projects::CreateFromTemplateService.new(current_user, params).execute
end
......
......@@ -37,6 +37,7 @@
.settings-content
= render 'shared/badges/badge_settings'
= render_if_exists 'groups/custom_project_templates_setting'
= render_if_exists 'groups/templates_setting', expanded: expanded
%section.settings.gs-advanced.no-animate#js-advanced-settings{ class: ('expanded' if expanded) }
......
......@@ -50,7 +50,7 @@
.project-template
.form-group
%div
= render 'project_templates', f: f
= render 'project_templates', f: f, project: @project
.tab-pane.import-project-pane.js-toggle-container{ id: 'import-project-pane', class: active_when(active_tab == 'import'), role: 'tabpanel' }
- if import_sources_enabled?
......
......@@ -9,9 +9,9 @@
.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 }
%a.btn.btn-default.append-right-10{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: template.name } }
= _("Preview")
%label.btn.btn-success.template-button.choose-template.append-bottom-0{ for: template.name }
%input{ type: "radio", autocomplete: "off", name: "project[template_name]", id: template.name, value: template.name, data: { track_label: "create_from_template", track_property: "template_use", track_event: "click_button" } }
%span
= _("Use template")
%a.btn.btn-default{ href: template.preview, rel: 'noopener noreferrer', target: '_blank', data: { track_label: "create_from_template", track_property: "template_preview", track_event: "click_button", track_value: template.name } }
= _("Preview")
# Custom instance-level project templates **[PREMIUM ONLY]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in [GitLab Premium](https://about.gitlab.com/pricing) 11.2.
When you create a new project, creating it based on custom project templates is
a convenient option to bootstrap from an existing project boilerplate.
The administration setting to configure a GitLab group that serves as template
source can be found under **Admin > Settings > Custom project templates**.
Within this section, you can configure the group where all the custom project
templates are sourced. Every project directly under the group namespace will be
available to the user if they have access to them. For example, every public
project in the group will be available to every logged user. However,
private projects will be available only if the user has view [permissions](../permissions.md)
in the project:
- Project Owner, Maintainer, Developer, Reporter or Guest
- Is a member of the Group: Owner, Maintainer, Developer, Reporter or Guest
Projects below subgroups of the template group are **not** supported.
Repository and database information that are copied over to each new project are
identical to the data exported with [GitLab's Project Import/Export](../project/settings/import_export.md).
If you would like to set project templates at a group level, please see [Custom group-level project templates](../group/custom_project_templates.md).
\ No newline at end of file
# Custom group-level project templates **[PREMIUM ONLY]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6861) in [GitLab Premium](https://about.gitlab.com/pricing) 11.6.
When you create a new project, creating it based on custom project templates is
a convenient option to bootstrap from an existing project boilerplate.
The group-level setting to configure a GitLab group that serves as template
source can be found under **Group > Settings > General > Custom project templates**.
Within this section, you can configure the group where all the custom project
templates are sourced. Every project directly under the group namespace will be
available to the user if they have access to them. For example, every public
project in the group will be available to every logged in user. However,
private projects will be available only if the user has view [permissions](../permissions.md)
in the project. That is, users with Owner, Maintainer, Developer, Reporter or Guest roles for projects,
or for groups to which the project belongs.
Projects of nested subgroups of a selected template source cannot be used.
Repository and database information that are copied over to each new project are
identical to the data exported with [GitLab's Project Import/Export](../project/settings/import_export.md).
If you would like to set project templates at an instance level, please see [Custom instance-level project templates](../admin_area/custom_project_templates.md).
\ No newline at end of file
......@@ -259,6 +259,11 @@ types with every project in a group.
Learn more about [Group-level file templates](https://docs.gitlab.com/ee/user/group/index.html#group-level-file-templates-premium).
#### Group-level project templates **[PREMIUM]**
Define project templates at a group-level by setting a group as a template source.
[Learn more about group-level project templates](custom_project_templates.md).
### Advanced settings
- **Projects**: view all projects within that group, add members to each project,
......
......@@ -3,6 +3,7 @@
describe CleanUpForMembers, :migration do
let(:migration) { described_class.new }
let(:groups) { table(:namespaces) }
let!(:group_member) { create_group_member }
let!(:unbinded_group_member) { create_group_member }
let!(:invited_group_member) { create_group_member(true) }
......@@ -25,7 +26,7 @@
end
def create_group_member(invited = false)
fill_member(GroupMember.new(group: create_group), invited)
fill_member(GroupMember.new(source_id: create_group.id, source_type: 'Namespace'), invited)
end
def create_project_member(invited = false)
......@@ -54,7 +55,7 @@ def fill_member(member_object, invited)
def create_group
name = FFaker::Lorem.characters(10)
Group.create(name: name, path: name.downcase.gsub(/\s/, '_'))
groups.create!(type: 'Group', name: name, path: name.downcase.gsub(/\s/, '_'))
end
def create_project
......
......@@ -94,17 +94,18 @@
end
context 'for milestones (by group)' do
# milestones (by group) is a little different than all of the other models
let!(:group1) { create(:group) }
let!(:group2) { create(:group) }
let!(:group3) { create(:group) }
# milestones (by group) is a little different than most of the other models
let(:groups) { table(:namespaces) }
let(:group1) { groups.create(name: 'Group 1', type: 'Group', path: 'group_1') }
let(:group2) { groups.create(name: 'Group 2', type: 'Group', path: 'group_2') }
let(:group3) { groups.create(name: 'Group 2', type: 'Group', path: 'group_3') }
let(:internal_id_query) { ->(group) { InternalId.where(usage: InternalId.usages['milestones'], namespace: group) } }
before do
3.times { create(:milestone, group: group1) }
3.times { create(:milestone, group: group2) }
3.times { create(:milestone, group: group3) }
3.times { create(:milestone, group_id: group1.id) }
3.times { create(:milestone, group_id: group2.id) }
3.times { create(:milestone, group_id: group3.id) }
internal_id_query.call(group1).first.tap do |iid|
iid.last_value = iid.last_value - 2
......
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