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 ...@@ -12,7 +12,7 @@ class Admin::IntegrationsController < Admin::ApplicationController
respond_to do |format| respond_to do |format|
format.json do 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) serializer = ::Integrations::ProjectSerializer.new.with_pagination(request, response)
render json: serializer.represent(projects) render json: serializer.represent(projects)
......
...@@ -38,7 +38,7 @@ class Groups::EmailCampaignsController < Groups::ApplicationController ...@@ -38,7 +38,7 @@ class Groups::EmailCampaignsController < Groups::ApplicationController
create_track_url create_track_url
when :verify when :verify
project_pipelines_url(group.projects.first) project_pipelines_url(group.projects.first)
when :trial when :trial, :trial_short
'https://about.gitlab.com/free-trial/' 'https://about.gitlab.com/free-trial/'
when :team, :team_short when :team, :team_short
group_group_members_url(group) group_group_members_url(group)
......
...@@ -4,6 +4,9 @@ module Ci ...@@ -4,6 +4,9 @@ module Ci
class PendingBuild < Ci::ApplicationRecord class PendingBuild < Ci::ApplicationRecord
belongs_to :project belongs_to :project
belongs_to :build, class_name: 'Ci::Build' 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 :ref_protected, -> { where(protected: true) }
scope :queued_before, ->(time) { where(arel_table[:created_at].lt(time)) } scope :queued_before, ->(time) { where(arel_table[:created_at].lt(time)) }
...@@ -20,7 +23,8 @@ module Ci ...@@ -20,7 +23,8 @@ module Ci
args = { args = {
build: build, build: build,
project: build.project, 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) if Feature.enabled?(:ci_pending_builds_maintain_shared_runners_data, type: :development, default_enabled: :yaml)
...@@ -54,3 +58,5 @@ module Ci ...@@ -54,3 +58,5 @@ module Ci
private_class_method :builds_access_level? private_class_method :builds_access_level?
end end
end end
Ci::PendingBuild.prepend_mod_with('Ci::PendingBuild')
...@@ -77,8 +77,8 @@ class Integration < ApplicationRecord ...@@ -77,8 +77,8 @@ class Integration < ApplicationRecord
scope :by_type, -> (type) { where(type: type) } scope :by_type, -> (type) { where(type: type) }
scope :by_active_flag, -> (flag) { where(active: flag) } scope :by_active_flag, -> (flag) { where(active: flag) }
scope :inherit_from_id, -> (id) { where(inherit_from_id: id) } scope :inherit_from_id, -> (id) { where(inherit_from_id: id) }
scope :inherit, -> { where.not(inherit_from_id: nil) } scope :with_default_settings, -> { where.not(inherit_from_id: nil) }
scope :not_inherited, -> { where(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_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)) } scope :for_instance, -> { where(instance: true, type: available_integration_types(include_project_specific: false)) }
......
...@@ -34,6 +34,7 @@ class Namespace < ApplicationRecord ...@@ -34,6 +34,7 @@ class Namespace < ApplicationRecord
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace' 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 :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
has_many :pending_builds, class_name: 'Ci::PendingBuild'
has_one :onboarding_progress has_one :onboarding_progress
# This should _not_ be `inverse_of: :namespace`, because that would also set # This should _not_ be `inverse_of: :namespace`, because that would also set
......
...@@ -20,7 +20,8 @@ module Users ...@@ -20,7 +20,8 @@ module Users
trial: 2, trial: 2,
team: 3, team: 3,
experience: 4, experience: 4,
team_short: 5 team_short: 5,
trial_short: 6
}, _suffix: true }, _suffix: true
scope :without_track_and_series, -> (track, series) do scope :without_track_and_series, -> (track, series) do
......
...@@ -208,12 +208,12 @@ module Groups ...@@ -208,12 +208,12 @@ module Groups
end end
def update_integrations def update_integrations
@group.integrations.inherit.delete_all @group.integrations.with_default_settings.delete_all
Integration.create_from_active_default_integrations(@group, :group_id) Integration.create_from_active_default_integrations(@group, :group_id)
end end
def propagate_integrations def propagate_integrations
@group.integrations.inherit.each do |integration| @group.integrations.with_default_settings.each do |integration|
PropagateIntegrationWorker.perform_async(integration.id) PropagateIntegrationWorker.perform_async(integration.id)
end end
end end
......
...@@ -13,8 +13,13 @@ module Namespaces ...@@ -13,8 +13,13 @@ module Namespaces
completed_actions: [:git_write], completed_actions: [:git_write],
incomplete_actions: [:user_added] incomplete_actions: [:user_added]
}, },
trial_short: {
interval_days: [2],
completed_actions: [:git_write],
incomplete_actions: [:trial_started]
},
verify: { verify: {
interval_days: [2, 6, 11], interval_days: [3, 7, 12],
completed_actions: [:git_write], completed_actions: [:git_write],
incomplete_actions: [:pipeline_created] incomplete_actions: [:pipeline_created]
}, },
...@@ -105,7 +110,7 @@ module Namespaces ...@@ -105,7 +110,7 @@ module Namespaces
case track case track
when :create, :verify when :create, :verify
user.can?(:create_projects, group) user.can?(:create_projects, group)
when :trial when :trial, :trial_short
user.can?(:start_trial, group) user.can?(:start_trial, group)
when :team, :team_short when :team, :team_short
user.can?(:admin_group_member, group) user.can?(:admin_group_member, group)
......
...@@ -241,7 +241,7 @@ module Projects ...@@ -241,7 +241,7 @@ module Projects
end end
def update_integrations def update_integrations
project.integrations.inherit.delete_all project.integrations.with_default_settings.delete_all
Integration.create_from_active_default_integrations(project, :project_id) Integration.create_from_active_default_integrations(project, :project_id)
end end
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 @@ ...@@ -2,7 +2,7 @@
key_path: counts.in_product_marketing_email_team_short_0_cta_clicked 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" 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 description: Total clicks on the team_short track's first email
product_section: product_section: growth
product_stage: growth product_stage: growth
product_group: group::activation product_group: group::activation
product_category: onboarding product_category: onboarding
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
key_path: counts.in_product_marketing_email_team_short_0_sent 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" 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 description: Total sent emails of the team_short track's first email
product_section: product_section: growth
product_stage: growth product_stage: growth
product_group: group::activation product_group: group::activation
product_category: onboarding 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 ( ...@@ -10901,7 +10901,9 @@ CREATE TABLE ci_pending_builds (
project_id bigint NOT NULL, project_id bigint NOT NULL,
created_at timestamp with time zone DEFAULT now() NOT NULL, created_at timestamp with time zone DEFAULT now() NOT NULL,
protected boolean DEFAULT false 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 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 ...@@ -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 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_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); 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 ...@@ -26698,6 +26702,9 @@ ALTER TABLE ONLY ci_daily_build_group_report_results
ALTER TABLE ONLY merge_requests ALTER TABLE ONLY merge_requests
ADD CONSTRAINT fk_fd82eae0b9 FOREIGN KEY (head_pipeline_id) REFERENCES ci_pipelines(id) ON DELETE SET NULL; 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 ALTER TABLE ONLY project_import_data
ADD CONSTRAINT fk_ffb9ee3a10 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; ADD CONSTRAINT fk_ffb9ee3a10 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
...@@ -2750,6 +2750,34 @@ Status: `data_available` ...@@ -2750,6 +2750,34 @@ Status: `data_available`
Tiers: `free`, `premium`, `ultimate` 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` ### `counts.in_product_marketing_email_verify_0_cta_clicked`
Total clicks on the verify track's first email Total clicks on the verify track's first email
......
...@@ -214,6 +214,14 @@ To set up the GitLab external URL: ...@@ -214,6 +214,14 @@ To set up the GitLab external URL:
sudo gitlab-ctl reconfigure 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. You can now visit GitLab with your browser at the new external URL.
### Visit GitLab for the first time ### Visit GitLab for the first time
......
...@@ -48,6 +48,13 @@ module Ci ...@@ -48,6 +48,13 @@ module Ci
enabled? && total_minutes_used >= total_minutes enabled? && total_minutes_used >= total_minutes
end 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 def total_minutes
@total_minutes ||= monthly_minutes + purchased_minutes @total_minutes ||= monthly_minutes + purchased_minutes
end end
...@@ -74,6 +81,14 @@ module Ci ...@@ -74,6 +81,14 @@ module Ci
private 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 def minutes_limit
return monthly_minutes if enabled? 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 ...@@ -5,6 +5,18 @@ module EE
module Runner module Runner
extend ActiveSupport::Concern 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) def cost_factor_for_project(project)
cost_factor.for_project(project) cost_factor.for_project(project)
end end
......
...@@ -15,9 +15,13 @@ module Ci ...@@ -15,9 +15,13 @@ module Ci
def execute def execute
authorize_current_user! 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 end
private private
...@@ -58,6 +62,10 @@ module Ci ...@@ -58,6 +62,10 @@ module Ci
def error_response def error_response
error('Unable to save additional pack') error('Unable to save additional pack')
end end
def reset_ci_minutes!
::Ci::Minutes::RefreshCachedDataService.new(namespace).execute
end
end end
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 ...@@ -11,7 +11,7 @@ class ClearNamespaceSharedRunnersMinutesService < BaseService
shared_runners_seconds: 0, shared_runners_seconds: 0,
shared_runners_seconds_last_reset: Time.current shared_runners_seconds_last_reset: Time.current
).tap do ).tap do
::Gitlab::Ci::Minutes::CachedQuota.new(@namespace).expire! ::Ci::Minutes::RefreshCachedDataService.new(@namespace).execute
end end
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
......
...@@ -30,7 +30,7 @@ module EE ...@@ -30,7 +30,7 @@ module EE
end end
if params[:extra_shared_runners_minutes_limit].present? || params[:shared_runners_minutes_limit].present? 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 end
namespace.update(update_attrs) namespace.update(update_attrs)
......
...@@ -36,9 +36,9 @@ module EE ...@@ -36,9 +36,9 @@ module EE
end end
end end
override :create_vulnerabilities override :create_findings
def create_vulnerabilities def create_findings
collate_remediations.each { |vulnerability| create_vulnerability(vulnerability, create_remediations(report_data['remediations'])) } collate_remediations.each { |finding| create_finding(finding, create_remediations(report_data['remediations'])) }
end end
end end
end end
......
...@@ -98,42 +98,42 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -98,42 +98,42 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
context 'when message is provided' do context 'when message is provided' do
it 'sets message from the report as a finding name' do it 'sets message from the report as a finding name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' } finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_name = Gitlab::Json.parse(vulnerability.raw_metadata)['message'] 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
end end
context 'when message is not provided' do context 'when message is not provided' do
context 'and name is provided' do context 'and name is provided' do
it 'sets name from the report as a name' do it 'sets name from the report as a name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1030' } finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expected_name = Gitlab::Json.parse(vulnerability.raw_metadata)['name'] 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
end end
context 'and name is not provided' do context 'and name is not provided' do
context 'when CVE identifier exists' do context 'when CVE identifier exists' do
it 'combines identifier with location to create name' do it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' } finding = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
expect(vulnerability.name).to eq("CVE-2017-11429 in yarn.lock") expect(finding.name).to eq("CVE-2017-11429 in yarn.lock")
end end
end end
context 'when CWE identifier exists' do context 'when CWE identifier exists' do
it 'combines identifier with location to create name' do it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' } finding = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
expect(vulnerability.name).to eq("CWE-2017-11429 in yarn.lock") expect(finding.name).to eq("CWE-2017-11429 in yarn.lock")
end end
end end
context 'when neither CVE nor CWE identifier exist' do context 'when neither CVE nor CWE identifier exist' do
it 'combines identifier with location to create name' do it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' } finding = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
expect(vulnerability.name).to eq("other-2017-11429 in yarn.lock") expect(finding.name).to eq("other-2017-11429 in yarn.lock")
end end
end end
end end
...@@ -143,17 +143,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -143,17 +143,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
describe 'parsing finding.details' do describe 'parsing finding.details' do
context 'when details are provided' do context 'when details are provided' do
it 'sets details from the report' do it 'sets details from the report' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' } finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_details = Gitlab::Json.parse(vulnerability.raw_metadata)['details'] 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
end end
context 'when details are not provided' do context 'when details are not provided' do
it 'sets empty hash' do it 'sets empty hash' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1030' } finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expect(vulnerability.details).to eq({}) expect(finding.details).to eq({})
end end
end end
end end
...@@ -162,19 +162,19 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -162,19 +162,19 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
let(:expected_remediation) { create(:ci_reports_security_remediation, diff: '') } let(:expected_remediation) { create(:ci_reports_security_remediation, diff: '') }
it 'finds remediation with same cve' do 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' => '' } remediation = { 'fixes' => [{ 'cve' => 'CVE-1020' }], 'summary' => '', 'diff' => '' }
expect(Gitlab::Json.parse(vulnerability.raw_metadata).dig('remediations').first).to include remediation expect(Gitlab::Json.parse(finding.raw_metadata).dig('remediations').first).to include remediation
expect(vulnerability.remediations.first.checksum).to eq(expected_remediation.checksum) expect(finding.remediations.first.checksum).to eq(expected_remediation.checksum)
end end
it 'finds remediation with same id' do 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' => '' } remediation = { 'fixes' => [{ 'cve' => 'CVE', 'id' => 'bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3' }], 'summary' => '', 'diff' => '' }
expect(Gitlab::Json.parse(vulnerability.raw_metadata).dig('remediations').first).to include remediation expect(Gitlab::Json.parse(finding.raw_metadata).dig('remediations').first).to include remediation
expect(vulnerability.remediations.first.checksum).to eq(expected_remediation.checksum) expect(finding.remediations.first.checksum).to eq(expected_remediation.checksum)
end end
it 'does not find remediation with different id' do it 'does not find remediation with different id' do
...@@ -189,8 +189,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -189,8 +189,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
"diff": "" "diff": ""
} }
report.findings.map do |vulnerability| report.findings.map do |finding|
expect(Gitlab::Json.parse(vulnerability.raw_metadata).dig('remediations')).not_to include(fix_with_id) expect(Gitlab::Json.parse(finding.raw_metadata).dig('remediations')).not_to include(fix_with_id)
end end
end end
end end
......
...@@ -326,6 +326,25 @@ RSpec.describe Ci::Minutes::Quota do ...@@ -326,6 +326,25 @@ RSpec.describe Ci::Minutes::Quota do
end end
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 describe '#total_minutes' do
subject { quota.total_minutes } 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 ...@@ -161,4 +161,64 @@ RSpec.describe EE::Ci::Runner do
end end
end 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 end
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Ci::Minutes::AdditionalPacks::CreateService do RSpec.describe Ci::Minutes::AdditionalPacks::CreateService do
include AfterNextHelpers
describe '#execute' do describe '#execute' do
let_it_be(:namespace) { create(:namespace) } let_it_be(:namespace) { create(:namespace) }
let_it_be(:admin) { build(:user, :admin) } let_it_be(:admin) { build(:user, :admin) }
...@@ -62,6 +64,12 @@ RSpec.describe Ci::Minutes::AdditionalPacks::CreateService do ...@@ -62,6 +64,12 @@ RSpec.describe Ci::Minutes::AdditionalPacks::CreateService do
expect(pack.number_of_minutes).to eq params[:number_of_minutes] expect(pack.number_of_minutes).to eq params[:number_of_minutes]
end end
it 'kicks off reset ci minutes service' do
expect_next(::Ci::Minutes::RefreshCachedDataService).to receive(:execute)
result
end
it 'returns success' do it 'returns success' do
expect(result[:status]).to eq :success expect(result[:status]).to eq :success
end 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 ...@@ -28,7 +28,7 @@ module Gitlab
create_analyzer create_analyzer
set_report_version set_report_version
create_vulnerabilities create_findings
report_data report_data
rescue JSON::ParserError rescue JSON::ParserError
...@@ -78,13 +78,13 @@ module Gitlab ...@@ -78,13 +78,13 @@ module Gitlab
data['tracking'] data['tracking']
end end
def create_vulnerabilities def create_findings
if report_data["vulnerabilities"] if report_data["vulnerabilities"]
report_data["vulnerabilities"].each { |vulnerability| create_vulnerability(vulnerability) } report_data["vulnerabilities"].each { |finding| create_finding(finding) }
end end
end end
def create_vulnerability(data, remediations = []) def create_finding(data, remediations = [])
identifiers = create_identifiers(data['identifiers']) identifiers = create_identifiers(data['identifiers'])
links = create_links(data['links']) links = create_links(data['links'])
location = create_location(data['location'] || {}) location = create_location(data['location'] || {})
......
...@@ -68,6 +68,10 @@ module Gitlab ...@@ -68,6 +68,10 @@ module Gitlab
s_('InProductMarketing|Start your trial now!') s_('InProductMarketing|Start your trial now!')
][series] ][series]
end end
def progress
super(current: series + 2, total: 4)
end
end end
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 "" ...@@ -16899,6 +16899,9 @@ msgstr ""
msgid "InProductMarketing|Automated security scans directly within GitLab" msgid "InProductMarketing|Automated security scans directly within GitLab"
msgstr "" msgstr ""
msgid "InProductMarketing|Be a DevOps hero"
msgstr ""
msgid "InProductMarketing|Beef up your security" msgid "InProductMarketing|Beef up your security"
msgstr "" msgstr ""
...@@ -16941,6 +16944,9 @@ msgstr "" ...@@ -16941,6 +16944,9 @@ msgstr ""
msgid "InProductMarketing|Easy" msgid "InProductMarketing|Easy"
msgstr "" msgstr ""
msgid "InProductMarketing|Expand your DevOps journey with a free GitLab trial"
msgstr ""
msgid "InProductMarketing|Explore GitLab CI/CD" msgid "InProductMarketing|Explore GitLab CI/CD"
msgstr "" msgstr ""
...@@ -17133,6 +17139,9 @@ msgstr "" ...@@ -17133,6 +17139,9 @@ msgstr ""
msgid "InProductMarketing|Start your trial now!" msgid "InProductMarketing|Start your trial now!"
msgstr "" 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:" msgid "InProductMarketing|Stop wondering and use GitLab to answer questions like:"
msgstr "" msgstr ""
......
...@@ -6,5 +6,7 @@ FactoryBot.define do ...@@ -6,5 +6,7 @@ FactoryBot.define do
project project
protected { build.protected } protected { build.protected }
instance_runners_enabled { true } instance_runners_enabled { true }
namespace { project.namespace }
minutes_exceeded { false }
end end
end end
...@@ -99,42 +99,42 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -99,42 +99,42 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
context 'when message is provided' do context 'when message is provided' do
it 'sets message from the report as a finding name' do it 'sets message from the report as a finding name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' } finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_name = Gitlab::Json.parse(vulnerability.raw_metadata)['message'] 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
end end
context 'when message is not provided' do context 'when message is not provided' do
context 'and name is provided' do context 'and name is provided' do
it 'sets name from the report as a name' do it 'sets name from the report as a name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1030' } finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expected_name = Gitlab::Json.parse(vulnerability.raw_metadata)['name'] 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
end end
context 'and name is not provided' do context 'and name is not provided' do
context 'when CVE identifier exists' do context 'when CVE identifier exists' do
it 'combines identifier with location to create name' do it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' } finding = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
expect(vulnerability.name).to eq("CVE-2017-11429 in yarn.lock") expect(finding.name).to eq("CVE-2017-11429 in yarn.lock")
end end
end end
context 'when CWE identifier exists' do context 'when CWE identifier exists' do
it 'combines identifier with location to create name' do it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' } finding = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
expect(vulnerability.name).to eq("CWE-2017-11429 in yarn.lock") expect(finding.name).to eq("CWE-2017-11429 in yarn.lock")
end end
end end
context 'when neither CVE nor CWE identifier exist' do context 'when neither CVE nor CWE identifier exist' do
it 'combines identifier with location to create name' do it 'combines identifier with location to create name' do
vulnerability = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' } finding = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
expect(vulnerability.name).to eq("other-2017-11429 in yarn.lock") expect(finding.name).to eq("other-2017-11429 in yarn.lock")
end end
end end
end end
...@@ -144,17 +144,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -144,17 +144,17 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
describe 'parsing finding.details' do describe 'parsing finding.details' do
context 'when details are provided' do context 'when details are provided' do
it 'sets details from the report' do it 'sets details from the report' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1020' } finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
expected_details = Gitlab::Json.parse(vulnerability.raw_metadata)['details'] 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
end end
context 'when details are not provided' do context 'when details are not provided' do
it 'sets empty hash' do it 'sets empty hash' do
vulnerability = report.findings.find { |x| x.compare_key == 'CVE-1030' } finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
expect(vulnerability.details).to eq({}) expect(finding.details).to eq({})
end end
end 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 ...@@ -23,6 +23,26 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Trial do
expect(message.body_line2).to be_present expect(message.body_line2).to be_present
expect(message.cta_text).to be_present expect(message.cta_text).to be_present
end 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 end
end end
...@@ -1361,6 +1361,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ...@@ -1361,6 +1361,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
"in_product_marketing_email_create_2_cta_clicked" => -1, "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_sent" => -1,
"in_product_marketing_email_team_short_0_cta_clicked" => -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_sent" => -1,
"in_product_marketing_email_verify_0_cta_clicked" => -1, "in_product_marketing_email_verify_0_cta_clicked" => -1,
"in_product_marketing_email_verify_1_sent" => -1, "in_product_marketing_email_verify_1_sent" => -1,
...@@ -1402,6 +1404,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do ...@@ -1402,6 +1404,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
"in_product_marketing_email_create_2_cta_clicked" => 0, "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_sent" => 0,
"in_product_marketing_email_team_short_0_cta_clicked" => 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_sent" => 1,
"in_product_marketing_email_verify_0_cta_clicked" => 0, "in_product_marketing_email_verify_0_cta_clicked" => 0,
"in_product_marketing_email_verify_1_sent" => 0, "in_product_marketing_email_verify_1_sent" => 0,
......
...@@ -8,6 +8,12 @@ RSpec.describe Ci::PendingBuild do ...@@ -8,6 +8,12 @@ RSpec.describe Ci::PendingBuild do
let(:build) { create(:ci_build, :created, pipeline: pipeline) } 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 describe '.upsert_from_build!' do
context 'another pending entry does not exist' do context 'another pending entry does not exist' do
it 'creates a new pending entry' do it 'creates a new pending entry' do
......
...@@ -61,21 +61,21 @@ RSpec.describe Integration do ...@@ -61,21 +61,21 @@ RSpec.describe Integration do
end end
describe 'Scopes' do describe 'Scopes' do
describe '.inherit' do describe '.with_default_settings' do
it 'returns the correct integrations' do it 'returns the correct integrations' do
instance_integration = create(:integration, :instance) instance_integration = create(:integration, :instance)
inheriting_integration = create(:integration, inherit_from_id: instance_integration.id) 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
end end
describe '.not_inherited' do describe '.with_custom_settings' do
it 'returns the correct integrations' do it 'returns the correct integrations' do
instance_integration = create(:integration, :instance) instance_integration = create(:integration, :instance)
create(:integration, inherit_from_id: instance_integration.id) 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
end end
......
...@@ -23,6 +23,7 @@ RSpec.describe Namespace do ...@@ -23,6 +23,7 @@ RSpec.describe Namespace do
it { is_expected.to have_one :package_setting_relation } it { is_expected.to have_one :package_setting_relation }
it { is_expected.to have_one :onboarding_progress } it { is_expected.to have_one :onboarding_progress }
it { is_expected.to have_one :admin_note } it { is_expected.to have_one :admin_note }
it { is_expected.to have_many :pending_builds }
end end
describe 'validations' do describe 'validations' do
......
...@@ -39,20 +39,21 @@ RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute' do ...@@ -39,20 +39,21 @@ RSpec.describe Namespaces::InProductMarketingEmailsService, '#execute' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
where(:track, :interval, :actions_completed) do where(:track, :interval, :actions_completed) do
:create | 1 | { created_at: frozen_time - 2.days } :create | 1 | { created_at: frozen_time - 2.days }
:create | 5 | { created_at: frozen_time - 6.days } :create | 5 | { created_at: frozen_time - 6.days }
:create | 10 | { created_at: frozen_time - 11.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 } :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 } :trial_short | 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 | 3 | { created_at: frozen_time - 4.days, git_write_at: frozen_time - 4.days }
:verify | 11 | { created_at: frozen_time - 12.days, git_write_at: frozen_time - 12.days } :verify | 7 | { created_at: frozen_time - 8.days, git_write_at: frozen_time - 8.days }
:trial | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days } :verify | 12 | { created_at: frozen_time - 13.days, git_write_at: frozen_time - 13.days }
:trial | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.days } :trial | 1 | { created_at: frozen_time - 2.days, git_write_at: frozen_time - 2.days, pipeline_created_at: frozen_time - 2.days }
:trial | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.days } :trial | 5 | { created_at: frozen_time - 6.days, git_write_at: frozen_time - 6.days, pipeline_created_at: frozen_time - 6.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 } :trial | 10 | { created_at: frozen_time - 11.days, git_write_at: frozen_time - 11.days, pipeline_created_at: frozen_time - 11.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 | 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 | 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 } :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 }
:experience | 30 | { created_at: frozen_time - 31.days, git_write_at: frozen_time - 31.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 end
with_them do 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