Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-11-06 21:10:28 +00:00
parent 2644e59eb5
commit a303eb5d32
50 changed files with 800 additions and 78 deletions

View File

@ -402,7 +402,7 @@ end
group :development, :test do group :development, :test do
gem 'deprecation_toolkit', '~> 1.5.1', require: false # rubocop:todo Gemfile/MissingFeatureCategory gem 'deprecation_toolkit', '~> 1.5.1', require: false # rubocop:todo Gemfile/MissingFeatureCategory
gem 'bullet', '~> 7.1.1' # rubocop:todo Gemfile/MissingFeatureCategory gem 'bullet', '~> 7.1.2' # rubocop:todo Gemfile/MissingFeatureCategory
gem 'parser', '~> 3.2', '>= 3.2.2.4' # rubocop:todo Gemfile/MissingFeatureCategory gem 'parser', '~> 3.2', '>= 3.2.2.4' # rubocop:todo Gemfile/MissingFeatureCategory
gem 'pry-byebug' # rubocop:todo Gemfile/MissingFeatureCategory gem 'pry-byebug' # rubocop:todo Gemfile/MissingFeatureCategory
gem 'pry-rails', '~> 0.3.9' # rubocop:todo Gemfile/MissingFeatureCategory gem 'pry-rails', '~> 0.3.9' # rubocop:todo Gemfile/MissingFeatureCategory
@ -434,7 +434,7 @@ group :development, :test do
gem 'knapsack', '~> 1.21.1', feature_category: :tooling gem 'knapsack', '~> 1.21.1', feature_category: :tooling
gem 'crystalball', '~> 0.7.0', require: false, feature_category: :tooling gem 'crystalball', '~> 0.7.0', require: false, feature_category: :tooling
gem 'test_file_finder', '~> 0.1.3', feature_category: :tooling gem 'test_file_finder', '~> 0.2.1', feature_category: :tooling
gem 'simple_po_parser', '~> 1.1.6', require: false # rubocop:todo Gemfile/MissingFeatureCategory gem 'simple_po_parser', '~> 1.1.6', require: false # rubocop:todo Gemfile/MissingFeatureCategory

View File

@ -64,7 +64,7 @@
{"name":"bootsnap","version":"1.17.0","platform":"ruby","checksum":"6b0ea4dd68f0d424968dcd13953c3f04b13a19a8761c540d3af13507fcfa1347"}, {"name":"bootsnap","version":"1.17.0","platform":"ruby","checksum":"6b0ea4dd68f0d424968dcd13953c3f04b13a19a8761c540d3af13507fcfa1347"},
{"name":"browser","version":"5.3.1","platform":"ruby","checksum":"62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c"}, {"name":"browser","version":"5.3.1","platform":"ruby","checksum":"62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c"},
{"name":"builder","version":"3.2.4","platform":"ruby","checksum":"99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10"}, {"name":"builder","version":"3.2.4","platform":"ruby","checksum":"99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10"},
{"name":"bullet","version":"7.1.1","platform":"ruby","checksum":"ad7789d9ad2bfe772f96620ba8f927e756c74525f2c03e7843d3518ce50e5b9c"}, {"name":"bullet","version":"7.1.2","platform":"ruby","checksum":"429725c174cb74ca0ae99b9720bf22cab80be59ee9401805f7ecc9ac62cbb3bb"},
{"name":"bundler-audit","version":"0.9.1","platform":"ruby","checksum":"bdc716fc21cd8652a6507b137e5bc51f5e0e4f6f106a114ab004c89d0200bd3d"}, {"name":"bundler-audit","version":"0.9.1","platform":"ruby","checksum":"bdc716fc21cd8652a6507b137e5bc51f5e0e4f6f106a114ab004c89d0200bd3d"},
{"name":"byebug","version":"11.1.3","platform":"ruby","checksum":"2485944d2bb21283c593d562f9ae1019bf80002143cc3a255aaffd4e9cf4a35b"}, {"name":"byebug","version":"11.1.3","platform":"ruby","checksum":"2485944d2bb21283c593d562f9ae1019bf80002143cc3a255aaffd4e9cf4a35b"},
{"name":"capybara","version":"3.39.2","platform":"ruby","checksum":"d6f0ca5f30897e64789428d4b047a0df105815a302069913578ac35d5ca99884"}, {"name":"capybara","version":"3.39.2","platform":"ruby","checksum":"d6f0ca5f30897e64789428d4b047a0df105815a302069913578ac35d5ca99884"},
@ -637,7 +637,7 @@
{"name":"terminal-table","version":"3.0.2","platform":"ruby","checksum":"f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91"}, {"name":"terminal-table","version":"3.0.2","platform":"ruby","checksum":"f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91"},
{"name":"terser","version":"1.0.2","platform":"ruby","checksum":"80c2e0bc7e2db4e12e8529658f9e0820e13d685ae67d745bf981f269743bb28e"}, {"name":"terser","version":"1.0.2","platform":"ruby","checksum":"80c2e0bc7e2db4e12e8529658f9e0820e13d685ae67d745bf981f269743bb28e"},
{"name":"test-prof","version":"1.2.3","platform":"ruby","checksum":"c52a40194cb30f399ed3eb6beb4c45b5daad8b8eb418e8ef69089e4dc7e01fd6"}, {"name":"test-prof","version":"1.2.3","platform":"ruby","checksum":"c52a40194cb30f399ed3eb6beb4c45b5daad8b8eb418e8ef69089e4dc7e01fd6"},
{"name":"test_file_finder","version":"0.1.4","platform":"ruby","checksum":"bc36d8339eac4fb9dc36514a7c5f4d389ac2fb6d010716fc715c5c8fbb98eacd"}, {"name":"test_file_finder","version":"0.2.1","platform":"ruby","checksum":"a5e9b369d80c76aefbb609acf5e11d89a048f35e565de3cc261c20112f0fcdb3"},
{"name":"text","version":"1.3.1","platform":"ruby","checksum":"2fbbbc82c1ce79c4195b13018a87cbb00d762bda39241bb3cdc32792759dd3f4"}, {"name":"text","version":"1.3.1","platform":"ruby","checksum":"2fbbbc82c1ce79c4195b13018a87cbb00d762bda39241bb3cdc32792759dd3f4"},
{"name":"thor","version":"1.2.2","platform":"ruby","checksum":"2f93c652828cba9fcf4f65f5dc8c306f1a7317e05aad5835a13740122c17f24c"}, {"name":"thor","version":"1.2.2","platform":"ruby","checksum":"2f93c652828cba9fcf4f65f5dc8c306f1a7317e05aad5835a13740122c17f24c"},
{"name":"thread_safe","version":"0.3.6","platform":"java","checksum":"bb28394cd0924c068981adee71f36a81c85c92e7d74d3f62372bd51489a0e0c2"}, {"name":"thread_safe","version":"0.3.6","platform":"java","checksum":"bb28394cd0924c068981adee71f36a81c85c92e7d74d3f62372bd51489a0e0c2"},

View File

@ -329,7 +329,7 @@ GEM
msgpack (~> 1.2) msgpack (~> 1.2)
browser (5.3.1) browser (5.3.1)
builder (3.2.4) builder (3.2.4)
bullet (7.1.1) bullet (7.1.2)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.11) uniform_notifier (~> 1.11)
bundler-audit (0.9.1) bundler-audit (0.9.1)
@ -1596,8 +1596,8 @@ GEM
terser (1.0.2) terser (1.0.2)
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
test-prof (1.2.3) test-prof (1.2.3)
test_file_finder (0.1.4) test_file_finder (0.2.1)
faraday (~> 1.0) faraday (>= 1.0, < 3.0, != 2.0.0)
text (1.3.1) text (1.3.1)
thor (1.2.2) thor (1.2.2)
thread_safe (0.3.6) thread_safe (0.3.6)
@ -1771,7 +1771,7 @@ DEPENDENCIES
better_errors (~> 2.10.1) better_errors (~> 2.10.1)
bootsnap (~> 1.17.0) bootsnap (~> 1.17.0)
browser (~> 5.3.1) browser (~> 5.3.1)
bullet (~> 7.1.1) bullet (~> 7.1.2)
bundler-audit (~> 0.9.1) bundler-audit (~> 0.9.1)
bundler-checksum (~> 0.1.0)! bundler-checksum (~> 0.1.0)!
capybara (~> 3.39, >= 3.39.2) capybara (~> 3.39, >= 3.39.2)
@ -2036,7 +2036,7 @@ DEPENDENCIES
telesignenterprise (~> 2.2) telesignenterprise (~> 2.2)
terser (= 1.0.2) terser (= 1.0.2)
test-prof (~> 1.2.3) test-prof (~> 1.2.3)
test_file_finder (~> 0.1.3) test_file_finder (~> 0.2.1)
thrift (>= 0.16.0) thrift (>= 0.16.0)
timfel-krb5-auth (~> 0.8) timfel-krb5-auth (~> 0.8)
toml-rb (~> 2.2.0) toml-rb (~> 2.2.0)

View File

@ -3,10 +3,11 @@
module Projects module Projects
module Ml module Ml
class ModelsIndexComponent < ViewComponent::Base class ModelsIndexComponent < ViewComponent::Base
attr_reader :paginator attr_reader :paginator, :model_count
def initialize(paginator:) def initialize(paginator:, model_count:)
@paginator = paginator @paginator = paginator
@model_count = model_count
end end
private private
@ -14,7 +15,8 @@ module Projects
def view_model def view_model
vm = { vm = {
models: models_view_model, models: models_view_model,
page_info: page_info_view_model page_info: page_info_view_model,
model_count: model_count
} }
Gitlab::Json.generate(vm.deep_transform_keys { |k| k.to_s.camelize(:lower) }) Gitlab::Json.generate(vm.deep_transform_keys { |k| k.to_s.camelize(:lower) })
@ -26,7 +28,8 @@ module Projects
name: m.name, name: m.name,
version: m.latest_version_name, version: m.latest_version_name,
version_count: m.version_count, version_count: m.version_count,
path: m.latest_package_path version_package_path: m.latest_package_path,
version_path: m.latest_version_path
} }
end end
end end

View File

@ -15,9 +15,11 @@ module Projects
.transform_keys(&:underscore) .transform_keys(&:underscore)
.permit(:name, :order_by, :sort) .permit(:name, :order_by, :sort)
@paginator = ::Projects::Ml::ModelFinder.new(@project, find_params) finder = ::Projects::Ml::ModelFinder.new(@project, find_params)
.execute
.keyset_paginate(cursor: params[:cursor], per_page: MAX_MODELS_PER_PAGE) @paginator = finder.execute.keyset_paginate(cursor: params[:cursor], per_page: MAX_MODELS_PER_PAGE)
@model_count = finder.count
end end
def show; end def show; end

View File

@ -3,6 +3,8 @@
module Projects module Projects
module Ml module Ml
class ModelFinder class ModelFinder
include Gitlab::Utils::StrongMemoize
VALID_ORDER_BY = %w[name created_at id].freeze VALID_ORDER_BY = %w[name created_at id].freeze
VALID_SORT = %w[asc desc].freeze VALID_SORT = %w[asc desc].freeze
@ -12,16 +14,26 @@ module Projects
end end
def execute def execute
relation
end
def count
relation.length
end
private
def relation
@models = ::Ml::Model @models = ::Ml::Model
.by_project(project) .by_project(project)
.including_latest_version .including_latest_version
.with_version_count .including_project
.with_version_count
@models = by_name @models = by_name
ordered ordered
end end
strong_memoize_attr :relation
private
def by_name def by_name
return models unless params[:name].present? return models unless params[:name].present?

View File

@ -22,6 +22,7 @@ module Ml
has_one :latest_version, -> { latest_by_model }, class_name: 'Ml::ModelVersion', inverse_of: :model has_one :latest_version, -> { latest_by_model }, class_name: 'Ml::ModelVersion', inverse_of: :model
scope :including_latest_version, -> { includes(:latest_version) } scope :including_latest_version, -> { includes(:latest_version) }
scope :including_project, -> { includes(:project) }
scope :with_version_count, -> { scope :with_version_count, -> {
left_outer_joins(:versions) left_outer_joins(:versions)
.select("ml_models.*, count(ml_model_versions.id) as version_count") .select("ml_models.*, count(ml_model_versions.id) as version_count")

View File

@ -5,7 +5,7 @@ module Ml
presents ::Ml::Model, as: :model presents ::Ml::Model, as: :model
def latest_version_name def latest_version_name
model.latest_version&.version latest_version&.version
end end
def version_count def version_count
@ -15,13 +15,21 @@ module Ml
end end
def latest_package_path def latest_package_path
return unless model.latest_version&.package_id.present? latest_version&.package_path
end
Gitlab::Routing.url_helpers.project_package_path(model.project, model.latest_version.package_id) def latest_version_path
latest_version&.path
end end
def path def path
Gitlab::Routing.url_helpers.project_ml_model_path(model.project, model.id) project_ml_model_path(model.project, model.id)
end
private
def latest_version
model.latest_version&.present
end end
end end
end end

View File

@ -9,11 +9,17 @@ module Ml
end end
def path def path
Gitlab::Routing.url_helpers.project_ml_model_version_path( project_ml_model_version_path(
model_version.model.project, model_version.model.project,
model_version.model, model_version.model,
model_version model_version
) )
end end
def package_path
return unless model_version.package_id.present?
project_package_path(model_version.project, model_version.package_id)
end
end end
end end

View File

@ -9,7 +9,12 @@ module MergeRequests
def initialize(project:, current_user:, changes:, push_options:, params: {}) def initialize(project:, current_user:, changes:, push_options:, params: {})
super(project: project, current_user: current_user, params: params) super(project: project, current_user: current_user, params: params)
@target_project = @project.default_merge_request_target @target_project = if push_options[:target_project]
Project.find_by_full_path(push_options[:target_project])
else
@project.default_merge_request_target
end
@changes = Gitlab::ChangesList.new(changes) @changes = Gitlab::ChangesList.new(changes)
@push_options = push_options @push_options = push_options
@errors = [] @errors = []
@ -63,6 +68,10 @@ module MergeRequests
return return
end end
unless project == target_project || project.in_fork_network_of?(target_project)
errors << "Projects #{project.full_path} and #{target_project.full_path} are not in the same network"
end
unless target_project.merge_requests_enabled? unless target_project.merge_requests_enabled?
errors << "Merge requests are not enabled for project #{target_project.full_path}" errors << "Merge requests are not enabled for project #{target_project.full_path}"
end end

View File

@ -1,4 +1,4 @@
- breadcrumb_title s_('ModelRegistry|Model registry') - breadcrumb_title s_('ModelRegistry|Model registry')
- page_title s_('ModelRegistry|Model registry') - page_title s_('ModelRegistry|Model registry')
= render(Projects::Ml::ModelsIndexComponent.new(paginator: @paginator)) = render(Projects::Ml::ModelsIndexComponent.new(paginator: @paginator, model_count: @model_count))

View File

@ -0,0 +1,7 @@
---
migration_job_name: DeleteInvalidProtectedBranchMergeAccessLevels
description: Remove rows from protected_branch_merge_access_levels for groups that do not have project_group_links to the project for the associated protected branch
feature_category: source_code_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/427486
milestone: 16.6
queued_migration_version: 20231016173129

View File

@ -0,0 +1,7 @@
---
migration_job_name: DeleteInvalidProtectedBranchPushAccessLevels
description: Remove rows from protected_branch_push_access_levels for groups that do not have project_group_links to the project for the associated protected branch
feature_category: source_code_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/427486
milestone: 16.6
queued_migration_version: 20231016194927

View File

@ -0,0 +1,7 @@
---
migration_job_name: DeleteInvalidProtectedTagCreateAccessLevels
description: Remove rows from protected_tag_create_access_levels for groups that do not have project_group_links to the project for the associated protected tag
feature_category: source_code_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/issues/427486
milestone: 16.6
queued_migration_version: 20231016194943

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddTemporaryIndexToMergeAccessLevels < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '16.6'
INDEX_NAME = 'tmp_idx_protected_branch_merge_access_levels_on_id_with_group'
def up
# Temporary index to be removed in 16.7 https://gitlab.com/gitlab-org/gitlab/-/issues/430843
add_concurrent_index(
:protected_branch_merge_access_levels,
%i[id],
where: 'group_id IS NOT NULL',
name: INDEX_NAME
)
end
def down
remove_concurrent_index_by_name(
:protected_branch_merge_access_levels,
INDEX_NAME
)
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
class QueueDeleteInvalidProtectedBranchMergeAccessLevels < Gitlab::Database::Migration[2.1]
MIGRATION = "DeleteInvalidProtectedBranchMergeAccessLevels"
DELAY_INTERVAL = 2.minutes
MAX_BATCH_SIZE = 10_000
BATCH_SIZE = 5_000
SUB_BATCH_SIZE = 500
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
queue_batched_background_migration(
MIGRATION,
:protected_branch_merge_access_levels,
:id,
job_interval: DELAY_INTERVAL,
queued_migration_version: '20231016173129',
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :protected_branch_merge_access_levels, :id, [])
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddTemporaryIndexToPushAccessLevels < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '16.6'
INDEX_NAME = 'tmp_idx_protected_branch_push_access_levels_on_id_with_group'
def up
# Temporary index to be removed in 16.7 https://gitlab.com/gitlab-org/gitlab/-/issues/430843
add_concurrent_index(
:protected_branch_push_access_levels,
%i[id],
where: 'group_id IS NOT NULL',
name: INDEX_NAME
)
end
def down
remove_concurrent_index_by_name(
:protected_branch_push_access_levels,
INDEX_NAME
)
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
class QueueDeleteInvalidProtectedBranchPushAccessLevels < Gitlab::Database::Migration[2.1]
MIGRATION = "DeleteInvalidProtectedBranchPushAccessLevels"
DELAY_INTERVAL = 2.minutes
MAX_BATCH_SIZE = 10_000
BATCH_SIZE = 5_000
SUB_BATCH_SIZE = 500
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
queue_batched_background_migration(
MIGRATION,
:protected_branch_push_access_levels,
:id,
job_interval: DELAY_INTERVAL,
queued_migration_version: '20231016194927',
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :protected_branch_push_access_levels, :id, [])
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddTemporaryIndexToCreateAccessLevels < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '16.6'
INDEX_NAME = 'tmp_idx_protected_tag_create_access_levels_on_id_with_group'
def up
# Temporary index to be removed in 16.7 https://gitlab.com/gitlab-org/gitlab/-/issues/430843
add_concurrent_index(
:protected_tag_create_access_levels,
%i[id],
where: 'group_id IS NOT NULL',
name: INDEX_NAME
)
end
def down
remove_concurrent_index_by_name(
:protected_tag_create_access_levels,
INDEX_NAME
)
end
end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
class QueueDeleteInvalidProtectedTagCreateAccessLevels < Gitlab::Database::Migration[2.1]
MIGRATION = "DeleteInvalidProtectedTagCreateAccessLevels"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 10_000
SUB_BATCH_SIZE = 500
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
queue_batched_background_migration(
MIGRATION,
:protected_tag_create_access_levels,
:id,
job_interval: DELAY_INTERVAL,
queued_migration_version: '20231016194943',
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :protected_tag_create_access_levels, :id, [])
end
end

View File

@ -0,0 +1 @@
b720259efa4eb9fe75a3352a64b9e14ae7b048240daf34c40a66cc5ef409dcc0

View File

@ -0,0 +1 @@
f886678df9907d2bf60f98c0184c91604069cd613b541a0476e30789f327df15

View File

@ -0,0 +1 @@
0f2e4b7fc2658b5063dbe8dea6c881fb59a9d99ed53332ae1bdb5578343c3e89

View File

@ -0,0 +1 @@
44474805c7858d07d093650e43f3313746976e4c523b408d029e32829a5b7301

View File

@ -0,0 +1 @@
43de9dd5e63a80c51aa21e42b7f41d03b6d36143afa45cf45ead6ee0cc8152cc

View File

@ -0,0 +1 @@
b17f7eaff454fab3e46e438d81fdebab14776322af261e0f2a12ceb69a5623a8

View File

@ -35071,6 +35071,12 @@ CREATE INDEX tmp_idx_orphaned_approval_merge_request_rules ON approval_merge_req
CREATE INDEX tmp_idx_orphaned_approval_project_rules ON approval_project_rules USING btree (id) WHERE ((report_type = ANY (ARRAY[2, 4])) AND (security_orchestration_policy_configuration_id IS NULL)); CREATE INDEX tmp_idx_orphaned_approval_project_rules ON approval_project_rules USING btree (id) WHERE ((report_type = ANY (ARRAY[2, 4])) AND (security_orchestration_policy_configuration_id IS NULL));
CREATE INDEX tmp_idx_protected_branch_merge_access_levels_on_id_with_group ON protected_branch_merge_access_levels USING btree (id) WHERE (group_id IS NOT NULL);
CREATE INDEX tmp_idx_protected_branch_push_access_levels_on_id_with_group ON protected_branch_push_access_levels USING btree (id) WHERE (group_id IS NOT NULL);
CREATE INDEX tmp_idx_protected_tag_create_access_levels_on_id_with_group ON protected_tag_create_access_levels USING btree (id) WHERE (group_id IS NOT NULL);
CREATE INDEX tmp_index_ci_job_artifacts_on_expire_at_where_locked_unknown ON ci_job_artifacts USING btree (expire_at, job_id) WHERE ((locked = 2) AND (expire_at IS NOT NULL)); CREATE INDEX tmp_index_ci_job_artifacts_on_expire_at_where_locked_unknown ON ci_job_artifacts USING btree (expire_at, job_id) WHERE ((locked = 2) AND (expire_at IS NOT NULL));
CREATE INDEX tmp_index_cis_vulnerability_reads_on_id ON vulnerability_reads USING btree (id) WHERE (report_type = 7); CREATE INDEX tmp_index_cis_vulnerability_reads_on_id ON vulnerability_reads USING btree (id) WHERE (report_type = 7);

View File

@ -120,51 +120,46 @@ However, Service-Integration will establish certain necessary and optional requi
###### Ease of Use, Ownership Requirements ###### Ease of Use, Ownership Requirements
1. <a name="R100">`R100`</a>: Required: the platform should be easy to use: imagine Heroku with [GitLab Production Readiness-approved](https://about.gitlab.com/handbook/engineering/infrastructure/production/readiness/) defaults. | ID | Required | Detail | Epic/Issue | Done? |
1. <a name="R110">`R110`</a>: Required: with the exception of an Infrastructure-led onboarding process, services are owned, deployed and managed by stage-group teams. In other words,services follow a "You Build It, You Run It" model of ownership. |---|---|---|---|---|
1. <a name="R120">`R120`</a>: Required: programming-language agnostic: no requirements for services. Services should be packaged as container images. | `R100` | Required | The platform should be easy to use: imagine Heroku with [GitLab Production Readiness-approved](https://about.gitlab.com/handbook/engineering/infrastructure/production/readiness/) defaults. | [Runway to [BETA] : Increased Adoption and Self Service](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/1115) | **{dotted-circle}** No |
1. <a name="R130">`R130`</a>: Recommended: Each service should be evaluated against the GitLab.com [Service Maturity Model](https://about.gitlab.com/handbook/engineering/infrastructure/service-maturity-model/). | `R110` | Required | With the exception of an Infrastructure-led onboarding process, services are owned, deployed and managed by stage-group teams. In other words,services follow a “You Build It, You Run It” model of ownership.| [[Paused] Discussion: Tiered Support Model for Runway](https://gitlab.com/gitlab-com/gl-infra/platform/runway/team/-/issues/97) | **{dotted-circle}** No |
1. <a name="R140">`R140`</a>: Recommended: services using the platform have expedited production-readiness processes. | `R120` | Required | Programming-language agnostic: no requirements for services. Services should be packaged as container images.| [Runway to [BETA] : Increased Adoption and Self Service](https://gitlab.com/groups/gitlab-com/gl-infra/-/epics/1115) | **{dotted-circle}** No |
1. Production-readiness requirements graded by service maturity: low-traffic, low-maturity experimental services will have lower requirement thresholds than more mature services. | `R130` | Recommended | Each service should be evaluated against the GitLab.com [Service Maturity Model](https://about.gitlab.com/handbook/engineering/infrastructure/service-maturity-model/).| [Discussion: Introduce an 'Infrastructure Well-Architected Service Framework'](https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/2537) | **{dotted-circle}** No |
1. By default, the platform should provide services with defaults that would pass production-readiness review for the lowest service maturity-level. | `R140` | Recommended | Services using the platform have expedited production-readiness processes. {::nomarkdown}<ol><li>Production-readiness requirements graded by service maturity: low-traffic, low-maturity experimental services will have lower requirement thresholds than more mature services. </li><li> By default, the platform should provide services with defaults that would pass production-readiness review for the lowest service maturity-level. </li><li> At introduction, lowest maturity services can be deployed without production readiness, provided the meet certain automatically validated requirements. This removes Infrastructure gate-keeping from being a blocker to experimental service delivery.</li></ol>{:/} | | |
1. At introduction, lowest maturity services can be deployed without production readiness, provided the meet certain automatically validated requirements. This removes Infrastructure gate-keeping from being a blocker to experimental service delivery.
###### Observability Requirements ###### Observability Requirements
1. <a name="R200">`R200`</a>: Required: the platform must provide SLIs for services out-of-the-box. | ID | Required | Detail | Epic/Issue | Done? |
1. While it is recommended that services expose internal metrics, it is not mandatory. The platform will provide monitoring from the load-balancer. This is to speed up deployment by removing barriers to experimentation. |---|---|---|---|---|
1. For services that provide internal metrics scrape endpoints, the platform must be configurable to collect these. | `R200` | Required | The platform must provide SLIs for services out-of-the-box.{::nomarkdown}<ol><li>While it is recommended that services expose internal metrics, it is not mandatory. The platform will provide monitoring from the load-balancer. This is to speed up deployment by removing barriers to experimentation.</li><li>For services that provide internal metrics scrape endpoints, the platform must be configurable to collect these.</li><li>The platform must provide generic load-balancer level SLIs for all services. Service owners must be able to select from constructing SLIs from internal application metrics, the platform-provided external SLIs, or a combination of both.</li></ol>{:/} | [Observability: Default Metrics](https://gitlab.com/gitlab-com/gl-infra/platform/runway/team/-/issues/72), [Observability: Custom Metrics](https://gitlab.com/gitlab-com/gl-infra/platform/runway/team/-/issues/67) | **{check-circle}** Yes |
1. The platform must provide generic load-balancer level SLIs for all services. Service owners must be able to select from constructing SLIs from internal application metrics, the platform-provided external SLIs, or a combination of both. | `R210` | Required | Observability dashboards, rules, alerts (with per-term routing) must be generated from a manifest. | [Observability: Metrics Catalog](https://gitlab.com/gitlab-com/gl-infra/platform/runway/team/-/issues/74) | **{check-circle}** Yes |
1. <a name="R210">`R210`</a>: Required: Observability dashboards, rules, alerts (with per-term routing) must be generated from a manifest. | `R220` | Required | Standardized logging infrastructure.{::nomarkdown}<ol><li>Mandate that all logging emitted from services must be Structured JSON. Text logs are permitted but not recommended.</li><li>See <a href="#common-service-libraries">Common Service Libraries</a> for more details of building common SDKs for observability.</li></ol>{:/} | [Observability: Logs in Elasticsearch for model-gateway](https://gitlab.com/gitlab-com/gl-infra/platform/runway/team/-/issues/75), [Observability: Runway logs available to users](https://gitlab.com/gitlab-com/gl-infra/platform/runway/team/-/issues/84) | |
1. <a name="R220">`R220`</a>:Required: standardized logging infrastructure.
1. Mandate that all logging emitted from services must be Structured JSON. Text logs are permitted but not recommended.
1. See [Common Service Libraries](#common-service-libraries) for more details of building common SDKs for observability.
###### Deployment Requirements ###### Deployment Requirements
1. <a name="R300">`R300`</a>: Required: No secrets stored in CI/CD. | ID | Required | Detail | Epic/Issue | Done? |
1. Authentication with Cloud Provider Resources should be exclusively via OIDC, managed as part of the platform. |---|---|---|---|---|
1. Secrets should be stored in the Infrastructure-provided Hashicorp Vault for the environment and passed to applications through files or environment variables. | `R300` | Required | No secrets stored in CI/CD. {::nomarkdown} <ol><li>Authentication with Cloud Provider Resources should be exclusively via OIDC, managed as part of the platform.</li><li> Secrets should be stored in the Infrastructure-provided Hashicorp Vault for the environment and passed to applications through files or environment variables. </li><li>Generation and management of service account tokens should be done declaratively, without manual interaction.</li></ul>{:/} | [Secrets Management](https://gitlab.com/gitlab-com/gl-infra/platform/runway/team/-/issues/52) | **{dotted-circle}** No |
1. Generation and management of service account tokens should be done declaratively, without manual interaction. | `R310` | Required | Multiple environment should be supported, eg Staging and Production. | | **{check-circle}** Yes |
1. <a name="R310">`R310`</a>: Required: multiple environment should be supported, eg Staging and Production. | `R320` | Required | The platform should be cost-effective. Kubernetes clusters should support multiple services and teams. | | |
1. <a name="R320">`R320`</a>: Required: the platform should be cost-effective. Kubernetes clusters should support multiple services and teams. | `R330` | Recommended | Gradual rollouts, rollbacks, blue-green deployments. | | |
1. <a name="R330">`R330`</a>: Recommended: gradual rollouts, rollbacks, blue-green deployments. | `R340` | Required | Services should be isolated from one another. | | |
1. <a name="R340">`R340`</a>: Required: services should be isolated from one another. | `R350` | Recommended | Services should have the ability to specify node characteristic requirements (eg, GPU). | | |
1. <a name="R350">`R350`</a>: Recommended: services should have the ability to specify node characteristic requirements (eg, GPU). | `R360` | Required | Developers should not need knowledge of Helm, Kubernetes, Prometheus in order to deploy. All required values are configured and validated in project-hosted manifest before generating Kubernetes manifests, Prometheus rules, etc. | | |
1. <a name="R360">`R360`</a>: Required: Developers should not need knowledge of Helm, Kubernetes, Prometheus in order to deploy. All required values are configured and validated in project-hosted manifest before generating Kubernetes manifests, Prometheus rules, etc. | `R370` | | Initially services should be synchronous only - using REST or GRPC requests.{::nomarkdown}<ol><li>This does not however preclude long-running HTTP(s) requests, for example long-polling or Websocket requests.</li></ol>{:/} | | |
1. <a name="R370">`R370`</a>: Initially services should be synchronous only - using REST or GRPC requests. | `R390` | | Each service hosted in its own GitLab repository with deployment manifest stored in the repository. {::nomarkdown}<ol><li>Continuous deployments that are initiated from the CI pipeline of the corresponding GitLab repository.</li></ol>{:/} | | |
1. This does not however preclude long-running HTTP(s) requests, for example long-polling or Websocket requests.
1. <a name="R390">`R390`</a>: Each service hosted in its own GitLab repository with deployment manifest stored in the repository.
1. Continuous deployments that are initiated from the CI pipeline of the corresponding GitLab repository.
##### Security Requirements ##### Security Requirements
1. <a name="R400">`R400`</a>: stateful services deployed on the platform that utilize their own stateful storage (for example, custom deployed Postgres instance), must not store application security tokens, cloud-provider service keys or other long-lived security tokens in their stateful stores. | ID | Required | Detail | Epic/Issue | Done? |
1. <a name="R410">`R410`</a>: long-lived shared secrets are discouraged, and should be referenced in the service manifest as such, to allow for accounting and monitoring. |---|---|---|---|---|
1. <a name="R420">`R420`</a>: services using long-lived shared secrets should ensure that secret rotation can take place without downtime. | `R400` | | Stateful services deployed on the platform that utilize their own stateful storage (for example, custom deployed Postgres instance), must not store application security tokens, cloud-provider service keys or other long-lived security tokens in their stateful stores. | | |
1. During a rotation, old and new generations of secrets should pass authentication, allowing gradual roll-out of new secrets. | `R410` | | Long-lived shared secrets are discouraged, and should be referenced in the service manifest as such, to allow for accounting and monitoring. | | |
| `R420` | | Services using long-lived shared secrets should ensure that secret rotation can take place without downtime. {::nomarkdown}<ol><li>During a rotation, old and new generations of secrets should pass authentication, allowing gradual roll-out of new secrets.</li></ol>{:/} | | |
##### Common Service Libraries ##### Common Service Libraries
1. <a name="R500">`R500`</a>: Experimental services would be strongly encouraged to adopt and use [LabKit](https://gitlab.com/gitlab-org/labkit) (for Go services), or [LabKit-Ruby](https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby) for observability, context, correlation, FIPs verification, etc. | ID | Required | Detail | Epic/Issue | Done? |
1. At present, there is no LabKit-Python library, but some experiments will run in Python, so building a library to providing observability, context, correlation services in Python will be required. |---|---|---|---|---|
| `R500` | Required | Experimental services would be strongly encouraged to adopt and use [LabKit](https://gitlab.com/gitlab-org/labkit) (for Go services), or [LabKit-Ruby](https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby) for observability, context, correlation, FIPs verification, etc. {::nomarkdown}<ol><li>At present, there is no LabKit-Python library, but some experiments will run in Python, so building a library to providing observability, context, correlation services in Python will be required. </li></ol>{:/} | | |

View File

@ -220,7 +220,7 @@ The following example projects can help you get started with the agent.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340882) in GitLab 14.8, GitLab warns you on the agent's list page to update the agent version installed on your cluster. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340882) in GitLab 14.8, GitLab warns you on the agent's list page to update the agent version installed on your cluster.
For the best experience, the version of the agent installed in your cluster should match the GitLab major and minor version. The previous minor version is also supported. For example, if your GitLab version is v14.9.4 (major version 14, minor version 9), then versions v14.9.0 and v14.9.1 of the agent are ideal, but any v14.8.x version of the agent is also supported. See [the release page](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/releases) of the GitLab agent. For the best experience, the version of the agent installed in your cluster should match the GitLab major and minor version. The previous and next minor versions are also supported. For example, if your GitLab version is v14.9.4 (major version 14, minor version 9), then versions v14.9.0 and v14.9.1 of the agent are ideal, but any v14.8.x or v14.10.x version of the agent is also supported. See [the release page](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent/-/releases) of the GitLab agent.
### Update the agent version ### Update the agent version

View File

@ -45,7 +45,8 @@ Git push options can perform actions for merge requests while pushing changes:
| Push option | Description | | Push option | Description |
|----------------------------------------------|-------------| |----------------------------------------------|-------------|
| `merge_request.create` | Create a new merge request for the pushed branch. | | `merge_request.create` | Create a new merge request for the pushed branch. |
| `merge_request.target=<branch_name>` | Set the target of the merge request to a particular branch or upstream project, such as: `git push -o merge_request.target=project_path/branch`. | | `merge_request.target=<branch_name>` | Set the target of the merge request to a particular branch, such as: `git push -o merge_request.target=branch_name`. |
| `merge_request.target_project=<project>` | Set the target of the merge request to a particular upstream project, such as: `git push -o merge_request.target_project=path/to/project`. Introduced in [GitLab 16.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132475). |
| `merge_request.merge_when_pipeline_succeeds` | Set the merge request to [merge when its pipeline succeeds](merge_requests/merge_when_pipeline_succeeds.md). | | `merge_request.merge_when_pipeline_succeeds` | Set the merge request to [merge when its pipeline succeeds](merge_requests/merge_when_pipeline_succeeds.md). |
| `merge_request.remove_source_branch` | Set the merge request to remove the source branch when it's merged. | | `merge_request.remove_source_branch` | Set the merge request to remove the source branch when it's merged. |
| `merge_request.title="<title>"` | Set the title of the merge request. For example: `git push -o merge_request.title="The title I want"`. | | `merge_request.title="<title>"` | Set the title of the merge request. For example: `git push -o merge_request.title="The title I want"`. |

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# A job to remove protected_branch_merge_access_levels for groups that do not have project_group_links
# to the project for the associated protected branch
class DeleteInvalidProtectedBranchMergeAccessLevels < BatchedMigrationJob
operation_name :delete_invalid_protected_branch_merge_access_levels
scope_to ->(relation) { relation.where.not(group_id: nil) }
feature_category :source_code_management
def perform
each_sub_batch do |sub_batch|
sub_batch
.joins('INNER JOIN protected_branches ON protected_branches.id = protected_branch_id')
.joins(%(
LEFT OUTER JOIN project_group_links pgl
ON pgl.group_id = protected_branch_merge_access_levels.group_id
AND pgl.project_id = protected_branches.project_id
))
.where(%(
pgl.id IS NULL
)).delete_all
end
end
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# A job to remove protected_branch_push_access_levels for groups that do not have project_group_links
# to the project for the associated protected branch
class DeleteInvalidProtectedBranchPushAccessLevels < BatchedMigrationJob
operation_name :delete_invalid_protected_branch_push_access_levels
scope_to ->(relation) { relation.where.not(group_id: nil) }
feature_category :source_code_management
def perform
each_sub_batch do |sub_batch|
sub_batch
.joins('INNER JOIN protected_branches ON protected_branches.id = protected_branch_id')
.joins(%(
LEFT OUTER JOIN project_group_links pgl
ON pgl.group_id = protected_branch_push_access_levels.group_id
AND pgl.project_id = protected_branches.project_id
))
.where(%(
pgl.id IS NULL
)).delete_all
end
end
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# A job to remove protected_tag_create_access_levels for groups that do not have project_group_links
# to the project for the associated protected branch
class DeleteInvalidProtectedTagCreateAccessLevels < BatchedMigrationJob
operation_name :delete_invalid_protected_tag_create_access_levels
scope_to ->(relation) { relation.where.not(group_id: nil) }
feature_category :source_code_management
def perform
each_sub_batch do |sub_batch|
sub_batch
.joins('INNER JOIN protected_tags ON protected_tags.id = protected_tag_id')
.joins(%(
LEFT OUTER JOIN project_group_links pgl
ON pgl.group_id = protected_tag_create_access_levels.group_id
AND pgl.project_id = protected_tags.project_id
))
.where(%(
pgl.id IS NULL
)).delete_all
end
end
end
end
end

View File

@ -14,6 +14,7 @@ module Gitlab
:milestone, :milestone,
:remove_source_branch, :remove_source_branch,
:target, :target,
:target_project,
:title, :title,
:unassign, :unassign,
:unlabel :unlabel

View File

@ -23,7 +23,7 @@ RSpec.describe Projects::Ml::ModelsIndexComponent, type: :component, feature_cat
end end
subject(:component) do subject(:component) do
described_class.new(paginator: paginator) described_class.new(model_count: 5, paginator: paginator)
end end
describe 'rendered' do describe 'rendered' do
@ -43,13 +43,15 @@ RSpec.describe Projects::Ml::ModelsIndexComponent, type: :component, feature_cat
{ {
'name' => model1.name, 'name' => model1.name,
'version' => model1.latest_version.version, 'version' => model1.latest_version.version,
'path' => "/#{project.full_path}/-/packages/#{model1.latest_version.package_id}", 'versionPackagePath' => "/#{project.full_path}/-/packages/#{model1.latest_version.package_id}",
'versionPath' => "/#{project.full_path}/-/ml/models/#{model1.id}/versions/#{model1.latest_version.id}",
'versionCount' => 1 'versionCount' => 1
}, },
{ {
'name' => model2.name, 'name' => model2.name,
'version' => nil, 'version' => nil,
'path' => nil, 'versionPackagePath' => nil,
'versionPath' => nil,
'versionCount' => 0 'versionCount' => 0
} }
], ],
@ -58,7 +60,8 @@ RSpec.describe Projects::Ml::ModelsIndexComponent, type: :component, feature_cat
'hasPreviousPage' => false, 'hasPreviousPage' => false,
'startCursor' => 'abcde', 'startCursor' => 'abcde',
'endCursor' => 'defgh' 'endCursor' => 'defgh'
} },
'modelCount' => 5
}) })
end end
end end

View File

@ -19,7 +19,7 @@ RSpec.describe 'Groups > Members > Sort members', :js, feature_category: :groups
def expect_sort_by(text, sort_direction) def expect_sort_by(text, sort_direction)
within('[data-testid="members-sort-dropdown"]') do within('[data-testid="members-sort-dropdown"]') do
expect(page).to have_css('button[aria-haspopup="menu"]', text: text) expect(page).to have_css('button[aria-haspopup="menu"]', text: text)
expect(page).to have_button("Sorting Direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}") expect(page).to have_button("Sort direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}")
end end
end end

View File

@ -149,7 +149,7 @@ RSpec.describe 'Projects > Members > Sorting', :js, feature_category: :groups_an
def expect_sort_by(text, sort_direction) def expect_sort_by(text, sort_direction)
within('[data-testid="members-sort-dropdown"]') do within('[data-testid="members-sort-dropdown"]') do
expect(page).to have_css('button[aria-haspopup="menu"]', text: text) expect(page).to have_css('button[aria-haspopup="menu"]', text: text)
expect(page).to have_button("Sorting Direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}") expect(page).to have_button("Sort direction: #{sort_direction == :asc ? 'Ascending' : 'Descending'}")
end end
end end
end end

View File

@ -19,9 +19,11 @@ RSpec.describe Projects::Ml::ModelFinder, feature_category: :mlops do
is_expected.to eq([model3, model2, model1]) is_expected.to eq([model3, model2, model1])
end end
it 'including the latest version', :aggregate_failures do it 'including the latest version and project', :aggregate_failures do
expect(models[0].association_cached?(:latest_version)).to be(true) expect(models[0].association_cached?(:latest_version)).to be(true)
expect(models[0].association_cached?(:project)).to be(true)
expect(models[1].association_cached?(:latest_version)).to be(true) expect(models[1].association_cached?(:latest_version)).to be(true)
expect(models[1].association_cached?(:project)).to be(true)
end end
it 'does not return models belonging to a different project' do it 'does not return models belonging to a different project' do

View File

@ -0,0 +1,76 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::DeleteInvalidProtectedBranchMergeAccessLevels,
feature_category: :source_code_management do
let(:projects_table) { table(:projects) }
let(:protected_branches_table) { table(:protected_branches) }
let(:namespaces_table) { table(:namespaces) }
let(:protected_branch_merge_access_levels_table) { table(:protected_branch_merge_access_levels) }
let(:project_group_links_table) { table(:project_group_links) }
let(:users_table) { table(:users) }
let(:user1) { users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 5) }
let(:project_group) { namespaces_table.create!(name: 'group-1', path: 'group-1', type: 'Group') }
let(:project_namespace) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-2', type: 'Project') }
let!(:project_1) do
projects_table
.create!(
name: 'project1',
path: 'path1',
namespace_id: project_group.id,
project_namespace_id: project_namespace.id,
visibility_level: 0
)
end
subject(:perform_migration) do
described_class.new(start_id: protected_branch_merge_access_levels_table.minimum(:id),
end_id: protected_branch_merge_access_levels_table.maximum(:id),
batch_table: :protected_branch_merge_access_levels,
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: ApplicationRecord.connection)
.perform
end
context 'when there are merge access levels' do
let(:protected_branch1) { protected_branches_table.create!(project_id: project_1.id, name: 'name') }
let!(:merge_access_level_for_user) do
protected_branch_merge_access_levels_table.create!(
protected_branch_id: protected_branch1.id,
user_id: user1.id
)
end
let(:invited_group) { namespaces_table.create!(name: 'group-2', path: 'group-2', type: 'Group') }
let!(:invited_group_link) do
project_group_links_table.create!(project_id: project_1.id, group_id: invited_group.id)
end
let!(:merge_access_level_with_linked_group) do
protected_branch_merge_access_levels_table.create!(
protected_branch_id: protected_branch1.id,
group_id: invited_group.id
)
end
let!(:merge_access_level_with_unlinked_group) do
protected_branch_merge_access_levels_table.create!(
protected_branch_id: protected_branch1.id,
group_id: project_group.id
)
end
it 'deletes merge access levels with groups that do not have project_group_links to the project' do
expect { subject }.to change { protected_branch_merge_access_levels_table.count }.from(3).to(2)
expect(protected_branch_merge_access_levels_table.all).to contain_exactly(
merge_access_level_with_linked_group,
merge_access_level_for_user
)
end
end
end

View File

@ -0,0 +1,76 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::DeleteInvalidProtectedBranchPushAccessLevels,
feature_category: :source_code_management do
let(:projects_table) { table(:projects) }
let(:protected_branches_table) { table(:protected_branches) }
let(:namespaces_table) { table(:namespaces) }
let(:protected_branch_push_access_levels_table) { table(:protected_branch_push_access_levels) }
let(:project_group_links_table) { table(:project_group_links) }
let(:users_table) { table(:users) }
let(:user1) { users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 5) }
let(:project_group) { namespaces_table.create!(name: 'group-1', path: 'group-1', type: 'Group') }
let(:project_namespace) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-2', type: 'Project') }
let!(:project_1) do
projects_table
.create!(
name: 'project1',
path: 'path1',
namespace_id: project_group.id,
project_namespace_id: project_namespace.id,
visibility_level: 0
)
end
subject(:perform_migration) do
described_class.new(start_id: protected_branch_push_access_levels_table.minimum(:id),
end_id: protected_branch_push_access_levels_table.maximum(:id),
batch_table: :protected_branch_push_access_levels,
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: ApplicationRecord.connection)
.perform
end
context 'when there are push access levels' do
let(:protected_branch1) { protected_branches_table.create!(project_id: project_1.id, name: 'name') }
let!(:push_access_level_for_user) do
protected_branch_push_access_levels_table.create!(
protected_branch_id: protected_branch1.id,
user_id: user1.id
)
end
let(:invited_group) { namespaces_table.create!(name: 'group-2', path: 'group-2', type: 'Group') }
let!(:invited_group_link) do
project_group_links_table.create!(project_id: project_1.id, group_id: invited_group.id)
end
let!(:push_access_level_with_linked_group) do
protected_branch_push_access_levels_table.create!(
protected_branch_id: protected_branch1.id,
group_id: invited_group.id
)
end
let!(:push_access_level_with_unlinked_group) do
protected_branch_push_access_levels_table.create!(
protected_branch_id: protected_branch1.id,
group_id: project_group.id
)
end
it 'deletes push access levels with groups that do not have project_group_links to the project' do
expect { subject }.to change { protected_branch_push_access_levels_table.count }.from(3).to(2)
expect(protected_branch_push_access_levels_table.all).to contain_exactly(
push_access_level_with_linked_group,
push_access_level_for_user
)
end
end
end

View File

@ -0,0 +1,76 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::DeleteInvalidProtectedTagCreateAccessLevels,
feature_category: :source_code_management do
let(:projects_table) { table(:projects) }
let(:protected_tags_table) { table(:protected_tags) }
let(:namespaces_table) { table(:namespaces) }
let(:protected_tag_create_access_levels_table) { table(:protected_tag_create_access_levels) }
let(:project_group_links_table) { table(:project_group_links) }
let(:users_table) { table(:users) }
let(:user1) { users_table.create!(name: 'user1', email: 'user1@example.com', projects_limit: 5) }
let(:project_group) { namespaces_table.create!(name: 'group-1', path: 'group-1', type: 'Group') }
let(:project_namespace) { namespaces_table.create!(name: 'namespace', path: 'namespace-path-2', type: 'Project') }
let!(:project_1) do
projects_table
.create!(
name: 'project1',
path: 'path1',
namespace_id: project_group.id,
project_namespace_id: project_namespace.id,
visibility_level: 0
)
end
subject(:perform_migration) do
described_class.new(start_id: protected_tag_create_access_levels_table.minimum(:id),
end_id: protected_tag_create_access_levels_table.maximum(:id),
batch_table: :protected_tag_create_access_levels,
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: ApplicationRecord.connection)
.perform
end
context 'when there are push access levels' do
let(:protected_tag) { protected_tags_table.create!(project_id: project_1.id, name: 'name') }
let!(:push_access_level_for_user) do
protected_tag_create_access_levels_table.create!(
protected_tag_id: protected_tag.id,
user_id: user1.id
)
end
let(:invited_group) { namespaces_table.create!(name: 'group-2', path: 'group-2', type: 'Group') }
let!(:invited_group_link) do
project_group_links_table.create!(project_id: project_1.id, group_id: invited_group.id)
end
let!(:push_access_level_with_linked_group) do
protected_tag_create_access_levels_table.create!(
protected_tag_id: protected_tag.id,
group_id: invited_group.id
)
end
let!(:push_access_level_with_unlinked_group) do
protected_tag_create_access_levels_table.create!(
protected_tag_id: protected_tag.id,
group_id: project_group.id
)
end
it 'deletes push access levels with groups that do not have project_group_links to the project' do
expect { subject }.to change { protected_tag_create_access_levels_table.count }.from(3).to(2)
expect(protected_tag_create_access_levels_table.all).to contain_exactly(
push_access_level_with_linked_group,
push_access_level_for_user
)
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueDeleteInvalidProtectedBranchMergeAccessLevels, feature_category: :source_code_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :protected_branch_merge_access_levels,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueDeleteInvalidProtectedBranchPushAccessLevels, feature_category: :source_code_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :protected_branch_push_access_levels,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueDeleteInvalidProtectedTagCreateAccessLevels, feature_category: :source_code_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :protected_tag_create_access_levels,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -78,6 +78,14 @@ RSpec.describe Ml::Model, feature_category: :mlops do
end end
end end
describe '.including_project' do
subject { described_class.including_project }
it 'loads latest version' do
expect(subject.first.association_cached?(:project)).to be(true)
end
end
describe 'with_version_count' do describe 'with_version_count' do
let(:model) { existing_model } let(:model) { existing_model }

View File

@ -58,6 +58,22 @@ RSpec.describe Ml::ModelPresenter, feature_category: :mlops do
end end
end end
describe '#latest_version_path' do
subject { model.present.latest_version_path }
context 'when model version does not have package' do
let(:model) { model1 }
it { is_expected.to be_nil }
end
context 'when latest model version has package' do
let(:model) { model2 }
it { is_expected.to eq("/#{project.full_path}/-/ml/models/#{model.id}/versions/#{model.latest_version.id}") }
end
end
describe '#path' do describe '#path' do
subject { model1.present.path } subject { model1.present.path }

View File

@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Ml::ModelVersionPresenter, feature_category: :mlops do RSpec.describe Ml::ModelVersionPresenter, feature_category: :mlops do
let_it_be(:project) { build_stubbed(:project) } let_it_be(:project) { build_stubbed(:project) }
let_it_be(:model) { build_stubbed(:ml_models, name: 'a_model', project: project) } let_it_be(:model) { build_stubbed(:ml_models, name: 'a_model', project: project) }
let_it_be(:model_version) { build_stubbed(:ml_model_versions, model: model, version: '1.1.1') } let_it_be(:model_version) { build_stubbed(:ml_model_versions, :with_package, model: model, version: '1.1.1') }
let_it_be(:presenter) { model_version.present } let_it_be(:presenter) { model_version.present }
describe '.display_name' do describe '.display_name' do
@ -15,8 +15,14 @@ RSpec.describe Ml::ModelVersionPresenter, feature_category: :mlops do
end end
describe '#path' do describe '#path' do
subject { model_version.present.path } subject { presenter.path }
it { is_expected.to eq("/#{project.full_path}/-/ml/models/#{model.id}/versions/#{model_version.id}") } it { is_expected.to eq("/#{project.full_path}/-/ml/models/#{model.id}/versions/#{model_version.id}") }
end end
describe '#package_path' do
subject { presenter.package_path }
it { is_expected.to eq("/#{project.full_path}/-/packages/#{model_version.package_id}") }
end
end end

View File

@ -43,17 +43,26 @@ RSpec.describe Projects::Ml::ModelsController, feature_category: :mlops do
index_request index_request
end end
it 'fetches the correct models' do it 'fetches the correct variables', :aggregate_failures do
stub_const("Projects::Ml::ModelsController::MAX_MODELS_PER_PAGE", 2)
index_request index_request
expect(assigns(:paginator).records).to match_array([model3, model2, model1]) page_models = [model3, model2]
all_models = [model3, model2, model1]
expect(assigns(:paginator).records).to match_array(page_models)
expect(assigns(:model_count)).to be all_models.count
end end
it 'does not perform N+1 sql queries' do it 'does not perform N+1 sql queries' do
list_models
control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { list_models } control_count = ActiveRecord::QueryRecorder.new(skip_cached: false) { list_models }
create_list(:ml_model_versions, 2, model: model1) create_list(:ml_model_versions, 2, model: model1)
create_list(:ml_model_versions, 2, model: model2) create_list(:ml_model_versions, 2, model: model2)
create_list(:ml_models, 4, project: project)
expect { list_models }.not_to exceed_all_query_limit(control_count) expect { list_models }.not_to exceed_all_query_limit(control_count)
end end

View File

@ -54,6 +54,17 @@ RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :sour
end end
end end
shared_examples_for 'a service that can set the target project of a merge request' do
subject(:last_mr) { MergeRequest.last }
it 'creates a merge request with the correct target project' do
project_path = push_options[:target_project] || project.default_merge_request_target.full_path
expect { service.execute }.to change { MergeRequest.count }.by(1)
expect(last_mr.target_project.full_path).to eq(project_path)
end
end
shared_examples_for 'a service that can set the title of a merge request' do shared_examples_for 'a service that can set the title of a merge request' do
subject(:last_mr) { MergeRequest.last } subject(:last_mr) { MergeRequest.last }
@ -347,6 +358,31 @@ RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :sour
it_behaves_like 'with the project default branch' it_behaves_like 'with the project default branch'
end end
describe '`target_project` push option' do
let(:changes) { new_branch_changes }
let(:double_forked_project) { fork_project(forked_project, user1, repository: true) }
let(:service) { described_class.new(project: double_forked_project, current_user: user1, changes: changes, push_options: push_options) }
let(:push_options) { { create: true, target_project: target_project.full_path } }
context 'to self' do
let(:target_project) { double_forked_project }
it_behaves_like 'a service that can set the target project of a merge request'
end
context 'to intermediate project' do
let(:target_project) { forked_project }
it_behaves_like 'a service that can set the target project of a merge request'
end
context 'to base project' do
let(:target_project) { project }
it_behaves_like 'a service that can set the target project of a merge request'
end
end
describe '`title` push option' do describe '`title` push option' do
let(:push_options) { { title: title } } let(:push_options) { { title: title } }
@ -861,6 +897,17 @@ RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :sour
end end
end end
describe 'when the target project does not exist' do
let(:push_options) { { create: true, target: 'my-branch', target_project: 'does-not-exist' } }
let(:changes) { default_branch_changes }
it 'records an error', :sidekiq_inline do
service.execute
expect(service.errors).to eq(["User access was denied"])
end
end
describe 'when user does not have access to target project' do describe 'when user does not have access to target project' do
let(:push_options) { { create: true, target: 'my-branch' } } let(:push_options) { { create: true, target: 'my-branch' } }
let(:changes) { default_branch_changes } let(:changes) { default_branch_changes }
@ -890,6 +937,18 @@ RSpec.describe MergeRequests::PushOptionsHandlerService, feature_category: :sour
end end
end end
describe 'when projects are unrelated' do
let(:unrelated_project) { create(:project, :public, :repository, group: child_group) }
let(:push_options) { { create: true, target_project: unrelated_project.full_path } }
let(:changes) { new_branch_changes }
it 'records an error' do
service.execute
expect(service.errors).to eq(["Projects #{project.full_path} and #{unrelated_project.full_path} are not in the same network"])
end
end
describe 'when MR has ActiveRecord errors' do describe 'when MR has ActiveRecord errors' do
let(:push_options) { { create: true } } let(:push_options) { { create: true } }
let(:changes) { new_branch_changes } let(:changes) { new_branch_changes }

View File

@ -158,7 +158,7 @@ def click_sort_option(option, ascending)
wait_for_requests wait_for_requests
# Reset the sort direction # Reset the sort direction
if page.has_selector?('button[aria-label="Sorting Direction: Ascending"]', wait: 0) && !ascending if page.has_selector?('button[aria-label="Sort direction: Ascending"]', wait: 0) && !ascending
click_button 'Sort direction' click_button 'Sort direction'
wait_for_requests wait_for_requests