Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-02-23 12:13:44 +00:00
parent e9c3815d3d
commit b286069fdf
23 changed files with 628 additions and 105 deletions

View File

@ -89,10 +89,6 @@ export default {
listTitle() {
return this.list?.label?.description || this.list?.assignee?.name || this.list.title || '';
},
listIterationPeriod() {
const iteration = this.list?.iteration;
return iteration ? this.getIterationPeriod(iteration) : '';
},
isIterationList() {
return this.listType === ListType.iteration;
},
@ -108,9 +104,6 @@ export default {
showIterationListDetails() {
return this.isIterationList && this.showListDetails;
},
iterationCadencesAvailable() {
return this.isIterationList && this.glFeatures.iterationCadences;
},
showListDetails() {
return !this.list.collapsed || !this.isSwimlanesHeader;
},
@ -344,13 +337,6 @@ export default {
class="board-title-main-text gl-text-truncate"
>
{{ listTitle }}
<span
v-if="iterationCadencesAvailable"
class="gl-display-inline-block gl-text-gray-400"
data-testid="board-list-iteration-period"
>
{{ listIterationPeriod }}</span
>
</span>
<span
v-if="listType === 'assignee'"

View File

@ -50,7 +50,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:issue_assignees_widget, project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, project, default_enabled: :yaml)
push_frontend_feature_flag(:fix_comment_scroll, project, default_enabled: :yaml)
push_frontend_feature_flag(:work_items, project, default_enabled: :yaml)
push_frontend_feature_flag(:work_items, project&.group, default_enabled: :yaml)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]

View File

@ -1,12 +1,15 @@
# frozen_string_literal: true
module LazyImageTagHelper
include PreferencesHelper
def placeholder_image
"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
end
# Override the default ActionView `image_tag` helper to support lazy-loading
def image_tag(source, options = {})
source = options[:dark_variant] if options[:dark_variant] && user_application_dark_mode?
options = options.symbolize_keys
unless options.delete(:lazy) == false

View File

@ -62,6 +62,10 @@ module PreferencesHelper
@user_application_theme ||= Gitlab::Themes.for_user(current_user).css_class
end
def user_application_dark_mode?
user_application_theme == 'gl-dark'
end
def user_application_theme_css_filename
@user_application_theme_css_filename ||= Gitlab::Themes.for_user(current_user).css_filename
end

View File

@ -18,5 +18,6 @@ module Ci
scope :unprotected, -> { where(protected: false) }
scope :by_environment_scope, -> (environment_scope) { where(environment_scope: environment_scope) }
scope :for_groups, ->(group_ids) { where(group_id: group_ids) }
end
end

View File

@ -341,12 +341,8 @@ class Project < ApplicationRecord
has_many :stages, class_name: 'Ci::Stage', inverse_of: :project
has_many :ci_refs, class_name: 'Ci::Ref', inverse_of: :project
# Ci::Build objects store data on the file system such as artifact files and
# build traces. Currently there's no efficient way of removing this data in
# bulk that doesn't involve loading the rows into memory. As a result we're
# still using `dependent: :destroy` here.
has_many :pending_builds, class_name: 'Ci::PendingBuild'
has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :builds, class_name: 'Ci::Build', inverse_of: :project
has_many :processables, class_name: 'Ci::Processable', inverse_of: :project
has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks
has_many :build_report_results, class_name: 'Ci::BuildReportResult', inverse_of: :project

View File

@ -41,7 +41,7 @@ class MemberEntity < Grape::Entity
expose :valid_level_roles, as: :valid_roles
expose :user, if: -> (member) { member.user.present? } do |member, options|
MemberUserEntity.represent(member.user, source: options[:source])
MemberUserEntity.represent(member.user, options)
end
expose :state

View File

@ -115,7 +115,7 @@ job1:
script:
- echo This rule uses parentheses.
rules:
if: ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH == "develop") && $MY_VARIABLE
- if: ($CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH || $CI_COMMIT_BRANCH == "develop") && $MY_VARIABLE
```
WARNING:

View File

@ -334,6 +334,14 @@ NOTE:
Specific information that follow related to Ruby and Git versions do not apply to [Omnibus installations](https://docs.gitlab.com/omnibus/)
and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with appropriate Ruby and Git versions and are not using system binaries for Ruby and Git. There is no need to install Ruby or Git when utilizing these two approaches.
### 14.7.0
- See [LFS objects import and mirror issue in GitLab 14.6.0 to 14.7.2](#lfs-objects-import-and-mirror-issue-in-gitlab-1460-to-1472).
### 14.6.0
- See [LFS objects import and mirror issue in GitLab 14.6.0 to 14.7.2](#lfs-objects-import-and-mirror-issue-in-gitlab-1460-to-1472).
### 14.5.0
- When `make` is run, Gitaly builds are now created in `_build/bin` and no longer in the root directory of the source directory. If you
@ -691,6 +699,12 @@ Users who were signed in before Maintenance mode was enabled will continue to be
[This bug](https://gitlab.com/gitlab-org/gitlab/-/issues/329261) was fixed in GitLab 14.5.0 and backported into 14.4.3 and 14.3.5.
### LFS objects import and mirror issue in GitLab 14.6.0 to 14.7.2
When Geo is enabled, LFS objects fail to be saved for imported or mirrored projects.
[This bug](https://gitlab.com/gitlab-org/gitlab/-/issues/352368) was fixed in GitLab 14.8.0 and backported into 14.7.3.
## Miscellaneous
- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating

View File

@ -10,6 +10,7 @@ module Gitlab
@pipeline = pipeline
@instance_variables_builder = Builder::Instance.new
@project_variables_builder = Builder::Project.new(project)
@group_variables_builder = Builder::Group.new(project.group)
end
def scoped_variables(job, environment:, dependencies:)
@ -72,9 +73,13 @@ module Gitlab
end
def secret_group_variables(environment:, ref:)
return [] unless project.group
if memoize_secret_variables?
memoized_secret_group_variables(environment: environment)
else
return [] unless project.group
project.group.ci_variables_for(ref, project, environment: environment)
project.group.ci_variables_for(ref, project, environment: environment)
end
end
def secret_project_variables(environment:, ref:)
@ -90,6 +95,7 @@ module Gitlab
attr_reader :pipeline
attr_reader :instance_variables_builder
attr_reader :project_variables_builder
attr_reader :group_variables_builder
delegate :project, to: :pipeline
def predefined_variables(job)
@ -119,6 +125,15 @@ module Gitlab
end
end
def memoized_secret_group_variables(environment:)
strong_memoize_with(:secret_group_variables, environment) do
group_variables_builder
.secret_variables(
environment: environment,
protected_ref: protected_ref?)
end
end
def ci_node_total_value(job)
parallel = job.options&.dig(:parallel)
parallel = parallel.dig(:total) if parallel.is_a?(Hash)

View File

@ -0,0 +1,48 @@
# frozen_string_literal: true
module Gitlab
module Ci
module Variables
class Builder
class Group
include Gitlab::Utils::StrongMemoize
def initialize(group)
@group = group
end
def secret_variables(environment:, protected_ref: false)
return [] unless group
variables = base_scope
variables = variables.unprotected unless protected_ref
variables = variables.for_environment(environment)
variables = variables.group_by(&:group_id)
variables = list_of_ids.reverse.flat_map { |group| variables[group.id] }.compact
Gitlab::Ci::Variables::Collection.new(variables)
end
private
attr_reader :group
def base_scope
strong_memoize(:base_scope) do
::Ci::GroupVariable.for_groups(list_of_ids)
end
end
def list_of_ids
strong_memoize(:list_of_ids) do
if group.root_ancestor.use_traversal_ids?
[group] + group.ancestors(hierarchy_order: :asc)
else
[group] + group.ancestors
end
end
end
end
end
end
end
end

View File

@ -4,8 +4,11 @@ module Gitlab
module Database
module EachDatabase
class << self
def each_database_connection
Gitlab::Database.database_base_models.each_pair do |connection_name, model|
def each_database_connection(only: nil)
selected_names = Array.wrap(only)
base_models = select_base_models(selected_names)
base_models.each_pair do |connection_name, model|
connection = model.connection
with_shared_connection(connection, connection_name) do
@ -28,6 +31,18 @@ module Gitlab
private
def select_base_models(names)
base_models = Gitlab::Database.database_base_models
return base_models if names.empty?
names.each_with_object(HashWithIndifferentAccess.new) do |name, hash|
raise ArgumentError, "#{name} is not a valid database name" unless base_models.key?(name)
hash[name] = base_models[name]
end
end
def with_shared_model_connections(shared_model, &blk)
Gitlab::Database.database_base_models.each_pair do |connection_name, connection_model|
if shared_model.limit_connection_names

View File

@ -8,8 +8,10 @@ namespace :dev do
ENV['force'] = 'yes'
Rake::Task["gitlab:setup"].invoke
# Make sure DB statistics are up to date.
ActiveRecord::Base.connection.execute('ANALYZE')
Gitlab::Database::EachDatabase.each_database_connection do |connection|
# Make sure DB statistics are up to date.
connection.execute('ANALYZE')
end
Rake::Task["gitlab:shell:setup"].invoke
end

View File

@ -4,30 +4,28 @@ databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
namespace :gitlab do
namespace :db do
desc 'GitLab | DB | Manually insert schema migration version'
desc 'GitLab | DB | Manually insert schema migration version on all configured databases'
task :mark_migration_complete, [:version] => :environment do |_, args|
mark_migration_complete(args[:version])
end
namespace :mark_migration_complete do
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |name|
desc "Gitlab | DB | Manually insert schema migration version on #{name} database"
task name, [:version] => :environment do |_, args|
mark_migration_complete(args[:version], database: name)
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |database|
desc "Gitlab | DB | Manually insert schema migration version on #{database} database"
task database, [:version] => :environment do |_, args|
mark_migration_complete(args[:version], only_on: database)
end
end
end
def mark_migration_complete(version, database: nil)
def mark_migration_complete(version, only_on: nil)
if version.to_i == 0
puts 'Must give a version argument that is a non-zero integer'.color(:red)
exit 1
end
Gitlab::Database.database_base_models.each do |name, model|
next if database && database.to_s != name
model.connection.execute("INSERT INTO schema_migrations (version) VALUES (#{model.connection.quote(version)})")
Gitlab::Database::EachDatabase.each_database_connection(only: only_on) do |connection, name|
connection.execute("INSERT INTO schema_migrations (version) VALUES (#{connection.quote(version)})")
puts "Successfully marked '#{version}' as complete on database #{name}".color(:green)
rescue ActiveRecord::RecordNotUnique
@ -35,32 +33,44 @@ namespace :gitlab do
end
end
desc 'GitLab | DB | Drop all tables'
desc 'GitLab | DB | Drop all tables on all configured databases'
task drop_tables: :environment do
connection = ActiveRecord::Base.connection
drop_tables
end
# In PostgreSQLAdapter, data_sources returns both views and tables, so use
# #tables instead
tables = connection.tables
# Removes the entry from the array
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
connection.execute('TRUNCATE schema_migrations') if connection.table_exists? 'schema_migrations'
# Drop any views
connection.views.each do |view|
connection.execute("DROP VIEW IF EXISTS #{connection.quote_table_name(view)} CASCADE")
namespace :drop_tables do
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |database|
desc "GitLab | DB | Drop all tables on the #{database} database"
task database => :environment do
drop_tables(only_on: database)
end
end
end
# Drop tables with cascade to avoid dependent table errors
# PG: http://www.postgresql.org/docs/current/static/ddl-depend.html
# Add `IF EXISTS` because cascade could have already deleted a table.
tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
def drop_tables(only_on: nil)
Gitlab::Database::EachDatabase.each_database_connection(only: only_on) do |connection, name|
# In PostgreSQLAdapter, data_sources returns both views and tables, so use tables instead
tables = connection.tables
# Drop all extra schema objects GitLab owns
Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
connection.execute("DROP SCHEMA IF EXISTS #{connection.quote_table_name(schema)} CASCADE")
# Removes the entry from the array
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
connection.execute('TRUNCATE schema_migrations') if connection.table_exists? 'schema_migrations'
# Drop any views
connection.views.each do |view|
connection.execute("DROP VIEW IF EXISTS #{connection.quote_table_name(view)} CASCADE")
end
# Drop tables with cascade to avoid dependent table errors
# PG: http://www.postgresql.org/docs/current/static/ddl-depend.html
# Add `IF EXISTS` because cascade could have already deleted a table.
tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
# Drop all extra schema objects GitLab owns
Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
connection.execute("DROP SCHEMA IF EXISTS #{connection.quote_table_name(schema)} CASCADE")
end
end
end
@ -208,8 +218,6 @@ namespace :gitlab do
end
namespace :reindex do
databases = ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
ActiveRecord::Tasks::DatabaseTasks.for_each(databases) do |database_name|
desc "Reindex #{database_name} database without downtime to eliminate bloat"
task database_name => :environment do

View File

@ -35570,6 +35570,12 @@ msgstr ""
msgid "SuperSonics|You do not have an active subscription"
msgstr ""
msgid "SuperSonics|You have a future dated license"
msgstr ""
msgid "SuperSonics|You have added a license that activates on %{date}. Please see the subscription history table below for more details."
msgstr ""
msgid "SuperSonics|You have successfully added a license that activates on %{date}. Please see the subscription history table below for more details."
msgstr ""

View File

@ -96,6 +96,30 @@ RSpec.describe PreferencesHelper do
end
end
describe '#user_application_dark_mode?' do
context 'with a user' do
it "returns true if user's selected dark theme" do
stub_user(theme_id: 11)
expect(helper.user_application_dark_mode?).to eq true
end
it "returns false if user's selected any light theme" do
stub_user(theme_id: 1)
expect(helper.user_application_dark_mode?).to eq false
end
end
context 'without a user' do
it 'returns false' do
stub_user
expect(helper.user_application_dark_mode?).to eq false
end
end
end
describe '#user_color_scheme' do
context 'with a user' do
it "returns user's scheme's css_class" do

View File

@ -0,0 +1,209 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Variables::Builder::Group do
let_it_be(:group) { create(:group) }
let(:builder) { described_class.new(group) }
describe '#secret_variables' do
let(:environment) { '*' }
let(:protected_ref) { false }
let_it_be(:variable) do
create(:ci_group_variable,
value: 'secret',
group: group)
end
let_it_be(:protected_variable) do
create(:ci_group_variable, :protected,
value: 'protected',
group: group)
end
let(:variable_item) { item(variable) }
let(:protected_variable_item) { item(protected_variable) }
subject do
builder.secret_variables(
environment: environment,
protected_ref: protected_ref)
end
context 'when the ref is not protected' do
let(:protected_ref) { false }
it 'contains only the CI variables' do
is_expected.to contain_exactly(variable_item)
end
end
context 'when the ref is protected' do
let(:protected_ref) { true }
it 'contains all the variables' do
is_expected.to contain_exactly(variable_item, protected_variable_item)
end
end
context 'when environment name is specified' do
let(:environment) { 'review/name' }
before do
Ci::GroupVariable.update_all(environment_scope: environment_scope)
end
context 'when environment scope is exactly matched' do
let(:environment_scope) { 'review/name' }
it { is_expected.to contain_exactly(variable_item) }
end
context 'when environment scope is matched by wildcard' do
let(:environment_scope) { 'review/*' }
it { is_expected.to contain_exactly(variable_item) }
end
context 'when environment scope does not match' do
let(:environment_scope) { 'review/*/special' }
it { is_expected.not_to contain_exactly(variable_item) }
end
context 'when environment scope has _' do
let(:environment_scope) { '*_*' }
it 'does not treat it as wildcard' do
is_expected.not_to contain_exactly(variable_item)
end
end
context 'when environment name contains underscore' do
let(:environment) { 'foo_bar/test' }
let(:environment_scope) { 'foo_bar/*' }
it 'matches literally for _' do
is_expected.to contain_exactly(variable_item)
end
end
# The environment name and scope cannot have % at the moment,
# but we're considering relaxing it and we should also make sure
# it doesn't break in case some data sneaked in somehow as we're
# not checking this integrity in database level.
context 'when environment scope has %' do
let(:environment_scope) { '*%*' }
it 'does not treat it as wildcard' do
is_expected.not_to contain_exactly(variable_item)
end
end
context 'when environment name contains a percent' do
let(:environment) { 'foo%bar/test' }
let(:environment_scope) { 'foo%bar/*' }
it 'matches literally for _' do
is_expected.to contain_exactly(variable_item)
end
end
end
context 'when variables with the same name have different environment scopes' do
let(:environment) { 'review/name' }
let_it_be(:partially_matched_variable) do
create(:ci_group_variable,
key: variable.key,
value: 'partial',
environment_scope: 'review/*',
group: group)
end
let_it_be(:perfectly_matched_variable) do
create(:ci_group_variable,
key: variable.key,
value: 'prefect',
environment_scope: 'review/name',
group: group)
end
it 'orders the variables from least to most matched' do
variables_collection = Gitlab::Ci::Variables::Collection.new([
variable,
partially_matched_variable,
perfectly_matched_variable
]).to_runner_variables
expect(subject.to_runner_variables).to eq(variables_collection)
end
end
context 'when group has children' do
let(:protected_ref) { true }
let_it_be(:group_child_1) { create(:group, parent: group) }
let_it_be(:group_child_2) { create(:group, parent: group_child_1) }
let_it_be_with_reload(:group_child_3) do
create(:group, parent: group_child_2)
end
let_it_be(:variable_child_1) do
create(:ci_group_variable, group: group_child_1)
end
let_it_be(:variable_child_2) do
create(:ci_group_variable, group: group_child_2)
end
let_it_be(:variable_child_3) do
create(:ci_group_variable, group: group_child_3)
end
context 'traversal queries' do
shared_examples 'correct ancestor order' do
let(:builder) { described_class.new(group_child_3) }
it 'returns all variables belonging to the group and parent groups' do
expected_array1 = Gitlab::Ci::Variables::Collection.new(
[protected_variable_item, variable_item])
.to_runner_variables
expected_array2 = Gitlab::Ci::Variables::Collection.new(
[variable_child_1, variable_child_2, variable_child_3]
).to_runner_variables
got_array = subject.to_runner_variables
expect(got_array.shift(2)).to contain_exactly(*expected_array1)
expect(got_array).to eq(expected_array2)
end
end
context 'recursive' do
before do
stub_feature_flags(use_traversal_ids: false)
end
include_examples 'correct ancestor order'
end
context 'linear' do
before do
stub_feature_flags(use_traversal_ids: true)
end
include_examples 'correct ancestor order'
end
end
end
end
def item(variable)
Gitlab::Ci::Variables::Collection::Item.fabricate(variable)
end
end

View File

@ -342,10 +342,88 @@ RSpec.describe Gitlab::Ci::Variables::Builder do
let_it_be(:protected_variable) { create(:ci_group_variable, protected: true, group: group) }
let_it_be(:unprotected_variable) { create(:ci_group_variable, protected: false, group: group) }
let(:protected_variable_item) { protected_variable }
let(:unprotected_variable_item) { unprotected_variable }
context 'with ci_variables_builder_memoize_secret_variables disabled' do
before do
stub_feature_flags(ci_variables_builder_memoize_secret_variables: false)
end
include_examples "secret CI variables"
let(:protected_variable_item) { protected_variable }
let(:unprotected_variable_item) { unprotected_variable }
include_examples "secret CI variables"
end
context 'with ci_variables_builder_memoize_secret_variables enabled' do
before do
stub_feature_flags(ci_variables_builder_memoize_secret_variables: true)
end
let(:protected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(protected_variable) }
let(:unprotected_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(unprotected_variable) }
include_examples "secret CI variables"
context 'variables memoization' do
let_it_be(:scoped_variable) { create(:ci_group_variable, group: group, environment_scope: 'scoped') }
let(:ref) { job.git_ref }
let(:environment) { job.expanded_environment_name }
let(:scoped_variable_item) { Gitlab::Ci::Variables::Collection::Item.fabricate(scoped_variable) }
context 'with protected environments' do
it 'memoizes the result by environment' do
expect(pipeline.project)
.to receive(:protected_for?)
.with(pipeline.jobs_git_ref)
.once.and_return(true)
expect_next_instance_of(described_class::Group) do |group_variables_builder|
expect(group_variables_builder)
.to receive(:secret_variables)
.with(environment: 'production', protected_ref: true)
.once
.and_call_original
end
2.times do
expect(builder.secret_group_variables(ref: ref, environment: 'production'))
.to contain_exactly(unprotected_variable_item, protected_variable_item)
end
end
end
context 'with unprotected environments' do
it 'memoizes the result by environment' do
expect(pipeline.project)
.to receive(:protected_for?)
.with(pipeline.jobs_git_ref)
.once.and_return(false)
expect_next_instance_of(described_class::Group) do |group_variables_builder|
expect(group_variables_builder)
.to receive(:secret_variables)
.with(environment: nil, protected_ref: false)
.once
.and_call_original
expect(group_variables_builder)
.to receive(:secret_variables)
.with(environment: 'scoped', protected_ref: false)
.once
.and_call_original
end
2.times do
expect(builder.secret_group_variables(ref: 'other', environment: nil))
.to contain_exactly(unprotected_variable_item)
expect(builder.secret_group_variables(ref: 'other', environment: 'scoped'))
.to contain_exactly(unprotected_variable_item, scoped_variable_item)
end
end
end
end
end
end
describe '#secret_project_variables' do

View File

@ -3,13 +3,13 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::EachDatabase do
describe '.each_database_connection' do
describe '.each_database_connection', :add_ci_connection do
before do
allow(Gitlab::Database).to receive(:database_base_models)
.and_return({ main: ActiveRecord::Base, ci: Ci::ApplicationRecord }.with_indifferent_access)
end
it 'yields each connection after connecting SharedModel', :add_ci_connection do
it 'yields each connection after connecting SharedModel' do
expect(Gitlab::Database::SharedModel).to receive(:using_connection)
.with(ActiveRecord::Base.connection).ordered.and_yield
@ -22,6 +22,42 @@ RSpec.describe Gitlab::Database::EachDatabase do
[Ci::ApplicationRecord.connection, 'ci']
)
end
context 'when only certain databases are selected' do
it 'yields the selected connections after connecting SharedModel' do
expect(Gitlab::Database::SharedModel).to receive(:using_connection)
.with(Ci::ApplicationRecord.connection).ordered.and_yield
expect { |b| described_class.each_database_connection(only: 'ci', &b) }
.to yield_successive_args([Ci::ApplicationRecord.connection, 'ci'])
end
context 'when the selected names are passed as symbols' do
it 'yields the selected connections after connecting SharedModel' do
expect(Gitlab::Database::SharedModel).to receive(:using_connection)
.with(Ci::ApplicationRecord.connection).ordered.and_yield
expect { |b| described_class.each_database_connection(only: :ci, &b) }
.to yield_successive_args([Ci::ApplicationRecord.connection, 'ci'])
end
end
context 'when the selected names are invalid' do
it 'does not yield any connections' do
expect do |b|
described_class.each_database_connection(only: :notvalid, &b)
rescue ArgumentError => e
expect(e.message).to match(/notvalid is not a valid database name/)
end.not_to yield_control
end
it 'raises an error' do
expect do
described_class.each_database_connection(only: :notvalid) {}
end.to raise_error(ArgumentError, /notvalid is not a valid database name/)
end
end
end
end
describe '.each_model_connection' do

View File

@ -43,6 +43,14 @@ RSpec.describe Ci::GroupVariable do
end
end
describe '.for_groups' do
let_it_be(:group) { create(:group) }
let_it_be(:group_variable) { create(:ci_group_variable, group: group) }
let_it_be(:other_variable) { create(:ci_group_variable) }
it { expect(described_class.for_groups([group.id])).to eq([group_variable]) }
end
it_behaves_like 'cleanup by a loose foreign key' do
let!(:model) { create(:ci_group_variable) }

View File

@ -6,10 +6,10 @@ RSpec.describe BatchDestroyDependentAssociations do
class TestProject < ActiveRecord::Base
self.table_name = 'projects'
has_many :builds, dependent: :destroy
has_many :builds
has_many :notification_settings, as: :source, dependent: :delete_all
has_many :pages_domains
has_many :todos
has_many :todos, dependent: :destroy
include BatchDestroyDependentAssociations
end
@ -18,7 +18,7 @@ RSpec.describe BatchDestroyDependentAssociations do
let_it_be(:project) { TestProject.new }
it 'returns the right associations' do
expect(project.dependent_associations_to_destroy.map(&:name)).to match_array([:builds])
expect(project.dependent_associations_to_destroy.map(&:name)).to match_array([:todos])
end
end
@ -26,36 +26,35 @@ RSpec.describe BatchDestroyDependentAssociations do
let_it_be(:project) { create(:project) }
let_it_be(:build) { create(:ci_build, project: project) }
let_it_be(:notification_setting) { create(:notification_setting, project: project) }
let_it_be(:note) { create(:note, project: project) }
let!(:todos) { create(:todo, project: project) }
it 'destroys multiple notes' do
create(:note, project: project)
it 'destroys multiple builds' do
create(:ci_build, project: project)
expect(Ci::Build.count).to eq(2)
expect(Note.count).to eq(2)
project.destroy_dependent_associations_in_batches
expect(Ci::Build.count).to eq(0)
expect(Note.count).to eq(0)
end
it 'destroys builds in batches' do
expect(project).to receive_message_chain(:builds, :find_each).and_yield(build)
expect(build).to receive(:destroy).and_call_original
it 'destroys note in batches' do
expect(project).to receive_message_chain(:notes, :find_each).and_yield(note)
expect(note).to receive(:destroy).and_call_original
project.destroy_dependent_associations_in_batches
expect(Ci::Build.count).to eq(0)
expect(Todo.count).to eq(1)
expect(Ci::Build.count).to eq(1)
expect(Note.count).to eq(0)
expect(User.count).to be > 0
expect(NotificationSetting.count).to eq(User.count)
end
it 'excludes associations' do
project.destroy_dependent_associations_in_batches(exclude: [:builds])
project.destroy_dependent_associations_in_batches(exclude: [:notes])
expect(Note.count).to eq(1)
expect(Ci::Build.count).to eq(1)
expect(Todo.count).to eq(1)
expect(User.count).to be > 0
expect(NotificationSetting.count).to eq(User.count)
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
require 'rake_helper'
RSpec.describe 'dev rake tasks' do
before do
Rake.application.rake_require 'tasks/gitlab/setup'
Rake.application.rake_require 'tasks/gitlab/shell'
Rake.application.rake_require 'tasks/dev'
end
describe 'setup' do
subject(:setup_task) { run_rake_task('dev:setup') }
let(:connections) { Gitlab::Database.database_base_models.values.map(&:connection) }
it 'sets up the development environment', :aggregate_failures do
expect(Rake::Task['gitlab:setup']).to receive(:invoke)
expect(connections).to all(receive(:execute).with('ANALYZE'))
expect(Rake::Task['gitlab:shell:setup']).to receive(:invoke)
setup_task
end
end
describe 'load' do
subject(:load_task) { run_rake_task('dev:load') }
it 'eager loads the application', :aggregate_failures do
expect(Rails.configuration).to receive(:eager_load=).with(true)
expect(Rails.application).to receive(:eager_load!)
load_task
end
end
end

View File

@ -261,45 +261,78 @@ RSpec.describe 'gitlab:db namespace rake task', :silence_stdout do
end
describe 'drop_tables' do
subject { run_rake_task('gitlab:db:drop_tables') }
let(:tables) { %w(one two) }
let(:tables) { %w(one two schema_migrations) }
let(:views) { %w(three four) }
let(:connection) { ActiveRecord::Base.connection }
let(:schemas) { Gitlab::Database::EXTRA_SCHEMAS }
before do
allow(connection).to receive(:execute).and_return(nil)
context 'with a single database' do
let(:connection) { ActiveRecord::Base.connection }
allow(connection).to receive(:tables).and_return(tables)
allow(connection).to receive(:views).and_return(views)
before do
skip_if_multiple_databases_are_setup
allow(connection).to receive(:execute).and_return(nil)
allow(connection).to receive(:tables).and_return(tables)
allow(connection).to receive(:views).and_return(views)
end
it 'drops all objects for the database', :aggregate_failures do
expect_objects_to_be_dropped(connection)
run_rake_task('gitlab:db:drop_tables')
end
end
it 'drops all tables, except schema_migrations' do
context 'with multiple databases', :aggregate_failures do
let(:main_model) { double(:model, connection: double(:connection, tables: tables, views: views)) }
let(:ci_model) { double(:model, connection: double(:connection, tables: tables, views: views)) }
let(:base_models) { { 'main' => main_model, 'ci' => ci_model } }
before do
skip_if_multiple_databases_not_setup
allow(Gitlab::Database).to receive(:database_base_models).and_return(base_models)
allow(main_model.connection).to receive(:table_exists?).with('schema_migrations').and_return(true)
allow(ci_model.connection).to receive(:table_exists?).with('schema_migrations').and_return(true)
(tables + views + schemas).each do |name|
allow(main_model.connection).to receive(:quote_table_name).with(name).and_return("\"#{name}\"")
allow(ci_model.connection).to receive(:quote_table_name).with(name).and_return("\"#{name}\"")
end
end
it 'drops all objects for all databases', :aggregate_failures do
expect_objects_to_be_dropped(main_model.connection)
expect_objects_to_be_dropped(ci_model.connection)
run_rake_task('gitlab:db:drop_tables')
end
context 'when the single database task is used' do
it 'drops all objects for the given database', :aggregate_failures do
expect_objects_to_be_dropped(main_model.connection)
expect(ci_model.connection).not_to receive(:execute)
run_rake_task('gitlab:db:drop_tables:main')
end
end
end
def expect_objects_to_be_dropped(connection)
expect(connection).to receive(:execute).with('DROP TABLE IF EXISTS "one" CASCADE')
expect(connection).to receive(:execute).with('DROP TABLE IF EXISTS "two" CASCADE')
subject
end
it 'drops all views' do
expect(connection).to receive(:execute).with('DROP VIEW IF EXISTS "three" CASCADE')
expect(connection).to receive(:execute).with('DROP VIEW IF EXISTS "four" CASCADE')
subject
end
it 'truncates schema_migrations table' do
expect(connection).to receive(:execute).with('TRUNCATE schema_migrations')
subject
end
it 'drops extra schemas' do
Gitlab::Database::EXTRA_SCHEMAS.each do |schema|
expect(connection).to receive(:execute).with("DROP SCHEMA IF EXISTS \"#{schema}\" CASCADE")
end
subject
end
end