Commit 46fa6920 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 40a1a83b 894ce317
......@@ -12,7 +12,7 @@ class Admin::IntegrationsController < Admin::ApplicationController
respond_to do |format|
format.json do
projects = Project.with_active_integration(integration.class).merge(::Integration.not_inherited)
projects = Project.with_active_integration(integration.class).merge(::Integration.with_custom_settings)
serializer = ::Integrations::ProjectSerializer.new.with_pagination(request, response)
render json: serializer.represent(projects)
......
......@@ -38,7 +38,7 @@ class Groups::EmailCampaignsController < Groups::ApplicationController
create_track_url
when :verify
project_pipelines_url(group.projects.first)
when :trial
when :trial, :trial_short
'https://about.gitlab.com/free-trial/'
when :team, :team_short
group_group_members_url(group)
......
......@@ -4,6 +4,9 @@ module Ci
class PendingBuild < Ci::ApplicationRecord
belongs_to :project
belongs_to :build, class_name: 'Ci::Build'
belongs_to :namespace, inverse_of: :pending_builds, class_name: 'Namespace'
validates :namespace, presence: true
scope :ref_protected, -> { where(protected: true) }
scope :queued_before, ->(time) { where(arel_table[:created_at].lt(time)) }
......@@ -20,7 +23,8 @@ module Ci
args = {
build: build,
project: build.project,
protected: build.protected?
protected: build.protected?,
namespace: build.project.namespace
}
if Feature.enabled?(:ci_pending_builds_maintain_shared_runners_data, type: :development, default_enabled: :yaml)
......@@ -54,3 +58,5 @@ module Ci
private_class_method :builds_access_level?
end
end
Ci::PendingBuild.prepend_mod_with('Ci::PendingBuild')
......@@ -77,8 +77,8 @@ class Integration < ApplicationRecord
scope :by_type, -> (type) { where(type: type) }
scope :by_active_flag, -> (flag) { where(active: flag) }
scope :inherit_from_id, -> (id) { where(inherit_from_id: id) }
scope :inherit, -> { where.not(inherit_from_id: nil) }
scope :not_inherited, -> { where(inherit_from_id: nil) }
scope :with_default_settings, -> { where.not(inherit_from_id: nil) }
scope :with_custom_settings, -> { where(inherit_from_id: nil) }
scope :for_group, -> (group) { where(group_id: group, type: available_integration_types(include_project_specific: false)) }
scope :for_instance, -> { where(instance: true, type: available_integration_types(include_project_specific: false)) }
......
......@@ -34,6 +34,7 @@ class Namespace < ApplicationRecord
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
has_many :pending_builds, class_name: 'Ci::PendingBuild'
has_one :onboarding_progress
# This should _not_ be `inverse_of: :namespace`, because that would also set
......
......@@ -20,7 +20,8 @@ module Users
trial: 2,
team: 3,
experience: 4,
team_short: 5
team_short: 5,
trial_short: 6
}, _suffix: true
scope :without_track_and_series, -> (track, series) do
......
......@@ -208,12 +208,12 @@ module Groups
end
def update_integrations
@group.integrations.inherit.delete_all
@group.integrations.with_default_settings.delete_all
Integration.create_from_active_default_integrations(@group, :group_id)
end
def propagate_integrations
@group.integrations.inherit.each do |integration|
@group.integrations.with_default_settings.each do |integration|
PropagateIntegrationWorker.perform_async(integration.id)
end
end
......
......@@ -13,8 +13,13 @@ module Namespaces
completed_actions: [:git_write],
incomplete_actions: [:user_added]
},
trial_short: {
interval_days: [2],
completed_actions: [:git_write],
incomplete_actions: [:trial_started]
},
verify: {
interval_days: [2, 6, 11],
interval_days: [3, 7, 12],
completed_actions: [:git_write],
incomplete_actions: [:pipeline_created]
},
......@@ -105,7 +110,7 @@ module Namespaces
case track
when :create, :verify
user.can?(:create_projects, group)
when :trial
when :trial, :trial_short
user.can?(:start_trial, group)
when :team, :team_short
user.can?(:admin_group_member, group)
......
......@@ -241,7 +241,7 @@ module Projects
end
def update_integrations
project.integrations.inherit.delete_all
project.integrations.with_default_settings.delete_all
Integration.create_from_active_default_integrations(project, :project_id)
end
end
......
---
name: ci_pending_builds_maintain_ci_minutes_data
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64443
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/332951
milestone: '14.2'
type: development
group: group::pipeline execution
default_enabled: false
......@@ -2,7 +2,7 @@
key_path: counts.in_product_marketing_email_team_short_0_cta_clicked
name: "count_clicks_on_the_first_email_of_the_team_short_track_for_in_product_marketing_emails"
description: Total clicks on the team_short track's first email
product_section:
product_section: growth
product_stage: growth
product_group: group::activation
product_category: onboarding
......
......@@ -2,7 +2,7 @@
key_path: counts.in_product_marketing_email_team_short_0_sent
name: "count_sent_first_email_of_the_team_short_track_for_in_product_marketing_emails"
description: Total sent emails of the team_short track's first email
product_section:
product_section: growth
product_stage: growth
product_group: group::activation
product_category: onboarding
......
---
key_path: counts.in_product_marketing_email_trial_short_0_cta_clicked
name: "count_clicks_on_the_first_email_of_the_trial_short_track_for_in_product_marketing_emails"
description: Total clicks on the trial_short track's first email
product_section: growth
product_stage: growth
product_group: group::activation
product_category: onboarding
value_type: number
status: implemented
milestone: "14.2"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66943
time_frame: all
data_source: database
data_category: Optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
---
key_path: counts.in_product_marketing_email_trial_short_0_sent
name: "count_sent_first_email_of_the_trial_short_track_for_in_product_marketing_emails"
description: Total sent emails of the trial_short track's first email
product_section: growth
product_stage: growth
product_group: group::activation
product_category: onboarding
value_type: number
status: implemented
milestone: "14.2"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66943
time_frame: all
data_source: database
data_category: Optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate
# frozen_string_literal: true
class AddRemainingCiMinutesToCiPendingBuild < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
with_lock_retries do
add_column(:ci_pending_builds, :namespace_id, :bigint)
add_column(:ci_pending_builds, :minutes_exceeded, :boolean, null: false, default: false)
end
end
def down
with_lock_retries do
remove_column(:ci_pending_builds, :minutes_exceeded)
remove_column(:ci_pending_builds, :namespace_id)
end
end
end
# frozen_string_literal: true
class AddNamespaceForeignKeyToCiPendingBuild < ActiveRecord::Migration[6.1]
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
INDEX_NAME = 'index_ci_pending_builds_on_namespace_id'
def up
add_concurrent_index(:ci_pending_builds, :namespace_id, name: INDEX_NAME)
add_concurrent_foreign_key(:ci_pending_builds, :namespaces, column: :namespace_id, on_delete: :cascade)
end
def down
remove_foreign_key_if_exists(:ci_pending_builds, column: :namespace_id)
remove_concurrent_index_by_name(:ci_pending_builds, INDEX_NAME)
end
end
fc330cf9875a423db87748e84c574f2208e164945b56361a563f2085d324f610
\ No newline at end of file
4400cd95cf149a7abc759ca412b0d87c81bc405719999ce60502869d21d17aaa
\ No newline at end of file
......@@ -10901,7 +10901,9 @@ CREATE TABLE ci_pending_builds (
project_id bigint NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL,
protected boolean DEFAULT false NOT NULL,
instance_runners_enabled boolean DEFAULT false NOT NULL
instance_runners_enabled boolean DEFAULT false NOT NULL,
namespace_id bigint,
minutes_exceeded boolean DEFAULT false NOT NULL
);
CREATE SEQUENCE ci_pending_builds_id_seq
......@@ -23267,6 +23269,8 @@ CREATE INDEX index_ci_pending_builds_id_on_protected_partial ON ci_pending_build
CREATE UNIQUE INDEX index_ci_pending_builds_on_build_id ON ci_pending_builds USING btree (build_id);
CREATE INDEX index_ci_pending_builds_on_namespace_id ON ci_pending_builds USING btree (namespace_id);
CREATE INDEX index_ci_pending_builds_on_project_id ON ci_pending_builds USING btree (project_id);
CREATE INDEX index_ci_pipeline_artifacts_failed_verification ON ci_pipeline_artifacts USING btree (verification_retry_at NULLS FIRST) WHERE (verification_state = 3);
......@@ -26698,6 +26702,9 @@ ALTER TABLE ONLY ci_daily_build_group_report_results
ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_fd82eae0b9 FOREIGN KEY (head_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL;
ALTER TABLE ONLY ci_pending_builds
ADD CONSTRAINT fk_fdc0137e4a FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY project_import_data
ADD CONSTRAINT fk_ffb9ee3a10 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
......@@ -2750,6 +2750,34 @@ Status: `data_available`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_trial_short_0_cta_clicked`
Total clicks on the trial_short track's first email
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210727170553_in_product_marketing_email_trial_short_0_cta_clicked.yml)
Group: `group::activation`
Data Category: `Optional`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_trial_short_0_sent`
Total sent emails of the trial_short track's first email
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210727170558_in_product_marketing_email_trial_short_0_sent.yml)
Group: `group::activation`
Data Category: `Optional`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_verify_0_cta_clicked`
Total clicks on the verify track's first email
......
......@@ -214,6 +214,14 @@ To set up the GitLab external URL:
sudo gitlab-ctl reconfigure
```
1. To prevent the domain name from
[resetting after a reboot](https://docs.bitnami.com/aws/apps/gitlab/configuration/change-default-address/),
Rename the utility that Bitnami uses:
```shell
sudo mv /opt/bitnami/apps/gitlab/bnconfig /opt/bitnami/apps/gitlab/bnconfig.bak
```
You can now visit GitLab with your browser at the new external URL.
### Visit GitLab for the first time
......
......@@ -48,6 +48,13 @@ module Ci
enabled? && total_minutes_used >= total_minutes
end
# TODO: merge this with minutes_used_up? in
# https://gitlab.com/gitlab-org/gitlab/-/issues/332933.
# This method is agnostic from Project#shared_runners_enabled
def actual_minutes_used_up?
limit_enabled? && total_minutes_used >= total_minutes
end
def total_minutes
@total_minutes ||= monthly_minutes + purchased_minutes
end
......@@ -74,6 +81,14 @@ module Ci
private
# TODO: rename to `enabled?`
# https://gitlab.com/gitlab-org/gitlab/-/issues/332933
def limit_enabled?
strong_memoize(:limit_enabled) do
namespace.root? && !!total_minutes.nonzero?
end
end
def minutes_limit
return monthly_minutes if enabled?
......
# frozen_string_literal: true
module EE
module Ci
module PendingBuild
extend ActiveSupport::Concern
class_methods do
extend ::Gitlab::Utils::Override
override :args_from_build
def args_from_build(build)
return super unless ::Feature.enabled?(
:ci_pending_builds_maintain_ci_minutes_data,
build&.project&.root_namespace,
type: :development,
default_enabled: :yaml
)
super.merge(minutes_exceeded: minutes_exceeded?(build.project))
end
private
def minutes_exceeded?(project)
::Ci::Runner.any_shared_runners_with_enabled_cost_factor?(project) &&
project.ci_minutes_quota.actual_minutes_used_up?
end
end
end
end
end
......@@ -5,6 +5,18 @@ module EE
module Runner
extend ActiveSupport::Concern
prepended do
def self.any_shared_runners_with_enabled_cost_factor?(project)
if project.public?
return true if project.force_cost_factor?
instance_type.where('public_projects_minutes_cost_factor > 0').exists?
else
instance_type.where('private_projects_minutes_cost_factor > 0').exists?
end
end
end
def cost_factor_for_project(project)
cost_factor.for_project(project)
end
......
......@@ -15,9 +15,13 @@ module Ci
def execute
authorize_current_user!
return successful_response if additional_pack.persisted?
if additional_pack.persisted? || save_additional_pack
reset_ci_minutes!
save_additional_pack ? successful_response : error_response
successful_response
else
error_response
end
end
private
......@@ -58,6 +62,10 @@ module Ci
def error_response
error('Unable to save additional pack')
end
def reset_ci_minutes!
::Ci::Minutes::RefreshCachedDataService.new(namespace).execute
end
end
end
end
......
# frozen_string_literal: true
module Ci
module Minutes
class RefreshCachedDataService
def initialize(root_namespace)
@root_namespace = root_namespace
end
def execute
return unless @root_namespace
reset_ci_minutes_cache!
update_pending_builds!
rescue StandardError => e
::Gitlab::ErrorTracking.track_exception(
e,
root_namespace_id: @root_namespace.id
)
end
def reset_ci_minutes_cache!
::Gitlab::Ci::Minutes::CachedQuota.new(@root_namespace).expire!
end
# rubocop: disable CodeReuse/ActiveRecord
def update_pending_builds!
return unless ::Feature.enabled?(:ci_pending_builds_maintain_ci_minutes_data, @root_namespace, type: :development, default_enabled: :yaml)
minutes_exceeded = @root_namespace.ci_minutes_quota.actual_minutes_used_up?
all_namespaces = @root_namespace.self_and_descendant_ids
::Ci::PendingBuild.where(namespace: all_namespaces).update_all(minutes_exceeded: minutes_exceeded)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
......@@ -11,7 +11,7 @@ class ClearNamespaceSharedRunnersMinutesService < BaseService
shared_runners_seconds: 0,
shared_runners_seconds_last_reset: Time.current
).tap do
::Gitlab::Ci::Minutes::CachedQuota.new(@namespace).expire!
::Ci::Minutes::RefreshCachedDataService.new(@namespace).execute
end
end
# rubocop: enable CodeReuse/ActiveRecord
......
......@@ -30,7 +30,7 @@ module EE
end
if params[:extra_shared_runners_minutes_limit].present? || params[:shared_runners_minutes_limit].present?
::Gitlab::Ci::Minutes::CachedQuota.new(namespace).expire!
::Ci::Minutes::RefreshCachedDataService.new(namespace).execute
end
namespace.update(update_attrs)
......
......@@ -36,9 +36,9 @@ module EE
end
end
override :create_vulnerabilities
def create_vulnerabilities
collate_remediations.each { |vulnerability| create_vulnerability(vulnerability, create_remediations(report_data['remediations'])) }
override :create_findings
def create_findings
collate_remediations.each { |finding| create_finding(finding, create_remediations(report_data['remediations'])) }
end
end
end
......
......@@ -98,42 +98,42 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
context 'when message is provided' do
it 'sets message from the report as a finding name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_name = Gitlab::Json.parse(vulnerability.raw_metadata)['message']
finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_name = Gitlab::Json.parse(finding.raw_metadata)['message']
expect(vulnerability.name).to eq(expected_name)
expect(finding.name).to eq(expected_name)
end
end
context 'when message is not provided' do
context 'and name is provided' do
it 'sets name from the report as a name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expected_name = Gitlab::Json.parse(vulnerability.raw_metadata)['name']
finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expected_name = Gitlab::Json.parse(finding.raw_metadata)['name']
expect(vulnerability.name).to eq(expected_name)
expect(finding.name).to eq(expected_name)
end
end
context 'and name is not provided' do
context 'when CVE identifier exists' do
it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
expect(vulnerability.name).to eq("CVE-2017-11429 in yarn.lock")
finding = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
expect(finding.name).to eq("CVE-2017-11429 in yarn.lock")
end
end
context 'when CWE identifier exists' do
it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
expect(vulnerability.name).to eq("CWE-2017-11429 in yarn.lock")
finding = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
expect(finding.name).to eq("CWE-2017-11429 in yarn.lock")
end
end
context 'when neither CVE nor CWE identifier exist' do
it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
expect(vulnerability.name).to eq("other-2017-11429 in yarn.lock")
finding = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
expect(finding.name).to eq("other-2017-11429 in yarn.lock")
end
end
end
......@@ -143,17 +143,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
describe 'parsing finding.details' do
context 'when details are provided' do
it 'sets details from the report' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_details = Gitlab::Json.parse(vulnerability.raw_metadata)['details']
finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_details = Gitlab::Json.parse(finding.raw_metadata)['details']
expect(vulnerability.details).to eq(expected_details)
expect(finding.details).to eq(expected_details)
end
end
context 'when details are not provided' do
it 'sets empty hash' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expect(vulnerability.details).to eq({})
finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expect(finding.details).to eq({})
end
end
end
......@@ -162,19 +162,19 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
let(:expected_remediation) { create(:ci_reports_security_remediation, diff: '') }
it 'finds remediation with same cve' do
vulnerability = report.findings.find { |x| x.compare_key == "CVE-1020" }
finding = report.findings.find { |x| x.compare_key == "CVE-1020" }
remediation = { 'fixes' => [{ 'cve' => 'CVE-1020' }], 'summary' => '', 'diff' => '' }
expect(Gitlab::Json.parse(vulnerability.raw_metadata).dig('remediations').first).to include remediation
expect(vulnerability.remediations.first.checksum).to eq(expected_remediation.checksum)
expect(Gitlab::Json.parse(finding.raw_metadata).dig('remediations').first).to include remediation
expect(finding.remediations.first.checksum).to eq(expected_remediation.checksum)
end
it 'finds remediation with same id' do
vulnerability = report.findings.find { |x| x.compare_key == "CVE-1030" }
finding = report.findings.find { |x| x.compare_key == "CVE-1030" }
remediation = { 'fixes' => [{ 'cve' => 'CVE', 'id' => 'bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3' }], 'summary' => '', 'diff' => '' }
expect(Gitlab::Json.parse(vulnerability.raw_metadata).dig('remediations').first).to include remediation
expect(vulnerability.remediations.first.checksum).to eq(expected_remediation.checksum)
expect(Gitlab::Json.parse(finding.raw_metadata).dig('remediations').first).to include remediation
expect(finding.remediations.first.checksum).to eq(expected_remediation.checksum)
end
it 'does not find remediation with different id' do
......@@ -189,8 +189,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
"diff": ""
}
report.findings.map do |vulnerability|
expect(Gitlab::Json.parse(vulnerability.raw_metadata).dig('remediations')).not_to include(fix_with_id)
report.findings.map do |finding|
expect(Gitlab::Json.parse(finding.raw_metadata).dig('remediations')).not_to include(fix_with_id)
end
end
end
......
......@@ -326,6 +326,25 @@ RSpec.describe Ci::Minutes::Quota do
end
end
describe '#actual_minutes_used_up?' do
subject { quota.actual_minutes_used_up? }
where(:minutes_used, :minutes_limit, :result, :title) do
100 | 0 | false | 'limit not enabled'
99 | 100 | false | 'total minutes not used'
101 | 100 | true | 'total minutes used'
end
with_them do
before do
allow(namespace).to receive(:shared_runners_seconds).and_return(minutes_used.minutes)
namespace.shared_runners_minutes_limit = minutes_limit
end
it { is_expected.to eq(result) }
end
end
describe '#total_minutes' do
subject { quota.total_minutes }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::PendingBuild do
let_it_be(:project) { create(:project) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
let(:build) { create(:ci_build, :created, pipeline: pipeline, project: project) }
describe '.upsert_from_build!' do
shared_examples 'ci minutes not available' do
it 'sets minutes_exceeded to true' do
expect { described_class.upsert_from_build!(build) }.to change(Ci::PendingBuild, :count).by(1)
expect(described_class.last.minutes_exceeded).to be_truthy
end
end
shared_examples 'ci minutes available' do
it 'sets minutes_exceeded to false' do
expect { described_class.upsert_from_build!(build) }.to change(Ci::PendingBuild, :count).by(1)
expect(described_class.last.minutes_exceeded).to be_falsey
end
end
context 'when ci minutes are not available' do
before do
allow_next_instance_of(::Ci::Minutes::Quota) do |instance|
allow(instance).to receive(:actual_minutes_used_up?).and_return(true)
end
end
context 'when project matches shared runners with cost factor enabled' do
before do
allow(::Ci::Runner).to receive(:any_shared_runners_with_enabled_cost_factor?).and_return(true)
end
context 'when ci_pending_builds_maintain_ci_minutes_data is enabled' do
it_behaves_like 'ci minutes not available'
end
context 'when ci_pending_builds_maintain_ci_minutes_data is disabled' do
before do
stub_feature_flags(ci_pending_builds_maintain_ci_minutes_data: false)
end
it_behaves_like 'ci minutes available'
end
end
context 'when project does not matches shared runners with cost factor enabled' do
context 'when ci_pending_builds_maintain_ci_minutes_data is enabled' do
it_behaves_like 'ci minutes available'
end
context 'when ci_pending_builds_maintain_ci_minutes_data is disabled' do
before do
stub_feature_flags(ci_pending_builds_maintain_ci_minutes_data: false)
end
it_behaves_like 'ci minutes available'
end
end
end
context 'when ci minutes are available' do
context 'when ci_pending_builds_maintain_ci_minutes_data is enabled' do
it_behaves_like 'ci minutes available'
end
context 'when ci_pending_builds_maintain_ci_minutes_data is disabled' do
before do
stub_feature_flags(ci_pending_builds_maintain_ci_minutes_data: false)
end
it_behaves_like 'ci minutes available'
end
end
context 'when using shared runners with cost factor disabled' do
context 'with new project' do
it_behaves_like 'ci minutes available'
end
end
end
end
......@@ -161,4 +161,64 @@ RSpec.describe EE::Ci::Runner do
end
end
end
describe '.any_shared_runners_with_enabled_cost_factor' do
subject(:runners) { Ci::Runner.any_shared_runners_with_enabled_cost_factor?(project) }
let_it_be(:namespace) { create(:group) }
context 'when project is public' do
let_it_be(:project) { create(:project, namespace: namespace, visibility_level: ::Gitlab::VisibilityLevel::PUBLIC) }
let_it_be(:runner) { create(:ci_runner, :instance, public_projects_minutes_cost_factor: 0.0) }
context 'when cost factor is forced' do
before do
allow(project).to receive(:force_cost_factor?).and_return(true)
end
it 'returns true' do
expect(runners).to be_truthy
end
end
context 'when cost factor is not forced' do
context 'when public cost factor is greater than zero' do
before do
runner.update!(public_projects_minutes_cost_factor: 1.0)
end
it 'returns true' do
expect(runners).to be_truthy
end
end
context 'when public cost factor is zero' do
it 'returns false' do
expect(runners).to be_falsey
end
end
end
end
context 'when project is private' do
let_it_be(:project) { create(:project, namespace: namespace, visibility_level: ::Gitlab::VisibilityLevel::PRIVATE) }
let_it_be(:runner) { create(:ci_runner, :instance, private_projects_minutes_cost_factor: 1.0) }
context 'when private cost factor is greater than zero' do
it 'returns true' do
expect(runners).to be_truthy
end
end
context 'when private cost factor is zero' do
before do
runner.update!(private_projects_minutes_cost_factor: 0.0)
end
it 'returns false' do
expect(runners).to be_falsey
end
end
end
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Ci::Minutes::AdditionalPacks::CreateService do
include AfterNextHelpers
describe '#execute' do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:admin) { build(:user, :admin) }
......@@ -62,6 +64,12 @@ RSpec.describe Ci::Minutes::AdditionalPacks::CreateService do
expect(pack.number_of_minutes).to eq params[:number_of_minutes]
end
it 'kicks off reset ci minutes service' do
expect_next(::Ci::Minutes::RefreshCachedDataService).to receive(:execute)
result
end
it 'returns success' do
expect(result[:status]).to eq :success
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::Minutes::RefreshCachedDataService do
include AfterNextHelpers
let_it_be(:project_1) { create(:project) }
let_it_be(:root_namespace) { project_1.root_namespace }
let_it_be(:build_1) { create(:ci_build, :pending, project: project_1) }
let_it_be(:build_2) { create(:ci_build, :pending) }
let_it_be(:pending_build_1) { create(:ci_pending_build, build: build_1, project: build_1.project, minutes_exceeded: true) }
let_it_be(:pending_build_2) { create(:ci_pending_build, build: build_2, project: build_2.project, minutes_exceeded: true) }
describe '#execute' do
subject { described_class.new(root_namespace).execute }
context 'when root_namespace is nil' do
let(:root_namespace) { nil }
it 'does nothing' do
expect { subject }.not_to raise_error
expect_next(::Gitlab::Ci::Minutes::CachedQuota).not_to receive(:expire!)
expect(pending_build_1.reload.minutes_exceeded).to be_truthy
expect(pending_build_2.reload.minutes_exceeded).to be_truthy
end
end
context 'when user purchases more ci minutes for a given namespace' do
before do
allow_next_instance_of(::Ci::Minutes::Quota) do |instance|
allow(instance).to receive(:actual_minutes_used_up?).and_return(false)
end
end
it 'updates relevant pending builds' do
subject
expect(pending_build_1.reload.minutes_exceeded).to be_falsey
expect(pending_build_2.reload.minutes_exceeded).to be_truthy
end
context 'when ci_pending_builds_maintain_ci_minutes_data is disabled' do
before do
stub_feature_flags(ci_pending_builds_maintain_ci_minutes_data: false)
end
it 'does not update pending builds' do
subject
expect(pending_build_1.reload.minutes_exceeded).to be_truthy
expect(pending_build_2.reload.minutes_exceeded).to be_truthy
end
end
it 'expires the CachedQuota' do
expect_next(::Gitlab::Ci::Minutes::CachedQuota).to receive(:expire!)
subject
end
end
end
end
......@@ -28,7 +28,7 @@ module Gitlab
create_analyzer
set_report_version
create_vulnerabilities
create_findings
report_data
rescue JSON::ParserError
......@@ -78,13 +78,13 @@ module Gitlab
data['tracking']
end
def create_vulnerabilities
def create_findings
if report_data["vulnerabilities"]
report_data["vulnerabilities"].each { |vulnerability| create_vulnerability(vulnerability) }
report_data["vulnerabilities"].each { |finding| create_finding(finding) }
end
end
def create_vulnerability(data, remediations = [])
def create_finding(data, remediations = [])
identifiers = create_identifiers(data['identifiers'])
links = create_links(data['links'])
location = create_location(data['location'] || {})
......
......@@ -68,6 +68,10 @@ module Gitlab
s_('InProductMarketing|Start your trial now!')
][series]
end
def progress
super(current: series + 2, total: 4)
end
end
end
end
......
# frozen_string_literal: true
module Gitlab
module Email
module Message
module InProductMarketing
class TrialShort < Base
def subject_line
s_('InProductMarketing|Be a DevOps hero')
end
def tagline
nil
end
def title
s_('InProductMarketing|Expand your DevOps journey with a free GitLab trial')
end
def subtitle
s_('InProductMarketing|Start your trial today to experience single application success and discover all the features of GitLab Ultimate for free!')
end
def body_line1
''
end
def body_line2
''
end
def cta_text
s_('InProductMarketing|Start a trial')
end
def progress
super(total: 4, track_name: 'Trial')
end
def logo_path
'mailers/in_product_marketing/trial-0.png'
end
end
end
end
end
end
......@@ -16899,6 +16899,9 @@ msgstr ""
msgid "InProductMarketing|Automated security scans directly within GitLab"
msgstr ""
msgid "InProductMarketing|Be a DevOps hero"
msgstr ""
msgid "InProductMarketing|Beef up your security"
msgstr ""
......@@ -16941,6 +16944,9 @@ msgstr ""
msgid "InProductMarketing|Easy"
msgstr ""
msgid "InProductMarketing|Expand your DevOps journey with a free GitLab trial"
msgstr ""
msgid "InProductMarketing|Explore GitLab CI/CD"
msgstr ""
......@@ -17133,6 +17139,9 @@ msgstr ""
msgid "InProductMarketing|Start your trial now!"
msgstr ""
msgid "InProductMarketing|Start your trial today to experience single application success and discover all the features of GitLab Ultimate for free!"
msgstr ""
msgid "InProductMarketing|Stop wondering and use GitLab to answer questions like:"
msgstr ""
......
......@@ -6,5 +6,7 @@ FactoryBot.define do
project
protected { build.protected }
instance_runners_enabled { true }
namespace { project.namespace }
minutes_exceeded { false }
end
end
......@@ -99,42 +99,42 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
context 'when message is provided' do
it 'sets message from the report as a finding name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_name = Gitlab::Json.parse(vulnerability.raw_metadata)['message']
finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_name = Gitlab::Json.parse(finding.raw_metadata)['message']
expect(vulnerability.name).to eq(expected_name)
expect(finding.name).to eq(expected_name)
end
end
context 'when message is not provided' do
context 'and name is provided' do
it 'sets name from the report as a name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expected_name = Gitlab::Json.parse(vulnerability.raw_metadata)['name']
finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expected_name = Gitlab::Json.parse(finding.raw_metadata)['name']
expect(vulnerability.name).to eq(expected_name)
expect(finding.name).to eq(expected_name)
end
end
context 'and name is not provided' do
context 'when CVE identifier exists' do
it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
expect(vulnerability.name).to eq("CVE-2017-11429 in yarn.lock")
finding = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
expect(finding.name).to eq("CVE-2017-11429 in yarn.lock")
end
end
context 'when CWE identifier exists' do
it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
expect(vulnerability.name).to eq("CWE-2017-11429 in yarn.lock")
finding = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
expect(finding.name).to eq("CWE-2017-11429 in yarn.lock")
end
end
context 'when neither CVE nor CWE identifier exist' do
it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
expect(vulnerability.name).to eq("other-2017-11429 in yarn.lock")
finding = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
expect(finding.name).to eq("other-2017-11429 in yarn.lock")
end
end
end
......@@ -144,17 +144,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
describe 'parsing finding.details' do
context 'when details are provided' do
it 'sets details from the report' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_details = Gitlab::Json.parse(vulnerability.raw_metadata)['details']
finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_details = Gitlab::Json.parse(finding.raw_metadata)['details']
expect(vulnerability.details).to eq(expected_details)
expect(finding.details).to eq(expected_details)
end
end
context 'when details are not provided' do
it 'sets empty hash' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expect(vulnerability.details).to eq({})
finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expect(finding.details).to eq({})
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Email::Message::InProductMarketing::TrialShort do
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
let(:series) { 0 }
subject(:message) { described_class.new(group: group, user: user, series: series)}
describe 'public methods' do
it 'returns value for series', :aggregate_failures do
expect(message.subject_line).to eq 'Be a DevOps hero'
expect(message.tagline).to be_nil
expect(message.title).to eq 'Expand your DevOps journey with a free GitLab trial'
expect(message.subtitle).to eq 'Start your trial today to experience single application success and discover all the features of GitLab Ultimate for free!'
expect(message.body_line1).to be_empty
expect(message.body_line2).to be_empty
expect(message.cta_text).to eq 'Start a trial'
expect(message.logo_path).to eq 'mailers/in_product_marketing/trial-0.png'
end
describe '#progress' do
subject { message.progress }
before do
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
end
context 'on gitlab.com' do
let(:is_gitlab_com) { true }
it { is_expected.to eq('This is email 1 of 4 in the Trial series.') }
end
context 'not on gitlab.com' do
let(:is_gitlab_com) { false }
it { is_expected.to include('This is email 1 of 4 in the Trial series', Gitlab::Routing.url_helpers.profile_notifications_url) }
end
end
end
end
......@@ -23,6 +23,26 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Trial do
expect(message.body_line2).to be_present
expect(message.cta_text).to be_present
end
describe '#progress' do
subject { message.progress }
before do
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
end
context 'on gitlab.com' do
let(:is_gitlab_com) { true }
it { is_expected.to eq("This is email #{series + 2} of 4 in the Trial series.") }
end
context 'not on gitlab.com' do
let(:is_gitlab_com) { false }
it { is_expected.to include("This is email #{series + 2} of 4 in the Trial series", Gitlab::Routing.url_helpers.profile_notifications_url) }
end
end
end
end
end
......@@ -1361,6 +1361,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
"in_product_marketing_email_create_2_cta_clicked" => -1,
"in_product_marketing_email_team_short_0_sent" => -1,
"in_product_marketing_email_team_short_0_cta_clicked" => -1,
"in_product_marketing_email_trial_short_0_sent" => -1,
"in_product_marketing_email_trial_short_0_cta_clicked" => -1,
"in_product_marketing_email_verify_0_sent" => -1,
"in_product_marketing_email_verify_0_cta_clicked" => -1,
"in_product_marketing_email_verify_1_sent" => -1,
......@@ -1402,6 +1404,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
"in_product_marketing_email_create_2_cta_clicked" => 0,
"in_product_marketing_email_team_short_0_sent" => 0,
"in_product_marketing_email_team_short_0_cta_clicked" => 0,
"in_product_marketing_email_trial_short_0_sent" => 0,
"in_product_marketing_email_trial_short_0_cta_clicked" => 0,
"in_product_marketing_email_verify_0_sent" => 1,
"in_product_marketing_email_verify_0_cta_clicked" => 0,
"in_product_marketing_email_verify_1_sent" => 0,
......
......@@ -8,6 +8,12 @@ RSpec.describe Ci::PendingBuild do
let(:build) { create(:ci_build, :created, pipeline: pipeline) }
describe 'associations' do
it { is_expected.to belong_to :project }
it { is_expected.to belong_to :build }
it { is_expected.to belong_to :namespace }
end
describe '.upsert_from_build!' do
context 'another pending entry does not exist' do
it 'creates a new pending entry' do
......
......@@ -61,21 +61,21 @@ RSpec.describe Integration do
end
describe 'Scopes' do
describe '.inherit' do
describe '.with_default_settings' do
it 'returns the correct integrations' do
instance_integration = create(:integration, :instance)
inheriting_integration = create(:integration, inherit_from_id: instance_integration.id)
expect(described_class.inherit).to match_array([inheriting_integration])
expect(described_class.with_default_settings).to match_array([inheriting_integration])
end
end
describe '.not_inherited' do
describe '.with_custom_settings' do
it 'returns the correct integrations' do
instance_integration = create(:integration, :instance)
create(:integration, inherit_from_id: instance_integration.id)
expect(described_class.not_inherited).to match_array([instance_integration])
expect(described_class.with_custom_settings).to match_array([instance_integration])
end
end
......
......@@ -23,6 +23,7 @@ RSpec.describe Namespace do
it { is_expected.to have_one :package_setting_relation }
it { is_expected.to have_one :onboarding_progress }
it { is_expected.to have_one :admin_note }
it { is_expected.to have_many :pending_builds }
end
describe 'validations' do
......
......@@ -39,20 +39,21 @@ RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute' do
using RSpec::Parameterized::TableSyntax
where(:track, :interval, :actions_completed) do
:create | 1 | { created_at: frozen_time - 2.days }
:create | 5 | { created_at: frozen_time - 6.days }
:create | 10 | { created_at: frozen_time - 11.days }
:team_short | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days }
:verify | 2 | { created_at: frozen_time - 3.days, git_write_at: frozen_time - 3.days }
:verify | 6 | { created_at: frozen_time - 7.days, git_write_at: frozen_time - 7.days }
:verify | 11 | { created_at: frozen_time - 12.days, git_write_at: frozen_time - 12.days }
:trial | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days }
:trial | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days }
:trial | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days }
:team | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days, trial_started_at: frozen_time - 2.days }
:team | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days, trial_started_at: frozen_time - 6.days }
:team | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days, trial_started_at: frozen_time - 11.days }
:experience | 30 | { created_at: frozen_time - 31.days, git_write_at: frozen_time - 31.days }
:create | 1 | { created_at: frozen_time - 2.days }
:create | 5 | { created_at: frozen_time - 6.days }
:create | 10 | { created_at: frozen_time - 11.days }
:team_short | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days }
:trial_short | 2 | { created_at: frozen_time - 3.days, git_write_at: frozen_time - 3.days }
:verify | 3 | { created_at: frozen_time - 4.days, git_write_at: frozen_time - 4.days }
:verify | 7 | { created_at: frozen_time - 8.days, git_write_at: frozen_time - 8.days }
:verify | 12 | { created_at: frozen_time - 13.days, git_write_at: frozen_time - 13.days }
:trial | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days }
:trial | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days }
:trial | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days }
:team | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days, trial_started_at: frozen_time - 2.days }
:team | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days, trial_started_at: frozen_time - 6.days }
:team | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days, trial_started_at: frozen_time - 11.days }
:experience | 30 | { created_at: frozen_time - 31.days, git_write_at: frozen_time - 31.days }
end
with_them do
......
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