Commit 30765c81 authored by Michael Kozono's avatar Michael Kozono

Merge branch 'group-project-template-in-paid-features-ee' into 'master'

Group project templates should be a Silver/Premium feature

Closes #10926

See merge request gitlab-org/gitlab-ee!10678
parents fe2aec39 5c68cc3f
......@@ -36,10 +36,10 @@ class ProjectsController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def new
namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
return access_denied! if namespace && !can?(current_user, :create_projects, namespace)
@namespace = Namespace.find_by(id: params[:namespace_id]) if params[:namespace_id]
return access_denied! if @namespace && !can?(current_user, :create_projects, @namespace)
@project = Project.new(namespace_id: namespace&.id)
@project = Project.new(namespace_id: @namespace&.id)
end
# rubocop: enable CodeReuse/ActiveRecord
......
......@@ -27,7 +27,7 @@ module EE
].tap do |params_ee|
params_ee << { insight_attributes: :project_id } if current_group&.insights_available?
params_ee << :file_template_project_id if current_group&.feature_available?(:custom_file_templates_for_namespace)
params_ee << :custom_project_templates_group_id if License.feature_available?(:custom_project_templates)
params_ee << :custom_project_templates_group_id if current_group&.group_project_template_available?
end
end
......
# frozen_string_literal: true
class GroupsWithTemplatesFinder
# We need to provide grace period for users who are now using group_project_template
# feature in free groups.
CUT_OFF_DATE = Date.parse('2019/05/22') + 3.months
def initialize(group_id = nil)
@group_id = group_id
end
def execute
groups = @group_id ? ::Group.find(group_id).self_and_ancestors : ::Group.all
groups = groups.with_project_templates
if ::Gitlab::CurrentSettings.should_check_namespace_plan? && Time.zone.now > CUT_OFF_DATE
groups = groups.with_feature_available_in_plan(:group_project_templates)
end
groups
end
private
attr_reader :group_id
end
......@@ -166,6 +166,11 @@ module EE
feature_available?(:multiple_group_issue_boards)
end
def group_project_template_available?
feature_available?(:group_project_templates) ||
(custom_project_templates_group_id? && Time.zone.now <= GroupsWithTemplatesFinder::CUT_OFF_DATE)
end
def actual_size_limit
return ::Gitlab::CurrentSettings.repository_size_limit if repository_size_limit.nil?
......
......@@ -38,6 +38,14 @@ module EE
scope :with_plan, -> { where.not(plan_id: nil) }
scope :with_shared_runners_minutes_limit, -> { where("namespaces.shared_runners_minutes_limit > 0") }
scope :with_extra_shared_runners_minutes_limit, -> { where("namespaces.extra_shared_runners_minutes_limit > 0") }
scope :with_feature_available_in_plan, -> (feature) do
plans = plans_with_feature(feature)
matcher = Plan.where(name: plans)
.joins(:hosted_subscriptions)
.where("gitlab_subscriptions.namespace_id = namespaces.id")
.select('1')
where("EXISTS (?)", matcher)
end
delegate :shared_runners_minutes, :shared_runners_seconds, :shared_runners_seconds_last_reset,
:extra_shared_runners_minutes, to: :namespace_statistics, allow_nil: true
......
......@@ -172,11 +172,11 @@ module EE
end
def available_subgroups_with_custom_project_templates(group_id = nil)
groups = group_id ? ::Group.find(group_id).self_and_ancestors : ::Group.all
groups = GroupsWithTemplatesFinder.new(group_id).execute
GroupsFinder.new(self, min_access_level: ::Gitlab::Access::MAINTAINER)
.execute
.where(id: groups.with_project_templates.select(:custom_project_templates_group_id))
.where(id: groups.select(:custom_project_templates_group_id))
.includes(:projects)
.reorder(nil)
.distinct
......
......@@ -69,6 +69,7 @@ class License < ApplicationRecord
ci_cd_projects
protected_environments
custom_project_templates
group_project_templates
packages
code_owner_approval_required
feature_flags
......
- return unless ::Gitlab::CurrentSettings.custom_project_templates_enabled?
- return unless @group.group_project_template_available?
- expanded = expanded_by_default?
%section.settings.no-animate.qa-custom-project-templates{ class: ('expanded' if expanded) }
......
---
title: Ensure custom group template feature is available only for groups on gold and silver
merge_request: 10678
author:
type: fixed
......@@ -188,7 +188,7 @@ describe 'Edit group settings' do
context 'is enabled' do
before do
stub_licensed_features(custom_project_templates: true)
stub_licensed_features(group_project_templates: true)
visit edit_group_path(selected_group)
end
......@@ -207,9 +207,54 @@ describe 'Edit group settings' do
end
end
context 'namespace plan is checked' do
before do
create(:gitlab_subscription, namespace: group, hosted_plan: plan)
stub_licensed_features(group_project_templates: true)
allow(Gitlab::CurrentSettings.current_application_settings)
.to receive(:should_check_namespace_plan?) { true }
visit edit_group_path(selected_group)
end
context 'namespace is on the proper plan' do
let(:plan) { create(:gold_plan) }
context 'when the group is a top parent group' do
let(:selected_group) { group }
let(:nested_group) { subgroup }
it_behaves_like 'shows custom project templates settings'
end
context 'when the group is a subgroup' do
let(:selected_group) { subgroup }
let(:nested_group) { subgroup_1 }
it_behaves_like 'shows custom project templates settings'
end
end
context 'is disabled for namespace' do
let(:plan) { create(:bronze_plan) }
context 'when the group is the top parent group' do
let(:selected_group) { group }
it_behaves_like 'does not show custom project templates settings'
end
context 'when the group is a subgroup' do
let(:selected_group) { subgroup }
it_behaves_like 'does not show custom project templates settings'
end
end
end
context 'is disabled' do
before do
stub_licensed_features(custom_project_templates: false)
stub_licensed_features(group_project_templates: false)
visit edit_group_path(selected_group)
end
......
......@@ -192,7 +192,7 @@ describe 'New project' do
context 'when licensed' do
before do
stub_licensed_features(custom_project_templates: true)
stub_licensed_features(custom_project_templates: true, group_project_templates: true)
end
it 'shows Group tab in Templates section' do
......@@ -293,6 +293,7 @@ describe 'New project' do
before do
group1.add_owner(user)
group2.add_owner(user)
group3.add_owner(user)
group4.add_owner(user)
group1.update(custom_project_templates_group_id: subgroup1.id)
group2.update(custom_project_templates_group_id: subgroup2.id)
......@@ -354,6 +355,82 @@ describe 'New project' do
end
end
end
context 'when namespace is supposed to be checked' do
context 'when in proper plan' do
context 'when creating project from top-level group with templates' do
let(:url) { new_project_path(namespace_id: group1.id) }
before do
allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?) { true }
create(:gitlab_subscription, :gold, namespace: group1)
end
it 'show Group tab in Templates section' do
visit url
click_link 'Create from template'
expect(page).to have_css('.custom-group-project-templates-tab')
end
it_behaves_like 'group templates displayed' do
let(:template_number) { 2 }
end
end
context 'when creating project with templates' do
let(:url) { new_project_path(namespace_id: group1.id) }
before do
allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?) { true }
create(:gitlab_subscription, :bronze, namespace: group1)
end
around do |example|
Timecop.freeze(GroupsWithTemplatesFinder::CUT_OFF_DATE - 1.day) do
example.run
end
end
it 'show Group tab in Templates section' do
visit url
click_link 'Create from template'
expect(page).to have_css('.custom-group-project-templates-tab')
end
it_behaves_like 'group templates displayed' do
let(:template_number) { 2 }
end
end
end
context 'when creating project with templates after grace period' do
let(:url) { new_project_path(namespace_id: group1.id) }
before do
stub_application_setting(check_namespace_plan: true)
create(:gitlab_subscription, :bronze, namespace: group1)
end
around do |example|
Timecop.freeze(GroupsWithTemplatesFinder::CUT_OFF_DATE + 1.day) do
example.run
end
end
it 'show Group tab in Templates section' do
visit url
click_link 'Create from template'
expect(page).to have_css('.custom-group-project-templates-tab')
end
it_behaves_like 'group templates displayed' do
let(:template_number) { 0 }
end
end
end
end
context 'when group template is not set' do
......
# frozen_string_literal: true
require 'spec_helper'
describe GroupsWithTemplatesFinder do
let(:group_1) { create(:group, name: 'group-1') }
let(:group_2) { create(:group, name: 'group-2') }
let(:group_3) { create(:group, name: 'group-3') }
let!(:group_4) { create(:group, name: 'group-4') }
let!(:subgroup_1) { create(:group, parent: group_1, name: 'subgroup-1') }
let!(:subgroup_2) { create(:group, parent: group_2, name: 'subgroup-2') }
let!(:subgroup_3) { create(:group, parent: group_3, name: 'subgroup-3') }
before do
group_1.update!(custom_project_templates_group_id: subgroup_1.id)
group_2.update!(custom_project_templates_group_id: subgroup_2.id)
group_3.update!(custom_project_templates_group_id: subgroup_3.id)
create(:project, namespace: subgroup_1)
create(:project, namespace: subgroup_2)
create(:project, namespace: subgroup_3)
create(:gitlab_subscription, :gold, namespace: group_1)
create(:gitlab_subscription, :silver, namespace: group_2)
end
describe 'without group id' do
it 'returns all groups' do
expect(described_class.new.execute).to contain_exactly(group_1, group_2, group_3)
end
context 'when namespace checked' do
before do
allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?) { true }
end
it 'returns all groups before cut-off date' do
Timecop.freeze(described_class::CUT_OFF_DATE - 1.day) do
expect(described_class.new.execute).to contain_exactly(group_1, group_2, group_3)
end
end
it 'returns groups on gold/silver plan after cut-off date' do
Timecop.freeze(described_class::CUT_OFF_DATE + 1.day) do
expect(described_class.new.execute).to contain_exactly(group_1, group_2)
end
end
end
end
describe 'with group id' do
it 'returns given group with it descendants' do
expect(described_class.new(group_1.id).execute).to contain_exactly(group_1)
end
context 'when namespace checked' do
before do
allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?) { true }
end
it 'returns given group with it descendants before cut-off date' do
Timecop.freeze(described_class::CUT_OFF_DATE - 1.day) do
expect(described_class.new(group_3.id).execute).to contain_exactly(group_3)
end
end
it 'does not return the group after the cut-off date' do
Timecop.freeze(described_class::CUT_OFF_DATE + 1.day) do
expect(described_class.new(group_3.id).execute).to be_empty
end
end
end
end
end
......@@ -289,78 +289,135 @@ describe Group do
end
describe 'Vulnerabilities::Occurrence collection methods' do
let(:project) { create(:project, namespace: group) }
let(:external_project) { create(:project) }
let(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) }
let!(:old_vuln) { create_vulnerability(project) }
let!(:new_vuln) { create_vulnerability(project) }
let!(:external_vuln) { create_vulnerability(external_project) }
let!(:failed_vuln) { create_vulnerability(project, failed_pipeline) }
def create_vulnerability(project, pipeline = nil)
pipeline ||= create(:ci_pipeline, :success, project: project)
create(:vulnerabilities_occurrence, pipelines: [pipeline], project: project)
end
describe '#latest_vulnerabilities' do
subject { group.latest_vulnerabilities }
let(:project) { create(:project, namespace: group) }
let(:external_project) { create(:project) }
let(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) }
it 'returns vulns only for the latest successful pipelines of projects belonging to the group' do
is_expected.to contain_exactly(new_vuln)
let!(:old_vuln) { create_vulnerability(project) }
let!(:new_vuln) { create_vulnerability(project) }
let!(:external_vuln) { create_vulnerability(external_project) }
let!(:failed_vuln) { create_vulnerability(project, failed_pipeline) }
def create_vulnerability(project, pipeline = nil)
pipeline ||= create(:ci_pipeline, :success, project: project)
create(:vulnerabilities_occurrence, pipelines: [pipeline], project: project)
end
context 'with vulnerabilities from other branches' do
let!(:branch_pipeline) { create(:ci_pipeline, :success, project: project, ref: 'feature-x') }
let!(:branch_vuln) { create(:vulnerabilities_occurrence, pipelines: [branch_pipeline], project: project) }
describe '#latest_vulnerabilities' do
subject { group.latest_vulnerabilities }
# TODO: This should actually fail and we must scope vulns
# per branch as soon as we store them for other branches
# Dependent on https://gitlab.com/gitlab-org/gitlab-ee/issues/9524
it 'includes vulnerabilities from all branches' do
is_expected.to contain_exactly(branch_vuln)
it 'returns vulns only for the latest successful pipelines of projects belonging to the group' do
is_expected.to contain_exactly(new_vuln)
end
context 'with vulnerabilities from other branches' do
let!(:branch_pipeline) { create(:ci_pipeline, :success, project: project, ref: 'feature-x') }
let!(:branch_vuln) { create(:vulnerabilities_occurrence, pipelines: [branch_pipeline], project: project) }
# TODO: This should actually fail and we must scope vulns
# per branch as soon as we store them for other branches
# Dependent on https://gitlab.com/gitlab-org/gitlab-ee/issues/9524
it 'includes vulnerabilities from all branches' do
is_expected.to contain_exactly(branch_vuln)
end
end
end
end
describe '#latest_vulnerabilities_with_sha' do
subject { group.latest_vulnerabilities_with_sha }
describe '#latest_vulnerabilities_with_sha' do
subject { group.latest_vulnerabilities_with_sha }
it 'returns vulns only for the latest successful pipelines of projects belonging to the group' do
is_expected.to contain_exactly(new_vuln)
end
it { is_expected.to all(respond_to(:sha)) }
context 'with vulnerabilities from other branches' do
let!(:branch_pipeline) { create(:ci_pipeline, :success, project: project, ref: 'feature-x') }
let!(:branch_vuln) { create(:vulnerabilities_occurrence, pipelines: [branch_pipeline], project: project) }
it 'returns vulns only for the latest successful pipelines of projects belonging to the group' do
is_expected.to contain_exactly(new_vuln)
# TODO: This should actually fail and we must scope vulns
# per branch as soon as we store them for other branches
# Dependent on https://gitlab.com/gitlab-org/gitlab-ee/issues/9524
it 'includes vulnerabilities from all branches' do
is_expected.to contain_exactly(branch_vuln)
end
end
end
it { is_expected.to all(respond_to(:sha)) }
describe '#all_vulnerabilities' do
subject { group.all_vulnerabilities }
context 'with vulnerabilities from other branches' do
let!(:branch_pipeline) { create(:ci_pipeline, :success, project: project, ref: 'feature-x') }
let!(:branch_vuln) { create(:vulnerabilities_occurrence, pipelines: [branch_pipeline], project: project) }
it 'returns vulns for all successful pipelines of projects belonging to the group' do
is_expected.to contain_exactly(old_vuln, new_vuln)
end
# TODO: This should actually fail and we must scope vulns
# per branch as soon as we store them for other branches
# Dependent on https://gitlab.com/gitlab-org/gitlab-ee/issues/9524
it 'includes vulnerabilities from all branches' do
is_expected.to contain_exactly(branch_vuln)
context 'with vulnerabilities from other branches' do
let!(:branch_pipeline) { create(:ci_pipeline, :success, project: project, ref: 'feature-x') }
let!(:branch_vuln) { create(:vulnerabilities_occurrence, pipelines: [branch_pipeline], project: project) }
# TODO: This should actually fail and we must scope vulns
# per branch as soon as we store them for other branches
# Dependent on https://gitlab.com/gitlab-org/gitlab-ee/issues/9524
it 'includes vulnerabilities from all branches' do
is_expected.to contain_exactly(old_vuln, new_vuln, branch_vuln)
end
end
end
end
end
describe '#group_project_template_available?' do
subject { group.group_project_template_available? }
context 'licensed' do
before do
stub_licensed_features(group_project_templates: true)
end
it 'returns true for licensed instance' do
is_expected.to be true
end
context 'when in need of checking plan' do
before do
allow(Gitlab::CurrentSettings.current_application_settings)
.to receive(:should_check_namespace_plan?) { true }
end
it 'returns true for groups in proper plan' do
create(:gitlab_subscription, namespace: group, hosted_plan: create(:gold_plan))
describe '#all_vulnerabilities' do
subject { group.all_vulnerabilities }
is_expected.to be true
end
it 'returns true for groups with group template already set within grace period' do
group.update!(custom_project_templates_group_id: create(:group, parent: group).id)
group.reload
Timecop.freeze(GroupsWithTemplatesFinder::CUT_OFF_DATE - 1.day) do
is_expected.to be true
end
end
it 'returns false for groups with group template already set after grace period' do
group.update!(custom_project_templates_group_id: create(:group, parent: group).id)
group.reload
it 'returns vulns for all successful pipelines of projects belonging to the group' do
is_expected.to contain_exactly(old_vuln, new_vuln)
Timecop.freeze(GroupsWithTemplatesFinder::CUT_OFF_DATE + 1.day) do
is_expected.to be false
end
end
end
context 'with vulnerabilities from other branches' do
let!(:branch_pipeline) { create(:ci_pipeline, :success, project: project, ref: 'feature-x') }
let!(:branch_vuln) { create(:vulnerabilities_occurrence, pipelines: [branch_pipeline], project: project) }
context 'unlicensed' do
before do
stub_licensed_features(group_project_templates: false)
end
# TODO: This should actually fail and we must scope vulns
# per branch as soon as we store them for other branches
# Dependent on https://gitlab.com/gitlab-org/gitlab-ee/issues/9524
it 'includes vulnerabilities from all branches' do
is_expected.to contain_exactly(old_vuln, new_vuln, branch_vuln)
it 'returns false unlicensed instance' do
is_expected.to be false
end
end
end
......
......@@ -50,6 +50,28 @@ describe Namespace do
end
end
end
describe '.with_feature_available_in_plan' do
let!(:namespace) { create :namespace, plan: namespace_plan }
context 'plan is nil' do
let(:namespace_plan) { nil }
it 'returns no namespace' do
expect(described_class.with_feature_available_in_plan(:group_project_templates)).to be_empty
end
end
context 'plan is set' do
let(:namespace_plan) { :bronze_plan }
it 'returns namespaces with plan' do
create(:gitlab_subscription, :bronze, namespace: namespace)
create(:gitlab_subscription, :free, namespace: create(:namespace))
expect(described_class.with_feature_available_in_plan(:audit_events)).to eq([namespace])
end
end
end
end
describe 'custom validations' do
......
......@@ -402,6 +402,23 @@ describe User do
expect(groups.map(&:name)).not_to include('subgroup-3')
end
end
context 'when namespace plan is checked' do
before do
create(:gitlab_subscription, namespace: group_1, hosted_plan: create(:bronze_plan))
create(:gitlab_subscription, namespace: group_2, hosted_plan: create(:gold_plan))
allow(Gitlab::CurrentSettings).to receive(:should_check_namespace_plan?) { true }
end
it 'returns groups on gold or silver plans' do
Timecop.freeze(GroupsWithTemplatesFinder::CUT_OFF_DATE + 1.day) do
groups = user.available_subgroups_with_custom_project_templates
expect(groups.size).to eq(1)
expect(groups.map(&:name)).to include('subgroup-2')
end
end
end
end
end
end
......
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