diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
index d897ed90204..dbf88683a19 100644
--- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
@@ -359,6 +359,7 @@ oauth:
variables:
QA_SCENARIO: Test::Integration::OAuth
rules:
+ - when: manual
- !reference [.rules:test:qa-default-branch, rules]
- if: $QA_SUITES =~ /Test::Integration::OAuth/
- !reference [.rules:test:manual, rules]
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index da213b0ed43..fd713a7a504 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -1,6 +1,5 @@
import { helpPagePath } from '~/helpers/help_page_helper';
import { __, s__ } from '~/locale';
-import ContinuousVulnerabilityScan from '~/security_configuration/components/continuous_vulnerability_scan.vue';
import {
REPORT_TYPE_SAST,
@@ -211,7 +210,6 @@ export const securityFeatures = [
configurationHelpPath: DEPENDENCY_SCANNING_CONFIG_HELP_PATH,
type: REPORT_TYPE_DEPENDENCY_SCANNING,
anchor: 'dependency-scanning',
- slotComponent: ContinuousVulnerabilityScan,
},
{
name: CONTAINER_SCANNING_NAME,
diff --git a/app/assets/javascripts/security_configuration/components/continuous_vulnerability_scan.vue b/app/assets/javascripts/security_configuration/components/continuous_vulnerability_scan.vue
deleted file mode 100644
index df648f665c7..00000000000
--- a/app/assets/javascripts/security_configuration/components/continuous_vulnerability_scan.vue
+++ /dev/null
@@ -1,133 +0,0 @@
-
-
-
-
-
- {{ $options.i18n.title }}
- {{ $options.i18n.badgeLabel }}
-
-
{{ errorMessage }}
-
-
-
- {{ $options.i18n.description }}
- {{
- $options.i18n.learnMore
- }}
-
-
-
-
- {{ content }}
-
-
-
-
-
-
diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue
index 395bdad5dcc..2100da78219 100644
--- a/app/assets/javascripts/security_configuration/components/feature_card.vue
+++ b/app/assets/javascripts/security_configuration/components/feature_card.vue
@@ -73,9 +73,6 @@ export default {
hasSecondary() {
return Boolean(this.feature.secondary);
},
- hasSlotComponent() {
- return Boolean(this.feature.slotComponent);
- },
// This condition is a temporary hack to not display any wrong information
// until this BE Bug is fixed: https://gitlab.com/gitlab-org/gitlab/-/issues/350307.
// More Information: https://gitlab.com/gitlab-org/gitlab/-/issues/350307#note_825447417
@@ -221,9 +218,5 @@ export default {
{{ $options.i18n.configurationGuide }}
-
-
-
-
diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js
index 4b498091134..aa3c9c87622 100644
--- a/app/assets/javascripts/security_configuration/index.js
+++ b/app/assets/javascripts/security_configuration/index.js
@@ -26,7 +26,6 @@ export const initSecurityConfiguration = (el) => {
autoDevopsHelpPagePath,
autoDevopsPath,
vulnerabilityTrainingDocsPath,
- continuousVulnerabilityScansEnabled,
} = el.dataset;
const { augmentedSecurityFeatures } = augmentFeatures(
@@ -44,7 +43,6 @@ export const initSecurityConfiguration = (el) => {
autoDevopsHelpPagePath,
autoDevopsPath,
vulnerabilityTrainingDocsPath,
- continuousVulnerabilityScansEnabled,
},
render(createElement) {
return createElement(SecurityConfigurationApp, {
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index cd2db2dad2c..7df902d8bec 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -166,6 +166,8 @@ class Projects::PipelinesController < Projects::ApplicationController
@stage = pipeline.stage(params[:stage])
return not_found unless @stage
+ return unless stage_stale?
+
render json: StageSerializer
.new(project: @project, current_user: @current_user)
.represent(@stage, details: true, retried: params[:retried])
@@ -263,6 +265,15 @@ class Projects::PipelinesController < Projects::ApplicationController
redirect_to url_for(safe_params.except(:scope).merge(status: safe_params[:scope])), status: :moved_permanently
end
+ def stage_stale?
+ return true if Feature.disabled?(:pipeline_stage_set_last_modified, @current_user)
+
+ last_modified = [@stage.updated_at.utc, @stage.statuses.maximum(:updated_at)].max
+
+ expires_in 24.hours
+ stale?(last_modified: last_modified, etag: @stage)
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def pipeline
return @pipeline if defined?(@pipeline)
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index c70100c03c8..1d687b29b02 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -118,6 +118,10 @@ module ProjectFeaturesCompatibility
write_feature_attribute_string(:model_experiments_access_level, value)
end
+ def model_registry_access_level=(value)
+ write_feature_attribute_string(:model_registry_access_level, value)
+ end
+
# TODO: Remove this method after we drop support for project create/edit APIs to set the
# container_registry_enabled attribute. They can instead set the container_registry_access_level
# attribute.
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 4c16ba18823..242194be440 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -94,16 +94,31 @@ module Routable
"(LOWER(routes.path) = LOWER(#{connection.quote(path)}))"
end
- route =
- if use_includes
- includes(:route).references(:routes)
- else
- joins(:route)
- end
+ if Feature.enabled?(:optimize_where_full_path_in, Feature.current_request)
+ route_scope = all
+ source_type_condition = { source_type: route_scope.klass.base_class }
- route
- .where(wheres.join(' OR '))
- .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
+ routes_matching_condition = Route.where(source_type_condition).where(wheres.join(' OR '))
+
+ result = route_scope.where(id: routes_matching_condition.pluck(:source_id))
+
+ if use_includes
+ result.preload(:route)
+ else
+ result
+ end
+ else
+ route =
+ if use_includes
+ includes(:route).references(:routes)
+ else
+ joins(:route)
+ end
+
+ route
+ .where(wheres.join(' OR '))
+ .allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/420046")
+ end
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 29afe9de722..bb421fc7dc6 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -499,7 +499,7 @@ class Project < ApplicationRecord
accepts_nested_attributes_for :prometheus_integration, update_only: true
accepts_nested_attributes_for :alerting_setting, update_only: true
- delegate :merge_requests_access_level, :forking_access_level, :issues_access_level, :wiki_access_level, :snippets_access_level, :builds_access_level, :repository_access_level, :package_registry_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level, :operations_access_level, :security_and_compliance_access_level, :container_registry_access_level, :environments_access_level, :feature_flags_access_level, :monitor_access_level, :releases_access_level, :infrastructure_access_level, :model_experiments_access_level, to: :project_feature, allow_nil: true
+ delegate :merge_requests_access_level, :forking_access_level, :issues_access_level, :wiki_access_level, :snippets_access_level, :builds_access_level, :repository_access_level, :package_registry_access_level, :pages_access_level, :metrics_dashboard_access_level, :analytics_access_level, :operations_access_level, :security_and_compliance_access_level, :container_registry_access_level, :environments_access_level, :feature_flags_access_level, :monitor_access_level, :releases_access_level, :infrastructure_access_level, :model_experiments_access_level, :model_registry_access_level, to: :project_feature, allow_nil: true
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :jira_dvcs_server_last_sync_at, to: :feature_usage
delegate :last_pipeline, to: :commit, allow_nil: true
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 36f1e09b2ba..c8287628716 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -27,6 +27,7 @@ class ProjectFeature < ApplicationRecord
releases
infrastructure
model_experiments
+ model_registry
].freeze
EXPORTABLE_FEATURES = (FEATURES - [:security_and_compliance, :pages]).freeze
@@ -81,6 +82,7 @@ class ProjectFeature < ApplicationRecord
attribute :feature_flags_access_level, default: ENABLED
attribute :environments_access_level, default: ENABLED
attribute :model_experiments_access_level, default: ENABLED
+ attribute :model_registry_access_level, default: ENABLED
attribute :package_registry_access_level, default: -> do
if ::Gitlab.config.packages.enabled
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index d60c5c41f43..609db11d139 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -127,7 +127,7 @@
= f.text_field :mastodon, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: "@robin@example.com"
.form-group.gl-form-group
- = f.label :website_url, s_('Profiles|Website url')
+ = f.label :website_url, s_('Profiles|Website URL')
= f.text_field :website_url, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|https://website.com")
.form-group.gl-form-group
= f.label :location, s_('Profiles|Location')
diff --git a/config/audit_events/types/project_feature_model_registry_access_level_updated.yml b/config/audit_events/types/project_feature_model_registry_access_level_updated.yml
new file mode 100644
index 00000000000..2be827cfcad
--- /dev/null
+++ b/config/audit_events/types/project_feature_model_registry_access_level_updated.yml
@@ -0,0 +1,9 @@
+---
+name: project_feature_model_registry_access_level_updated
+description: Model registry access level was updated
+introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/412734
+introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138399
+feature_category: mlops
+milestone: '16.7'
+saved_to_database: true
+streamed: true
diff --git a/config/feature_flags/development/global_dependency_scanning_on_advisory_ingestion.yml b/config/feature_flags/development/global_dependency_scanning_on_advisory_ingestion.yml
deleted file mode 100644
index fbede45e665..00000000000
--- a/config/feature_flags/development/global_dependency_scanning_on_advisory_ingestion.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: global_dependency_scanning_on_advisory_ingestion
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135581
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/427424
-milestone: '16.6'
-type: development
-group: group::composition analysis
-default_enabled: true
diff --git a/config/feature_flags/development/optimize_where_full_path_in.yml b/config/feature_flags/development/optimize_where_full_path_in.yml
new file mode 100644
index 00000000000..a47b703a958
--- /dev/null
+++ b/config/feature_flags/development/optimize_where_full_path_in.yml
@@ -0,0 +1,8 @@
+---
+name: optimize_where_full_path_in
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/137886
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/432863
+milestone: '16.7'
+type: development
+group: group::tenant scale
+default_enabled: false
diff --git a/config/feature_flags/development/pipeline_stage_set_last_modified.yml b/config/feature_flags/development/pipeline_stage_set_last_modified.yml
new file mode 100644
index 00000000000..55790f7a447
--- /dev/null
+++ b/config/feature_flags/development/pipeline_stage_set_last_modified.yml
@@ -0,0 +1,8 @@
+---
+name: pipeline_stage_set_last_modified
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138499
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433359
+milestone: '16.7'
+type: development
+group: group::global search
+default_enabled: false
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 88115fab689..77503814158 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -511,8 +511,6 @@
- 1
- - package_cleanup
- 1
-- - package_metadata_advisory_scan
- - 1
- - package_metadata_global_advisory_scan
- 1
- - package_repositories
diff --git a/db/click_house/migrate/20231205104100_modify_ci_finished_builds_started_at_default.rb b/db/click_house/migrate/20231205104100_modify_ci_finished_builds_started_at_default.rb
new file mode 100644
index 00000000000..00c8c825015
--- /dev/null
+++ b/db/click_house/migrate/20231205104100_modify_ci_finished_builds_started_at_default.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ModifyCiFinishedBuildsStartedAtDefault < ClickHouse::Migration
+ def up
+ execute <<~SQL
+ ALTER TABLE ci_finished_builds MODIFY COLUMN started_at DEFAULT COALESCE(finished_at, 0)
+ SQL
+ end
+
+ def down
+ execute <<~SQL
+ ALTER TABLE ci_finished_builds MODIFY COLUMN started_at DEFAULT now()
+ SQL
+ end
+end
diff --git a/db/click_house/migrate/20231205104101_modify_ci_finished_builds_finished_at_default.rb b/db/click_house/migrate/20231205104101_modify_ci_finished_builds_finished_at_default.rb
new file mode 100644
index 00000000000..6ea4b158536
--- /dev/null
+++ b/db/click_house/migrate/20231205104101_modify_ci_finished_builds_finished_at_default.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class ModifyCiFinishedBuildsFinishedAtDefault < ClickHouse::Migration
+ def up
+ execute <<~SQL
+ ALTER TABLE ci_finished_builds MODIFY COLUMN finished_at DEFAULT 0
+ SQL
+ end
+
+ def down
+ execute <<~SQL
+ ALTER TABLE ci_finished_builds MODIFY COLUMN finished_at DEFAULT now()
+ SQL
+ end
+end
diff --git a/db/click_house/migrate/20231205112200_fix_invalid_ci_finished_builds_started_at_values.rb b/db/click_house/migrate/20231205112200_fix_invalid_ci_finished_builds_started_at_values.rb
new file mode 100644
index 00000000000..28a63490793
--- /dev/null
+++ b/db/click_house/migrate/20231205112200_fix_invalid_ci_finished_builds_started_at_values.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class FixInvalidCiFinishedBuildsStartedAtValues < ClickHouse::Migration
+ def up
+ # Fix existing records to have the new default
+ execute <<~SQL
+ ALTER TABLE ci_finished_builds UPDATE started_at = finished_at WHERE started_at > finished_at
+ SQL
+ end
+
+ def down
+ # no-op as there is no way to retrieve old data
+ end
+end
diff --git a/db/migrate/20231130195635_add_model_registry_access_level_to_project_feature.rb b/db/migrate/20231130195635_add_model_registry_access_level_to_project_feature.rb
new file mode 100644
index 00000000000..95675a1f82a
--- /dev/null
+++ b/db/migrate/20231130195635_add_model_registry_access_level_to_project_feature.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddModelRegistryAccessLevelToProjectFeature < Gitlab::Database::Migration[2.2]
+ OPERATIONS_DEFAULT_VALUE = 20
+
+ enable_lock_retries!
+ milestone '16.7'
+
+ def change
+ add_column :project_features,
+ :model_registry_access_level,
+ :integer,
+ null: false,
+ default: OPERATIONS_DEFAULT_VALUE
+ end
+end
diff --git a/db/schema_migrations/20231130195635 b/db/schema_migrations/20231130195635
new file mode 100644
index 00000000000..5e8d8232feb
--- /dev/null
+++ b/db/schema_migrations/20231130195635
@@ -0,0 +1 @@
+5c9d89f5d5401d6a7082d5790cb12ee610a0a06138cf3608534a09685c812ea8
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index c64a5a234c4..3596bb114ca 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -21881,7 +21881,8 @@ CREATE TABLE project_features (
feature_flags_access_level integer DEFAULT 20 NOT NULL,
environments_access_level integer DEFAULT 20 NOT NULL,
releases_access_level integer DEFAULT 20 NOT NULL,
- model_experiments_access_level integer DEFAULT 20 NOT NULL
+ model_experiments_access_level integer DEFAULT 20 NOT NULL,
+ model_registry_access_level integer DEFAULT 20 NOT NULL
);
CREATE SEQUENCE project_features_id_seq
diff --git a/doc/administration/audit_event_streaming/audit_event_types.md b/doc/administration/audit_event_streaming/audit_event_types.md
index fa89074ad53..cc396a6345c 100644
--- a/doc/administration/audit_event_streaming/audit_event_types.md
+++ b/doc/administration/audit_event_streaming/audit_event_types.md
@@ -313,6 +313,7 @@ Audit event types belong to the following product categories.
| Name | Description | Saved to database | Streamed | Introduced in |
|:-----|:------------|:------------------|:---------|:--------------|
| [`project_feature_model_experiments_access_level_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121027) | Model experiments access level was updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/412384) |
+| [`project_feature_model_registry_access_level_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138399) | Model registry access level was updated| **{check-circle}** Yes | **{check-circle}** Yes | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/issues/412734) |
### Not categorized
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index 6109d4d603d..3a3dbc8c50d 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -218,8 +218,6 @@ Use sentence case for topic titles. For example:
When referring to specific user interface text, like a button label or menu
item, use the same capitalization that's displayed in the user interface.
-Standards for this content are listed in the [Pajamas Design System Content section](https://design.gitlab.com/content/punctuation/)
-and typically match what's mentioned in this Documentation Style Guide.
If you think the user interface text contains style mistakes,
create an issue or an MR to propose a change to the user interface text.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 6b1ffee85c0..87ac456d8c3 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -406,6 +406,7 @@ The following table lists group permissions available for each role:
| Manage group runners | | | | | ✓ |
| [Migrate groups](group/import/index.md) | | | | | ✓ |
| Manage [subscriptions, and purchase storage and compute minutes](../subscriptions/gitlab_com/index.md) | | | | | ✓ |
+| Manage group-level custom roles | | | | | ✓ |
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index 0c191ca1b14..f242a2d891b 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -116,6 +116,7 @@ to perform actions.
Prerequisites:
- You must have the Owner or Maintainer role.
+- [Group membership lock](../../group/access_and_permissions.md#prevent-members-from-being-added-to-projects-in-a-group) must be disabled.
To add a user to a project:
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 7c57666b843..f5dcbc07704 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -378,6 +378,10 @@ module API
authorize! :admin_group, user_group
end
+ def authorize_admin_member_role!
+ authorize! :admin_member_role, user_group
+ end
+
def authorize_read_builds!
authorize! :read_build, user_project
end
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 6f3601e9a21..e38930ed548 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -318,6 +318,7 @@ included_attributes:
- :releases_access_level
- :infrastructure_access_level
- :model_experiments_access_level
+ - :model_registry_access_level
prometheus_metrics:
- :created_at
- :updated_at
@@ -738,6 +739,7 @@ included_attributes:
- :releases_access_level
- :infrastructure_access_level
- :model_experiments_access_level
+ - :model_registry_access_level
- :auto_devops_deploy_strategy
- :auto_devops_enabled
- :container_registry_enabled
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 2b91923b9f4..8cf0c45c7a7 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9441,18 +9441,6 @@ msgstr ""
msgid "CVE|Why Request a CVE ID?"
msgstr ""
-msgid "CVS|By enabling this feature, you accept the %{linkStart}Testing Terms of Use%{linkEnd}"
-msgstr ""
-
-msgid "CVS|Continuous Vulnerability Scan"
-msgstr ""
-
-msgid "CVS|Detect vulnerabilities outside a pipeline as new data is added to the GitLab Advisory Database."
-msgstr ""
-
-msgid "CVS|Toggle CVS"
-msgstr ""
-
msgid "Cadence is not automated"
msgstr ""
@@ -37150,7 +37138,7 @@ msgstr ""
msgid "Profiles|Using emoji in names seems fun, but please try to set a status message instead"
msgstr ""
-msgid "Profiles|Website url"
+msgid "Profiles|Website URL"
msgstr ""
msgid "Profiles|Who you represent or work for."
diff --git a/qa/Gemfile b/qa/Gemfile
index 47b6c7daf39..33f50c30139 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -2,7 +2,7 @@
source 'https://rubygems.org'
-gem 'gitlab-qa', '~> 12', '>= 12.5.1', require: 'gitlab/qa'
+gem 'gitlab-qa', '~> 13', require: 'gitlab/qa'
gem 'gitlab_quality-test_tooling', '~> 1.8.1', require: false
gem 'gitlab-utils', path: '../gems/gitlab-utils'
gem 'activesupport', '~> 7.0.8' # This should stay in sync with the root's Gemfile
@@ -26,7 +26,7 @@ gem 'rspec-parameterized', '~> 1.0.0'
gem 'octokit', '~> 8.0.0'
gem "faraday-retry", "~> 2.2"
gem 'zeitwerk', '~> 2.6', '>= 2.6.12'
-gem 'influxdb-client', '~> 2.9'
+gem 'influxdb-client', '~> 3.0'
gem 'terminal-table', '~> 3.0.2', require: false
gem 'slack-notifier', '~> 2.4', require: false
gem 'fog-google', '~> 1.19', require: false
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index f8083fcfd22..e79bfeff4ef 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -123,7 +123,7 @@ GEM
gitlab (4.19.0)
httparty (~> 0.20)
terminal-table (>= 1.5.1)
- gitlab-qa (12.5.1)
+ gitlab-qa (13.0.0)
activesupport (>= 6.1, < 7.1)
gitlab (~> 4.19)
http (~> 5.0)
@@ -189,7 +189,7 @@ GEM
httpclient (2.8.3)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
- influxdb-client (2.9.0)
+ influxdb-client (3.0.0)
jwt (2.5.0)
knapsack (4.0.0)
rake
@@ -363,10 +363,10 @@ DEPENDENCIES
faraday-retry (~> 2.2)
fog-core (= 2.1.0)
fog-google (~> 1.19)
- gitlab-qa (~> 12, >= 12.5.1)
+ gitlab-qa (~> 13)
gitlab-utils!
gitlab_quality-test_tooling (~> 1.8.1)
- influxdb-client (~> 2.9)
+ influxdb-client (~> 3.0)
knapsack (~> 4.0)
nokogiri (~> 1.15, >= 1.15.5)
octokit (~> 8.0.0)
diff --git a/rubocop/cop/gitlab/feature_available_usage.rb b/rubocop/cop/gitlab/feature_available_usage.rb
index 307ff7ea6f6..0b1c4367eae 100644
--- a/rubocop/cop/gitlab/feature_available_usage.rb
+++ b/rubocop/cop/gitlab/feature_available_usage.rb
@@ -29,6 +29,7 @@ module RuboCop
releases
infrastructure
model_experiments
+ model_registry
].freeze
EE_FEATURES = %i[requirements].freeze
ALL_FEATURES = (FEATURES + EE_FEATURES).freeze
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index a1d56194ba5..82fdeea9557 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -43,6 +43,7 @@ FactoryBot.define do
releases_access_level { ProjectFeature::ENABLED }
infrastructure_access_level { ProjectFeature::ENABLED }
model_experiments_access_level { ProjectFeature::ENABLED }
+ model_registry_access_level { ProjectFeature::ENABLED }
# we can't assign the delegated `#ci_cd_settings` attributes directly, as the
# `#ci_cd_settings` relation needs to be created first
diff --git a/spec/frontend/security_configuration/components/continuous_vulnerability_scan_spec.js b/spec/frontend/security_configuration/components/continuous_vulnerability_scan_spec.js
deleted file mode 100644
index c395c91d880..00000000000
--- a/spec/frontend/security_configuration/components/continuous_vulnerability_scan_spec.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlBadge, GlToggle } from '@gitlab/ui';
-import VueApollo from 'vue-apollo';
-import Vue from 'vue';
-import ProjectSetContinuousVulnerabilityScanning from '~/security_configuration/graphql/project_set_continuous_vulnerability_scanning.graphql';
-import ContinuousVulnerabilityScan from '~/security_configuration/components/continuous_vulnerability_scan.vue';
-import createMockApollo from 'helpers/mock_apollo_helper';
-
-Vue.use(VueApollo);
-
-const setCVSMockResponse = {
- data: {
- projectSetContinuousVulnerabilityScanning: {
- continuousVulnerabilityScanningEnabled: true,
- errors: [],
- },
- },
-};
-
-const defaultProvide = {
- continuousVulnerabilityScansEnabled: true,
- projectFullPath: 'project/full/path',
-};
-
-describe('ContinuousVulnerabilityScan', () => {
- let wrapper;
- let apolloProvider;
- let requestHandlers;
-
- const createComponent = (options) => {
- requestHandlers = {
- setCVSMutationHandler: jest.fn().mockResolvedValue(setCVSMockResponse),
- };
-
- apolloProvider = createMockApollo([
- [ProjectSetContinuousVulnerabilityScanning, requestHandlers.setCVSMutationHandler],
- ]);
-
- wrapper = shallowMount(ContinuousVulnerabilityScan, {
- propsData: {
- feature: {
- available: true,
- configured: true,
- },
- },
- provide: {
- glFeatures: {
- dependencyScanningOnAdvisoryIngestion: true,
- globalDependencyScanningOnAdvisoryIngestion: false,
- },
- ...defaultProvide,
- },
- apolloProvider,
- ...options,
- });
- };
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- apolloProvider = null;
- });
-
- const findBadge = () => wrapper.findComponent(GlBadge);
- const findToggle = () => wrapper.findComponent(GlToggle);
-
- it('renders the component', () => {
- expect(wrapper.exists()).toBe(true);
- });
-
- it('renders the correct title', () => {
- expect(wrapper.text()).toContain('Continuous Vulnerability Scan');
- });
-
- it('renders the badge and toggle component with correct values', () => {
- expect(findBadge().exists()).toBe(true);
- expect(findBadge().text()).toBe('Experiment');
-
- expect(findToggle().exists()).toBe(true);
- expect(findToggle().props('value')).toBe(defaultProvide.continuousVulnerabilityScansEnabled);
- });
-
- it('should disable toggle when feature is not configured', () => {
- createComponent({
- propsData: {
- feature: {
- available: true,
- configured: false,
- },
- },
- });
- expect(findToggle().props('disabled')).toBe(true);
- });
-
- it('calls mutation on toggle change with correct payload', () => {
- findToggle().vm.$emit('change', true);
-
- expect(requestHandlers.setCVSMutationHandler).toHaveBeenCalledWith({
- input: {
- projectPath: 'project/full/path',
- enable: true,
- },
- });
- });
-
- describe('when feature flag is disabled', () => {
- it.each`
- dependencyScanningOnAdvisoryIngestion | globalDependencyScanningOnAdvisoryIngestion
- ${false} | ${false}
- ${true} | ${true}
- ${false} | ${true}
- `(
- 'when dependencyScanningOnAdvisoryIngestion: `$dependencyScanningOnAdvisoryIngestion` and globalDependencyScanningOnAdvisoryIngestion: `$globalDependencyScanningOnAdvisoryIngestion` should not render toggle and badge',
- ({ dependencyScanningOnAdvisoryIngestion, globalDependencyScanningOnAdvisoryIngestion }) => {
- createComponent({
- provide: {
- glFeatures: {
- dependencyScanningOnAdvisoryIngestion,
- globalDependencyScanningOnAdvisoryIngestion,
- },
- ...defaultProvide,
- },
- });
-
- expect(findToggle().exists()).toBe(false);
- expect(findBadge().exists()).toBe(false);
- },
- );
- });
-});
diff --git a/spec/frontend/security_configuration/components/feature_card_spec.js b/spec/frontend/security_configuration/components/feature_card_spec.js
index c715d01dd58..983a66a7fd3 100644
--- a/spec/frontend/security_configuration/components/feature_card_spec.js
+++ b/spec/frontend/security_configuration/components/feature_card_spec.js
@@ -1,6 +1,5 @@
import { GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
-import Vue from 'vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { securityFeatures } from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
@@ -14,10 +13,6 @@ import {
import { manageViaMRErrorMessage } from '../constants';
import { makeFeature } from './utils';
-const MockComponent = Vue.component('MockComponent', {
- render: (createElement) => createElement('span'),
-});
-
describe('FeatureCard component', () => {
let feature;
let wrapper;
@@ -394,17 +389,4 @@ describe('FeatureCard component', () => {
});
});
});
-
- describe('when a slot component is passed', () => {
- beforeEach(() => {
- feature = makeFeature({
- slotComponent: MockComponent,
- });
- createComponent({ feature });
- });
-
- it('renders the component properly', () => {
- expect(wrapper.findComponent(MockComponent).exists()).toBe(true);
- });
- });
});
diff --git a/spec/graphql/mutations/design_management/delete_spec.rb b/spec/graphql/mutations/design_management/delete_spec.rb
index 1b78529fbc7..7f499301543 100644
--- a/spec/graphql/mutations/design_management/delete_spec.rb
+++ b/spec/graphql/mutations/design_management/delete_spec.rb
@@ -86,46 +86,47 @@ RSpec.describe Mutations::DesignManagement::Delete do
end
end
- it 'runs no more than 31 queries' do
+ it 'runs no more than 34 queries' do
allow(Gitlab::Tracking).to receive(:event) # rubocop:disable RSpec/ExpectGitlabTracking
filenames.each(&:present?) # ignore setup
- # Queries: as of 2022-09-08
+ # Queries: as of 2022-12-01
# -------------
- # 01. routing query
- # 02. policy query: find namespace by type and id
- # 03. policy query: find namespace by id
- # 04. policy query: project.project_feature
- # 05,06. project.authorizations for user (same query twice)
- # 07. find issue by iid
- # 08. find project by id
- # 09. find namespace by id
- # 10. find group namespace by id
- # 11. policy query: find namespace by id (same query as 3)
- # 12. project.authorizations for user (same query as 5)
- # 13. find user by id
- # 14. project.project_features (same query as 3)
- # 15. project.authorizations for user (same query as 5)
- # 16. current designs by filename and issue
- # 17, 18 project.authorizations for user (same query as 5)
- # 19. find design_management_repository for project
- # 20. find route by id and source_type
+ # 01. for routes to find routes.source_id of projects matching paths
+ # 02. Find projects with the above source id.
+ # 03. preload routes of the above projects
+ # 04. policy query: find namespace by type and id
+ # 05. policy query: namespace_bans
+ # 06. policy query: project.project_feature
+ # 07,08. project.authorizations for user (same query twice)
+ # 09. find issue by iid
+ # 10. find project by id
+ # 11. find namespace by id
+ # 12. policy query: find namespace by type and id (same query as 4)
+ # 13. project.authorizations for user (same query as 7)
+ # 14. find user by id
+ # 15. project.project_features (same query as 6)
+ # 16. project.authorizations for user (same query as 7)
+ # 17. current designs by filename and issue
+ # 18, 19 project.authorizations for user (same query as 7)
+ # 20. find design_management_repository for project
+ # 21. find route by source_id and source_type
# ------------- our queries are below:
- # 21. start transaction
- # 22. create version with sha and issue
- # 23. create design-version links
- # 24. validate version.actions.present?
- # 25. validate version.sha is unique
- # 26. validate version.issue.present?
- # 27. leave transaction
- # 28. find project by id (same query as 8)
- # 29. find namespace by id (same query as 9)
- # 30. find project by id (same query as 8)
- # 31. find project by id (same query as 8)
- # 32. create event
- # 33. find plan for standard context
+ # 22. start transaction
+ # 23. create version with sha and issue
+ # 24. create design-version links
+ # 25. validate version.actions.present?
+ # 26. validate version.sha is unique
+ # 27. validate version.issue.present?
+ # 28. leave transaction
+ # 29. find project by id (same query as 10)
+ # 30. find namespace by id (same query as 11)
+ # 31. find project by id (same query as 10)
+ # 32. find project by id (same query as 10)
+ # 33. create event
+ # 34. find plan for standard context
#
- expect { run_mutation }.not_to exceed_query_limit(33)
+ expect { run_mutation }.not_to exceed_query_limit(34)
end
end
diff --git a/spec/graphql/resolvers/group_resolver_spec.rb b/spec/graphql/resolvers/group_resolver_spec.rb
index ed406d14772..c04961b4804 100644
--- a/spec/graphql/resolvers/group_resolver_spec.rb
+++ b/spec/graphql/resolvers/group_resolver_spec.rb
@@ -12,7 +12,7 @@ RSpec.describe Resolvers::GroupResolver do
it 'batch-resolves groups by full path' do
paths = [group1.full_path, group2.full_path]
- result = batch_sync(max_queries: 1) do
+ result = batch_sync(max_queries: 3) do
paths.map { |path| resolve_group(path) }
end
diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb
index dec9d4701e1..03febc75d3f 100644
--- a/spec/graphql/resolvers/project_resolver_spec.rb
+++ b/spec/graphql/resolvers/project_resolver_spec.rb
@@ -13,7 +13,7 @@ RSpec.describe Resolvers::ProjectResolver do
it 'batch-resolves projects by full path' do
paths = [project1.full_path, project2.full_path]
- result = batch_sync(max_queries: 1) do
+ result = batch_sync(max_queries: 3) do
paths.map { |path| resolve_project(path) }
end
diff --git a/spec/lib/banzai/filter/references/alert_reference_filter_spec.rb b/spec/lib/banzai/filter/references/alert_reference_filter_spec.rb
index 9723e9b39f1..9a2e68aaae0 100644
--- a/spec/lib/banzai/filter/references/alert_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/alert_reference_filter_spec.rb
@@ -240,9 +240,15 @@ RSpec.describe Banzai::Filter::References::AlertReferenceFilter, feature_categor
# Since we're not batching alert queries across projects,
# we have to account for that.
- # 1 for both projects, 1 for alerts in each project == 3
+ # 1 for routes to find routes.source_id of projects matching paths
+ # 1 for projects belonging to the above routes
+ # 1 for preloading routes of the projects
+ # 1 for loading the namespaces associated to the project
+ # 1 for loading the routes associated with the namespace
+ # 1x2 for alerts in each project
+ # Total == 7
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
- max_count += 2
+ max_count += 6
expect do
reference_filter(markdown)
diff --git a/spec/lib/banzai/filter/references/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/references/commit_reference_filter_spec.rb
index 6e0f9eda0e2..35a3f20f7b7 100644
--- a/spec/lib/banzai/filter/references/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/commit_reference_filter_spec.rb
@@ -287,12 +287,18 @@ RSpec.describe Banzai::Filter::References::CommitReferenceFilter, feature_catego
reference_filter(markdown)
end.count
- markdown = "#{commit_reference} 8b95f2f1 8b95f2f2 8b95f2f3 #{commit2_reference} #{commit3_reference}"
+ expect(max_count).to eq 0
+
+ markdown = "#{commit_reference} 8b95f2f1 8b95f2f2 8b95f2f3 #{commit2_reference} #{commit3_reference}"
# Commits are not DB entries, they are on the project itself.
- # So adding commits from two more projects to the markdown should
- # only increase by 1 query
- max_count += 1
+ # 1 for for routes to find routes.source_id of projects matching paths
+ # 1 for projects belonging to the above routes
+ # 1 for preloading routes of the projects
+ # 1 for loading the namespaces associated to the project
+ # 1 for loading the routes associated with the namespace
+ # Total = 5
+ max_count += 5
expect do
reference_filter(markdown)
diff --git a/spec/lib/banzai/filter/references/label_reference_filter_spec.rb b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb
index a4587b70dfa..81b08a4c516 100644
--- a/spec/lib/banzai/filter/references/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/label_reference_filter_spec.rb
@@ -747,10 +747,16 @@ RSpec.describe Banzai::Filter::References::LabelReferenceFilter, feature_categor
# Since we're not batching label queries across projects/groups,
# queries increase when a new project/group is added.
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
- # first reference to already loaded project (1),
- # second reference requires project and namespace (2), and label (1)
+ # 1 for for routes to find routes.source_id of projects matching paths
+ # 1 for projects belonging to the above routes
+ # 1 for preloading routes of the projects
+ # 1 for loading the namespaces associated to the project
+ # 1 for loading the routes associated with the namespace
+ # 1 for the group
+ # 1x2 for labels
+ # Total == 8
markdown = "#{project_reference} #{group2_reference}"
- max_count = control_count + 3
+ max_count = control_count + 7
expect do
reference_filter(markdown)
diff --git a/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb
index 1fa62d70b72..e778f07227c 100644
--- a/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/milestone_reference_filter_spec.rb
@@ -522,7 +522,7 @@ RSpec.describe Banzai::Filter::References::MilestoneReferenceFilter, feature_cat
# queries increase when a new project/group is added.
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
markdown = "#{project_reference} #{group2_reference}"
- control_count += 5
+ control_count += 9
expect do
reference_filter(markdown)
diff --git a/spec/lib/banzai/filter/references/project_reference_filter_spec.rb b/spec/lib/banzai/filter/references/project_reference_filter_spec.rb
index 9433862ac8a..c55fff78756 100644
--- a/spec/lib/banzai/filter/references/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/project_reference_filter_spec.rb
@@ -119,7 +119,7 @@ RSpec.describe Banzai::Filter::References::ProjectReferenceFilter, feature_categ
reference_filter(markdown)
end.count
- expect(max_count).to eq 1
+ expect(max_count).to eq 2
markdown = "#{normal_project_reference} #{invalidate_reference(normal_project_reference)} #{group_project_reference} #{nested_project_reference}"
diff --git a/spec/lib/banzai/filter/references/reference_cache_spec.rb b/spec/lib/banzai/filter/references/reference_cache_spec.rb
index 577e4471433..04877931610 100644
--- a/spec/lib/banzai/filter/references/reference_cache_spec.rb
+++ b/spec/lib/banzai/filter/references/reference_cache_spec.rb
@@ -79,8 +79,16 @@ RSpec.describe Banzai::Filter::References::ReferenceCache, feature_category: :te
expect(control_count).to eq 3
# Since this is an issue filter that is not batching issue queries
# across projects, we have to account for that.
- # 1 for original issue, 2 for second route/project, 1 for other issue
- max_count = control_count + 4
+ # 1 for for routes to find routes.source_id of projects matching paths
+ # 1 for projects belonging to the above routes
+ # 1 for preloading routes of the projects
+ # 1 for loading the namespaces associated to the project
+ # 1 for loading the routes associated with the namespace
+ # 1x2 for issues
+ # 1x2 for groups
+ # 1x2 for work_item_types
+ # Total = 11
+ max_count = control_count + 8
expect do
cache.load_references_per_parent(filter.nodes)
diff --git a/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb
index b196d85ba8a..00eac7262f4 100644
--- a/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/references/snippet_reference_filter_spec.rb
@@ -239,9 +239,15 @@ RSpec.describe Banzai::Filter::References::SnippetReferenceFilter, feature_categ
# Since we're not batching snippet queries across projects,
# we have to account for that.
- # 1 for both projects, 1 for snippets in each project == 3
+ # 1 for for routes to find routes.source_id of projects matching paths
+ # 1 for projects belonging to the above routes
+ # 1 for preloading routes of the projects
+ # 1 for loading the namespaces associated to the project
+ # 1 for loading the routes associated with the namespace
+ # 1x2 for snippets in each project == 2
+ # Total = 7
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/330359
- max_count = control_count + 2
+ max_count = control_count + 6
expect do
reference_filter(markdown)
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index e0efa1bfd38..3efa33d8879 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -706,6 +706,7 @@ ProjectFeature:
- monitor_access_level
- infrastructure_access_level
- model_experiments_access_level
+- model_registry_access_level
- created_at
- updated_at
ProtectedBranch::MergeAccessLevel:
diff --git a/spec/models/concerns/routable_spec.rb b/spec/models/concerns/routable_spec.rb
index 7e324812b97..e71392f7bbc 100644
--- a/spec/models/concerns/routable_spec.rb
+++ b/spec/models/concerns/routable_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.shared_examples 'routable resource' do
- shared_examples_for '.find_by_full_path' do |has_cross_join: false|
+ shared_examples_for '.find_by_full_path' do
it 'finds records by their full path' do
expect(described_class.find_by_full_path(record.full_path)).to eq(record)
expect(described_class.find_by_full_path(record.full_path.upcase)).to eq(record)
@@ -46,22 +46,98 @@ RSpec.shared_examples 'routable resource' do
end
end
- if has_cross_join
- it 'has a cross-join' do
- expect(Gitlab::Database).to receive(:allow_cross_joins_across_databases)
+ it 'does not have cross-join' do
+ expect(Gitlab::Database).not_to receive(:allow_cross_joins_across_databases)
- described_class.find_by_full_path(record.full_path)
- end
- else
- it 'does not have cross-join' do
- expect(Gitlab::Database).not_to receive(:allow_cross_joins_across_databases)
-
- described_class.find_by_full_path(record.full_path)
- end
+ described_class.find_by_full_path(record.full_path)
end
end
it_behaves_like '.find_by_full_path', :aggregate_failures
+
+ shared_examples_for '.where_full_path_in' do
+ context 'without any paths' do
+ it 'returns an empty relation' do
+ expect(described_class.where_full_path_in([])).to eq([])
+ end
+ end
+
+ context 'without any valid paths' do
+ it 'returns an empty relation' do
+ expect(described_class.where_full_path_in(%w[unknown])).to eq([])
+ end
+ end
+
+ context 'with valid paths' do
+ it 'returns the entities matching the paths' do
+ result = described_class.where_full_path_in([record.full_path, record_2.full_path])
+
+ expect(result).to contain_exactly(record, record_2)
+ end
+
+ it 'returns entities regardless of the casing of paths' do
+ result = described_class.where_full_path_in([record.full_path.upcase, record_2.full_path.upcase])
+
+ expect(result).to contain_exactly(record, record_2)
+ end
+ end
+
+ context 'on the usage of `use_includes` parameter' do
+ let_it_be(:klass) { record.class.to_s.downcase }
+ let_it_be(:record_3) { create(:"#{klass}") }
+ let_it_be(:record_4) { create(:"#{klass}") }
+
+ context 'when use_includes: true' do
+ it 'includes route information when loading records' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ described_class.where_full_path_in([record.full_path, record_2.full_path], use_includes: true)
+ .map(&:route)
+ end
+
+ expect do
+ described_class.where_full_path_in(
+ [
+ record.full_path,
+ record_2.full_path,
+ record_3.full_path,
+ record_4.full_path
+ ], use_includes: true)
+ .map(&:route)
+ end.to issue_same_number_of_queries_as(control_count)
+ end
+ end
+
+ context 'when use_includes: false' do
+ it 'does not include route information when loading records' do
+ control_count = ActiveRecord::QueryRecorder.new do
+ described_class.where_full_path_in([record.full_path, record_2.full_path], use_includes: false)
+ .map(&:route)
+ end
+
+ expect do
+ described_class.where_full_path_in(
+ [
+ record.full_path,
+ record_2.full_path,
+ record_3.full_path,
+ record_4.full_path
+ ], use_includes: false)
+ .map(&:route)
+ end.not_to issue_same_number_of_queries_as(control_count)
+ end
+ end
+ end
+ end
+
+ it_behaves_like '.where_full_path_in', :aggregate_failures
+
+ context 'when the `optimize_where_full_path_in` feature flag is turned OFF' do
+ before do
+ stub_feature_flags(optimize_where_full_path_in: false)
+ end
+
+ it_behaves_like '.where_full_path_in', :aggregate_failures
+ end
end
RSpec.shared_examples 'routable resource with parent' do
@@ -105,10 +181,12 @@ RSpec.describe Group, 'Routable', :with_clean_rails_cache, feature_category: :gr
it_behaves_like 'routable resource' do
let_it_be(:record) { group }
+ let_it_be(:record_2) { nested_group }
end
it_behaves_like 'routable resource with parent' do
let_it_be(:record) { nested_group }
+ let_it_be(:record_2) { group }
end
describe 'Validations' do
@@ -169,34 +247,6 @@ RSpec.describe Group, 'Routable', :with_clean_rails_cache, feature_category: :gr
expect(group.route.namespace).to eq(group)
end
- describe '.where_full_path_in' do
- context 'without any paths' do
- it 'returns an empty relation' do
- expect(described_class.where_full_path_in([])).to eq([])
- end
- end
-
- context 'without any valid paths' do
- it 'returns an empty relation' do
- expect(described_class.where_full_path_in(%w[unknown])).to eq([])
- end
- end
-
- context 'with valid paths' do
- it 'returns the projects matching the paths' do
- result = described_class.where_full_path_in([group.to_param, nested_group.to_param])
-
- expect(result).to contain_exactly(group, nested_group)
- end
-
- it 'returns projects regardless of the casing of paths' do
- result = described_class.where_full_path_in([group.to_param.upcase, nested_group.to_param.upcase])
-
- expect(result).to contain_exactly(group, nested_group)
- end
- end
- end
-
describe '#parent_loaded?' do
before do
group.parent = create(:group)
@@ -232,9 +282,11 @@ end
RSpec.describe Project, 'Routable', :with_clean_rails_cache, feature_category: :groups_and_projects do
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project) { create(:project, namespace: namespace) }
+ let_it_be(:project_2) { create(:project) }
it_behaves_like 'routable resource with parent' do
let_it_be(:record) { project }
+ let_it_be(:record_2) { project_2 }
end
it 'creates route with namespace referencing project namespace' do
@@ -252,6 +304,17 @@ RSpec.describe Project, 'Routable', :with_clean_rails_cache, feature_category: :
expect(record).to be_nil
end
end
+
+ describe '.where_full_path_in' do
+ it 'does not return records if the sources are different, but the IDs match' do
+ group = create(:group, id: 1992)
+ project = create(:project, id: 1992)
+
+ records = described_class.where(id: project.id).where_full_path_in([group.full_path])
+
+ expect(records).to be_empty
+ end
+ end
end
RSpec.describe Namespaces::ProjectNamespace, 'Routable', :with_clean_rails_cache, feature_category: :groups_and_projects do
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index c0a78ff2f53..149b0d4df8c 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -31,6 +31,7 @@ RSpec.describe ProjectFeature, feature_category: :groups_and_projects do
specify { expect(subject.package_registry_access_level).to eq(ProjectFeature::ENABLED) }
specify { expect(subject.container_registry_access_level).to eq(ProjectFeature::ENABLED) }
specify { expect(subject.model_experiments_access_level).to eq(ProjectFeature::ENABLED) }
+ specify { expect(subject.model_registry_access_level).to eq(ProjectFeature::ENABLED) }
end
describe 'PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT' do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 53c2373c08b..dcd2e634ce3 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1128,6 +1128,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
it { is_expected.to delegate_method(:container_registry_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:environments_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:model_experiments_access_level).to(:project_feature) }
+ it { is_expected.to delegate_method(:model_registry_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:feature_flags_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:releases_access_level).to(:project_feature) }
it { is_expected.to delegate_method(:infrastructure_access_level).to(:project_feature) }
diff --git a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
index cb7bac771b3..1bd239ecd87 100644
--- a/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
+++ b/spec/requests/api/graphql/mutations/merge_requests/set_assignees_spec.rb
@@ -127,7 +127,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur
context 'when passing append as true' do
let(:mode) { Types::MutationOperationModeEnum.enum[:append] }
let(:input) { { assignee_usernames: [assignee2.username], operation_mode: mode } }
- let(:db_query_limit) { 23 }
+ let(:db_query_limit) { 25 }
before do
# In CE, APPEND is a NOOP as you can't have multiple assignees
@@ -147,7 +147,7 @@ RSpec.describe 'Setting assignees of a merge request', :assume_throttled, featur
end
context 'when passing remove as true' do
- let(:db_query_limit) { 31 }
+ let(:db_query_limit) { 33 }
let(:mode) { Types::MutationOperationModeEnum.enum[:remove] }
let(:input) { { assignee_usernames: [assignee.username], operation_mode: mode } }
let(:expected_result) { [] }
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index bab5bd2b6ac..ab03df84cd5 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -133,6 +133,7 @@ project_feature:
- project_id
- updated_at
- operations_access_level
+ - model_registry_access_level
computed_attributes:
- issues_enabled
- jobs_enabled
diff --git a/spec/requests/projects/pipelines_controller_spec.rb b/spec/requests/projects/pipelines_controller_spec.rb
index 7bdb66755db..e5d96631df6 100644
--- a/spec/requests/projects/pipelines_controller_spec.rb
+++ b/spec/requests/projects/pipelines_controller_spec.rb
@@ -75,6 +75,58 @@ RSpec.describe Projects::PipelinesController, feature_category: :continuous_inte
expect(response).to have_gitlab_http_status(:ok)
end
+ context 'when pipeline_stage_set_last_modified is disabled' do
+ before do
+ stub_feature_flags(pipeline_stage_set_last_modified: false)
+ end
+
+ it 'does not set Last-Modified' do
+ create(:ci_build, :retried, :failed, pipeline: pipeline, stage: 'build')
+
+ request_build_stage
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.headers['Last-Modified']).to be_nil
+ expect(response.headers['Cache-Control']).to eq('max-age=0, private, must-revalidate')
+ end
+ end
+
+ context 'when pipeline_stage_set_last_modified is enabled' do
+ before do
+ stub_feature_flags(pipeline_stage_set_last_modified: true)
+ stage.statuses.update_all(updated_at: status_timestamp)
+ end
+
+ let(:last_modified) { DateTime.parse(response.headers['Last-Modified']).utc }
+ let(:cache_control) { response.headers['Cache-Control'] }
+
+ context 'when status.updated_at is before stage.updated' do
+ let(:stage) { pipeline.stage('build') }
+ let(:status_timestamp) { stage.updated_at - 10.minutes }
+
+ it 'sets correct Last-Modified of stage.updated_at' do
+ request_build_stage
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(last_modified).to be_within(1.second).of stage.updated_at
+ expect(cache_control).to eq('max-age=86400, private')
+ end
+ end
+
+ context 'when status.updated_at is after stage.updated' do
+ let(:stage) { pipeline.stage('build') }
+ let(:status_timestamp) { stage.updated_at + 10.minutes }
+
+ it 'sets correct Last-Modified of max(status.updated_at)' do
+ request_build_stage
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(last_modified).to be_within(1.second).of status_timestamp
+ expect(cache_control).to eq('max-age=86400, private')
+ end
+ end
+ end
+
context 'with retried builds' do
it 'does not execute N+1 queries' do
create(:ci_build, :retried, :failed, pipeline: pipeline, stage: 'build')