Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/CODEOWNERS9
-rw-r--r--.rubocop_todo/rails/inverse_of.yml7
-rw-r--r--app/assets/javascripts/search/sidebar/components/app.vue4
-rw-r--r--app/assets/javascripts/search/topbar/components/app.vue79
-rw-r--r--app/controllers/application_controller.rb5
-rw-r--r--app/controllers/concerns/uploads_actions.rb1
-rw-r--r--app/controllers/projects/avatars_controller.rb2
-rw-r--r--app/controllers/projects/design_management/designs/raw_images_controller.rb2
-rw-r--r--app/controllers/projects/design_management/designs/resized_image_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb2
-rw-r--r--app/controllers/projects/repositories_controller.rb2
-rw-r--r--app/controllers/search_controller.rb1
-rw-r--r--app/models/alert_management/alert.rb5
-rw-r--r--app/models/alert_management/alert_assignee.rb2
-rw-r--r--app/models/alert_management/alert_user_mention.rb5
-rw-r--r--app/models/application_setting.rb6
-rw-r--r--app/models/audit_event.rb2
-rw-r--r--app/models/board.rb4
-rw-r--r--app/models/bulk_imports/entity.rb3
-rw-r--r--app/models/bulk_imports/tracker.rb1
-rw-r--r--app/models/group.rb3
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/packages/mark_package_for_destruction_service.rb11
-rw-r--r--app/services/packages/mark_packages_for_destruction_service.rb11
-rw-r--r--app/views/search/_results_list.html.haml25
-rw-r--r--app/views/search/_results_status.html.haml48
-rw-r--r--app/views/search/show.html.haml3
-rw-r--r--db/post_migrate/20230223082752_schedule_fk_validation_for_p_ci_builds_metadata_partitions_and_ci_builds.rb14
-rw-r--r--db/schema_migrations/202302230827521
-rw-r--r--doc/.vale/gitlab/HeadingDepth.yml2
-rw-r--r--doc/.vale/gitlab/spelling-exceptions.txt3
-rw-r--r--doc/administration/reply_by_email.md4
-rw-r--r--doc/api/group_protected_branches.md469
-rw-r--r--doc/api/protected_branches.md46
-rw-r--r--doc/ci/test_cases/index.md2
-rw-r--r--doc/development/fips_compliance.md1
-rw-r--r--doc/install/installation.md3
-rw-r--r--doc/user/crm/index.md4
-rw-r--r--doc/user/group/settings/group_access_tokens.md3
-rw-r--r--doc/user/permissions.md2
-rw-r--r--doc/user/profile/personal_access_tokens.md3
-rw-r--r--doc/user/project/requirements/index.md2
-rw-r--r--doc/user/project/service_desk.md13
-rw-r--r--doc/user/project/settings/project_access_tokens.md3
-rw-r--r--lib/banzai/filter/inline_observability_filter.rb6
-rw-r--r--lib/banzai/filter/inline_observability_redactor_filter.rb77
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb1
-rw-r--r--lib/gitlab/no_cache_headers.rb1
-rw-r--r--lib/gitlab/observability.rb13
-rw-r--r--lib/tasks/gitlab/tw/codeowners.rake2
-rw-r--r--qa/qa/specs/features/api/3_create/repository/files_spec.rb1
-rw-r--r--spec/controllers/application_controller_spec.rb23
-rw-r--r--spec/features/markdown/observability_spec.rb20
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js82
-rw-r--r--spec/lib/banzai/filter/inline_observability_filter_spec.rb55
-rw-r--r--spec/lib/banzai/filter/inline_observability_redactor_filter_spec.rb85
-rw-r--r--spec/lib/banzai/pipeline/post_process_pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/observability_spec.rb24
-rw-r--r--spec/models/alert_management/alert_assignee_spec.rb6
-rw-r--r--spec/models/alert_management/alert_spec.rb10
-rw-r--r--spec/models/alert_management/alert_user_mention_spec.rb6
-rw-r--r--spec/models/application_setting_spec.rb14
-rw-r--r--spec/models/audit_event_spec.rb4
-rw-r--r--spec/models/board_spec.rb8
-rw-r--r--spec/models/bulk_imports/entity_spec.rb7
-rw-r--r--spec/models/bulk_imports/tracker_spec.rb5
-rw-r--r--spec/models/group_spec.rb12
-rw-r--r--spec/models/user_spec.rb5
-rw-r--r--spec/requests/api/files_spec.rb1
-rw-r--r--spec/requests/api/repositories_spec.rb1
-rw-r--r--spec/services/packages/mark_package_for_destruction_service_spec.rb6
-rw-r--r--spec/services/packages/mark_packages_for_destruction_service_spec.rb5
74 files changed, 800 insertions, 507 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 8c0c48ff449..72cdaf77f52 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -645,7 +645,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/api/templates/licenses.md @rdickenson
/doc/api/todos.md @msedlakjakubowski
/doc/api/topics.md @lciutacu
-/doc/api/usage_data.md @dianalogan
+/doc/api/usage_data.md @lciutacu
/doc/api/users.md @jglassman1
/doc/api/version.md @phillipwells
/doc/api/visual_review_discussions.md @drcatherinepope
@@ -767,8 +767,8 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/development/sec/ @rdickenson
/doc/development/sec/security_report_ingestion_overview.md @dianalogan
/doc/development/secure_coding_guidelines.md @sselhorn
-/doc/development/service_ping/ @dianalogan
-/doc/development/snowplow/ @dianalogan
+/doc/development/service_ping/ @lciutacu
+/doc/development/snowplow/ @lciutacu
/doc/development/spam_protection_and_captcha/ @phillipwells
/doc/development/sql.md @aqualls
/doc/development/testing_guide/ @sselhorn
@@ -864,11 +864,12 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/admin_area/settings/rate_limit_on_issues_creation.md @msedlakjakubowski
/doc/user/admin_area/settings/rate_limit_on_notes_creation.md @msedlakjakubowski
/doc/user/admin_area/settings/rate_limit_on_pipelines_creation.md @drcatherinepope
+/doc/user/admin_area/settings/rate_limit_on_projects_api.md @lciutacu
/doc/user/admin_area/settings/rate_limit_on_users_api.md @jglassman1
/doc/user/admin_area/settings/scim_setup.md @jglassman1
/doc/user/admin_area/settings/terraform_limits.md @phillipwells
/doc/user/admin_area/settings/third_party_offers.md @lciutacu
-/doc/user/admin_area/settings/usage_statistics.md @dianalogan
+/doc/user/admin_area/settings/usage_statistics.md @lciutacu
/doc/user/admin_area/settings/visibility_and_access_controls.md @aqualls
/doc/user/analytics/ @lciutacu
/doc/user/analytics/ci_cd_analytics.md @rdickenson
diff --git a/.rubocop_todo/rails/inverse_of.yml b/.rubocop_todo/rails/inverse_of.yml
index 31535699d2e..1bb1559a394 100644
--- a/.rubocop_todo/rails/inverse_of.yml
+++ b/.rubocop_todo/rails/inverse_of.yml
@@ -1,13 +1,6 @@
---
Rails/InverseOf:
Exclude:
- - 'app/models/alert_management/alert.rb'
- - 'app/models/alert_management/alert_assignee.rb'
- - 'app/models/application_setting.rb'
- - 'app/models/audit_event.rb'
- - 'app/models/board.rb'
- - 'app/models/bulk_imports/entity.rb'
- - 'app/models/bulk_imports/tracker.rb'
- 'app/models/ci/build.rb'
- 'app/models/ci/build_pending_state.rb'
- 'app/models/ci/build_trace_chunk.rb'
diff --git a/app/assets/javascripts/search/sidebar/components/app.vue b/app/assets/javascripts/search/sidebar/components/app.vue
index 2efc80fef75..7c9e30a2d97 100644
--- a/app/assets/javascripts/search/sidebar/components/app.vue
+++ b/app/assets/javascripts/search/sidebar/components/app.vue
@@ -27,7 +27,9 @@ export default {
</script>
<template>
- <section class="search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4 gl-mb-6 gl-mt-5">
+ <section
+ class="search-sidebar gl-display-flex gl-flex-direction-column gl-md-mr-5 gl-mb-6 gl-mt-5"
+ >
<scope-navigation />
<results-filters v-if="showIssueAndMergeFilters" />
<language-filter v-if="showBlobFilter" />
diff --git a/app/assets/javascripts/search/topbar/components/app.vue b/app/assets/javascripts/search/topbar/components/app.vue
index da6039f4758..d4f866713a8 100644
--- a/app/assets/javascripts/search/topbar/components/app.vue
+++ b/app/assets/javascripts/search/topbar/components/app.vue
@@ -86,45 +86,50 @@ export default {
</script>
<template>
- <section class="search-page-form gl-lg-display-flex gl-flex-direction-column">
- <div class="gl-lg-display-flex gl-flex-direction-row gl-align-items-flex-end">
- <div class="gl-flex-grow-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2">
- <div
- class="gl-sm-display-flex gl-flex-direction-row gl-justify-content-space-between gl-mb-4 gl-md-mb-0"
- >
- <label>{{ $options.i18n.searchLabel }}</label>
- <template v-if="showSyntaxOptions">
- <gl-button
- category="tertiary"
- variant="link"
- size="small"
- button-text-classes="gl-font-sm!"
- @click="onToggleDrawer"
- >{{ $options.i18n.syntaxOptionsLabel }}
- </gl-button>
- <markdown-drawer
- ref="markdownDrawer"
- :document-path="$options.SYNTAX_OPTIONS_DOCUMENT"
- />
- </template>
+ <section class="gl-p-5 gl-bg-gray-10 gl-border-b">
+ <div class="search-page-form gl-lg-display-flex gl-flex-direction-column">
+ <div class="gl-lg-display-flex gl-flex-direction-row gl-align-items-flex-end">
+ <div class="gl-flex-grow-1 gl-mb-4 gl-lg-mb-0 gl-lg-mr-2">
+ <div
+ class="gl-display-flex gl-flex-direction-row gl-justify-content-space-between gl-mb-0 gl-md-mb-4"
+ >
+ <label class="gl-mb-1 gl-md-pb-2">{{ $options.i18n.searchLabel }}</label>
+ <template v-if="showSyntaxOptions">
+ <gl-button
+ category="tertiary"
+ variant="link"
+ size="small"
+ button-text-classes="gl-font-sm!"
+ @click="onToggleDrawer"
+ >{{ $options.i18n.syntaxOptionsLabel }}
+ </gl-button>
+ <markdown-drawer
+ ref="markdownDrawer"
+ :document-path="$options.SYNTAX_OPTIONS_DOCUMENT"
+ />
+ </template>
+ </div>
+ <gl-search-box-by-click
+ id="dashboard_search"
+ v-model="search"
+ name="search"
+ :placeholder="$options.i18n.searchPlaceholder"
+ @submit="applyQuery"
+ />
+ </div>
+ <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-3">
+ <label class="gl-display-block gl-mb-1 gl-md-pb-2">{{
+ $options.i18n.groupFieldLabel
+ }}</label>
+ <group-filter :initial-data="groupInitialJson" />
+ </div>
+ <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-ml-3">
+ <label class="gl-display-block gl-mb-1 gl-md-pb-2">{{
+ $options.i18n.projectFieldLabel
+ }}</label>
+ <project-filter :initial-data="projectInitialJson" />
</div>
- <gl-search-box-by-click
- id="dashboard_search"
- v-model="search"
- name="search"
- :placeholder="$options.i18n.searchPlaceholder"
- @submit="applyQuery"
- />
- </div>
- <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-mx-3">
- <label class="gl-display-block">{{ $options.i18n.groupFieldLabel }}</label>
- <group-filter :initial-data="groupInitialJson" />
- </div>
- <div v-if="showFilters" class="gl-mb-4 gl-lg-mb-0 gl-lg-ml-3">
- <label class="gl-display-block">{{ $options.i18n.projectFieldLabel }}</label>
- <project-filter :initial-data="projectInitialJson" />
</div>
</div>
- <hr class="gl-mt-5 gl-mb-0 gl-border-gray-100" />
</section>
</template>
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5bca5a91ac5..ff888cf9d72 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -33,7 +33,6 @@ class ApplicationController < ActionController::Base
before_action :check_password_expiration, if: :html_request?
before_action :ldap_security_check
before_action :default_headers
- before_action :default_cache_headers
before_action :add_gon_variables, if: :html_request?
before_action :configure_permitted_parameters, if: :devise_controller?
before_action :require_email, unless: :devise_controller?
@@ -316,10 +315,6 @@ class ApplicationController < ActionController::Base
headers['X-Content-Type-Options'] = 'nosniff'
end
- def default_cache_headers
- headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility
- end
-
def stream_csv_headers(csv_filename)
no_cache_headers
stream_headers
diff --git a/app/controllers/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 308da018a42..e53d0bc65a0 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -9,7 +9,6 @@ module UploadsActions
included do
prepend_before_action :set_request_format_from_path_extension
- skip_before_action :default_cache_headers, only: :show
rescue_from FileUploader::InvalidSecret, with: :render_404
end
diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb
index 70d9b524e4d..5db7609e07a 100644
--- a/app/controllers/projects/avatars_controller.rb
+++ b/app/controllers/projects/avatars_controller.rb
@@ -3,8 +3,6 @@
class Projects::AvatarsController < Projects::ApplicationController
include SendsBlob
- skip_before_action :default_cache_headers, only: :show
-
before_action :authorize_admin_project!, only: [:destroy]
feature_category :projects
diff --git a/app/controllers/projects/design_management/designs/raw_images_controller.rb b/app/controllers/projects/design_management/designs/raw_images_controller.rb
index beb7e9d294b..ea406d2f2ef 100644
--- a/app/controllers/projects/design_management/designs/raw_images_controller.rb
+++ b/app/controllers/projects/design_management/designs/raw_images_controller.rb
@@ -7,8 +7,6 @@ module Projects
class RawImagesController < Projects::DesignManagement::DesignsController
include SendsBlob
- skip_before_action :default_cache_headers, only: :show
-
def show
blob = design_repository.blob_at(ref, design.full_path)
diff --git a/app/controllers/projects/design_management/designs/resized_image_controller.rb b/app/controllers/projects/design_management/designs/resized_image_controller.rb
index 6bf304419e1..a09d8a73892 100644
--- a/app/controllers/projects/design_management/designs/resized_image_controller.rb
+++ b/app/controllers/projects/design_management/designs/resized_image_controller.rb
@@ -10,8 +10,6 @@ module Projects
before_action :validate_size!
before_action :validate_sha!
- skip_before_action :default_cache_headers, only: :show
-
def show
relation = design.actions
relation = relation.up_to_version(version) if version
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 895a9a00624..79b5990abba 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -6,8 +6,6 @@ class Projects::RawController < Projects::ApplicationController
include SendsBlob
include StaticObjectExternalStorage
- skip_before_action :default_cache_headers, only: :show
-
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:blob) }
before_action :assign_ref_vars
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index e688720ce6a..80bc92c0b69 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -8,8 +8,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) }
- skip_before_action :default_cache_headers, only: :archive
-
# Authorize
before_action :check_archive_rate_limiting!, only: :archive
before_action :require_non_empty_project, except: :create
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 9df86e2c702..d1f846ae55c 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -24,7 +24,6 @@ class SearchController < ApplicationController
before_action :block_anonymous_global_searches, :check_scope_global_search_enabled, except: :opensearch
skip_before_action :authenticate_user!
- skip_before_action :default_cache_headers, only: [:count, :autocomplete]
requires_cross_project_access if: -> do
search_term_present = params[:search].present? || params[:term].present?
diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb
index a5a539eae75..4af9b644362 100644
--- a/app/models/alert_management/alert.rb
+++ b/app/models/alert_management/alert.rb
@@ -25,8 +25,9 @@ module AlertManagement
has_many :assignees, through: :alert_assignees
has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
- has_many :ordered_notes, -> { fresh }, as: :noteable, class_name: 'Note'
- has_many :user_mentions, class_name: 'AlertManagement::AlertUserMention', foreign_key: :alert_management_alert_id
+ has_many :ordered_notes, -> { fresh }, as: :noteable, class_name: 'Note', inverse_of: :noteable
+ has_many :user_mentions, class_name: 'AlertManagement::AlertUserMention', foreign_key: :alert_management_alert_id,
+ inverse_of: :alert
has_many :metric_images, class_name: '::AlertManagement::MetricImage'
has_internal_id :iid, scope: :project
diff --git a/app/models/alert_management/alert_assignee.rb b/app/models/alert_management/alert_assignee.rb
index c74b2699182..27e720c3262 100644
--- a/app/models/alert_management/alert_assignee.rb
+++ b/app/models/alert_management/alert_assignee.rb
@@ -3,7 +3,7 @@
module AlertManagement
class AlertAssignee < ApplicationRecord
belongs_to :alert, inverse_of: :alert_assignees
- belongs_to :assignee, class_name: 'User', foreign_key: :user_id
+ belongs_to :assignee, class_name: 'User', foreign_key: :user_id, inverse_of: :alert_assignees
validates :alert, presence: true
validates :assignee, presence: true, uniqueness: { scope: :alert_id }
diff --git a/app/models/alert_management/alert_user_mention.rb b/app/models/alert_management/alert_user_mention.rb
index d36aa80ee05..1ab71127677 100644
--- a/app/models/alert_management/alert_user_mention.rb
+++ b/app/models/alert_management/alert_user_mention.rb
@@ -2,7 +2,10 @@
module AlertManagement
class AlertUserMention < UserMention
- belongs_to :alert_management_alert, class_name: '::AlertManagement::Alert'
+ belongs_to :alert, class_name: '::AlertManagement::Alert',
+ foreign_key: :alert_management_alert_id,
+ inverse_of: :user_mentions
+
belongs_to :note
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c80ff359a69..7219374fe2a 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -30,11 +30,13 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord
add_authentication_token_field :static_objects_external_storage_auth_token, encrypted: :required
add_authentication_token_field :error_tracking_access_token, encrypted: :required
- belongs_to :self_monitoring_project, class_name: "Project", foreign_key: 'instance_administration_project_id'
+ belongs_to :self_monitoring_project, class_name: "Project", foreign_key: :instance_administration_project_id,
+ inverse_of: :application_setting
belongs_to :push_rule
alias_attribute :self_monitoring_project_id, :instance_administration_project_id
- belongs_to :instance_group, class_name: "Group", foreign_key: 'instance_administrators_group_id'
+ belongs_to :instance_group, class_name: "Group", foreign_key: :instance_administrators_group_id,
+ inverse_of: :application_setting
alias_attribute :instance_group_id, :instance_administrators_group_id
alias_attribute :instance_administrators_group, :instance_group
alias_attribute :housekeeping_optimize_repository_period, :housekeeping_incremental_repack_period
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 3312216932b..163e741d990 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -21,7 +21,7 @@ class AuditEvent < ApplicationRecord
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
- belongs_to :user, foreign_key: :author_id
+ belongs_to :user, foreign_key: :author_id, inverse_of: :audit_events
validates :author_id, presence: true
validates :entity_id, presence: true
diff --git a/app/models/board.rb b/app/models/board.rb
index 2181b2f0545..702ae0cc9f5 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -6,8 +6,8 @@ class Board < ApplicationRecord
belongs_to :group
belongs_to :project
- has_many :lists, -> { ordered }, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
- has_many :destroyable_lists, -> { destroyable.ordered }, class_name: "List"
+ has_many :lists, -> { ordered }, dependent: :delete_all, inverse_of: :board # rubocop:disable Cop/ActiveRecordDependent
+ has_many :destroyable_lists, -> { destroyable.ordered }, class_name: "List", inverse_of: :board
validates :name, presence: true
validates :project, presence: true, if: :project_needed?
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index 66e6d92f2ed..887c39dfd58 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -26,10 +26,11 @@ class BulkImports::Entity < ApplicationRecord
belongs_to :parent, class_name: 'BulkImports::Entity', optional: true
belongs_to :project, optional: true
- belongs_to :group, foreign_key: :namespace_id, optional: true
+ belongs_to :group, foreign_key: :namespace_id, optional: true, inverse_of: :bulk_import_entities
has_many :trackers,
class_name: 'BulkImports::Tracker',
+ inverse_of: :entity,
foreign_key: :bulk_import_entity_id
has_many :failures,
diff --git a/app/models/bulk_imports/tracker.rb b/app/models/bulk_imports/tracker.rb
index b04ef1cb7ae..701f1a9e49e 100644
--- a/app/models/bulk_imports/tracker.rb
+++ b/app/models/bulk_imports/tracker.rb
@@ -7,6 +7,7 @@ class BulkImports::Tracker < ApplicationRecord
belongs_to :entity,
class_name: 'BulkImports::Entity',
+ inverse_of: :trackers,
foreign_key: :bulk_import_entity_id,
optional: false
diff --git a/app/models/group.rb b/app/models/group.rb
index 7e09280dfff..38eb11a0d74 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -110,7 +110,10 @@ class Group < Namespace
has_one :import_state, class_name: 'GroupImportState', inverse_of: :group
+ has_many :application_setting, foreign_key: :instance_administrators_group_id, inverse_of: :instance_group
+
has_many :bulk_import_exports, class_name: 'BulkImports::Export', inverse_of: :group
+ has_many :bulk_import_entities, class_name: 'BulkImports::Entity', foreign_key: :namespace_id, inverse_of: :group
has_many :group_deploy_keys_groups, inverse_of: :group
has_many :group_deploy_keys, through: :group_deploy_keys_groups
diff --git a/app/models/project.rb b/app/models/project.rb
index cd35332d7df..f363f189178 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -167,6 +167,8 @@ class Project < ApplicationRecord
has_one :last_event, -> { order 'events.created_at DESC' }, class_name: 'Event'
has_many :boards
+ has_many :application_setting, inverse_of: :self_monitoring_project
+
def self.integration_association_name(name)
"#{name}_integration"
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 9a9530c4d3a..8ff69d3f67f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -228,7 +228,9 @@ class User < ApplicationRecord
has_many :notification_settings
has_many :award_emoji, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :triggers, class_name: 'Ci::Trigger', foreign_key: :owner_id
+ has_many :audit_events, foreign_key: :author_id, inverse_of: :user
+ has_many :alert_assignees, class_name: '::AlertManagement::AlertAssignee', inverse_of: :assignee
has_many :issue_assignees, inverse_of: :assignee
has_many :merge_request_assignees, inverse_of: :assignee, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :merge_request_reviewers, inverse_of: :reviewer, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
diff --git a/app/services/packages/mark_package_for_destruction_service.rb b/app/services/packages/mark_package_for_destruction_service.rb
index 3417febe79a..8ccc242ae36 100644
--- a/app/services/packages/mark_package_for_destruction_service.rb
+++ b/app/services/packages/mark_package_for_destruction_service.rb
@@ -13,7 +13,8 @@ module Packages
package.sync_maven_metadata(current_user)
service_response_success('Package was successfully marked as pending destruction')
- rescue StandardError
+ rescue StandardError => e
+ track_exception(e)
service_response_error('Failed to mark the package as pending destruction', 400)
end
@@ -30,5 +31,13 @@ module Packages
def user_can_delete_package?
can?(current_user, :destroy_package, package.project)
end
+
+ def track_exception(error)
+ Gitlab::ErrorTracking.track_exception(
+ error,
+ project_id: package.project_id,
+ package_id: package.id
+ )
+ end
end
end
diff --git a/app/services/packages/mark_packages_for_destruction_service.rb b/app/services/packages/mark_packages_for_destruction_service.rb
index 023392cf2d9..ade9ad2c974 100644
--- a/app/services/packages/mark_packages_for_destruction_service.rb
+++ b/app/services/packages/mark_packages_for_destruction_service.rb
@@ -31,13 +31,15 @@ module Packages
def execute(batch_size: BATCH_SIZE)
no_access = false
min_batch_size = [batch_size, BATCH_SIZE].min
+ package_ids = []
@packages.each_batch(of: min_batch_size) do |batched_packages|
loaded_packages = batched_packages.including_project_route.to_a
+ package_ids = loaded_packages.map(&:id)
break no_access = true unless can_destroy_packages?(loaded_packages)
- ::Packages::Package.id_in(loaded_packages.map(&:id))
+ ::Packages::Package.id_in(package_ids)
.update_all(status: :pending_destruction)
sync_maven_metadata(loaded_packages)
@@ -47,7 +49,8 @@ module Packages
return UNAUTHORIZED_RESPONSE if no_access
SUCCESS_RESPONSE
- rescue StandardError
+ rescue StandardError => e
+ track_exception(e, package_ids)
ERROR_RESPONSE
end
@@ -75,5 +78,9 @@ module Packages
can?(@current_user, :destroy_package, package)
end
end
+
+ def track_exception(error, package_ids)
+ Gitlab::ErrorTracking.track_exception(error, package_ids: package_ids)
+ end
end
end
diff --git a/app/views/search/_results_list.html.haml b/app/views/search/_results_list.html.haml
index 7a57b5cc0fc..ce4dd02b41d 100644
--- a/app/views/search/_results_list.html.haml
+++ b/app/views/search/_results_list.html.haml
@@ -5,16 +5,17 @@
- elsif @search_objects.to_a.empty?
= render partial: "search/results/empty"
- else
- - if @scope == 'commits'
- %ul.content-list.commit-list
- = render partial: "search/results/commit", collection: @search_objects
- - else
- .search-results.js-search-results
- - if @scope == 'projects'
- .term
- = render 'shared/projects/list', projects: @search_objects, pipeline_status: false
- - else
- = render_if_exists partial: "search/results/#{@scope.singularize}", collection: @search_objects
+ .gl-md-pl-5
+ - if @scope == 'commits'
+ %ul.content-list.commit-list
+ = render partial: "search/results/commit", collection: @search_objects
+ - else
+ .search-results.js-search-results
+ - if @scope == 'projects'
+ .term
+ = render 'shared/projects/list', projects: @search_objects, pipeline_status: false
+ - else
+ = render_if_exists partial: "search/results/#{@scope.singularize}", collection: @search_objects
- - if @scope != 'projects'
- = paginate_collection(@search_objects)
+ - if @scope != 'projects'
+ = paginate_collection(@search_objects)
diff --git a/app/views/search/_results_status.html.haml b/app/views/search/_results_status.html.haml
index 27405631360..2321abb31df 100644
--- a/app/views/search/_results_status.html.haml
+++ b/app/views/search/_results_status.html.haml
@@ -1,25 +1,25 @@
- return unless @search_service_presenter.show_results_status?
-
-.search-results-status
- .gl-display-flex.gl-flex-direction-column
- .gl-p-5.gl-display-flex
- .gl-md-display-flex.gl-text-left.gl-align-items-center.gl-flex-grow-1.gl-white-space-nowrap.gl-max-w-full
- - unless @search_service_presenter.without_count?
- = search_entries_info(@search_objects, @scope, @search_term)
- - unless @search_service_presenter.show_snippets?
- - if @project
- - link_to_project = link_to(@project.full_name, @project, class: 'ml-md-1 gl-text-truncate search-wrap-f-md-down')
- - if @scope == 'blobs'
- = _("in")
- .mx-md-1
- #js-blob-ref-switcher{ data: { "project-id" => @project.id, "ref" => repository_ref(@project), "field-name": "repository_ref" } }
- = s_('SearchCodeResults|of %{link_to_project}').html_safe % { link_to_project: link_to_project }
- - else
- = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
- - elsif @group
- - link_to_group = link_to(@group.name, @group, class: 'ml-md-1')
- = _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
- - if @search_service_presenter.show_sort_dropdown?
- .gl-md-display-flex.gl-flex-direction-column
- #js-search-sort{ data: { "search-sort-options" => search_sort_options.to_json } }
- %hr.gl-mb-5.gl-mt-0.gl-border-gray-100.gl-w-full
+.gl-md-pl-5
+ .search-results-status
+ .gl-display-flex.gl-flex-direction-column
+ .gl-p-5.gl-display-flex
+ .gl-md-display-flex.gl-text-left.gl-align-items-center.gl-flex-grow-1.gl-white-space-nowrap.gl-max-w-full
+ - unless @search_service_presenter.without_count?
+ = search_entries_info(@search_objects, @scope, @search_term)
+ - unless @search_service_presenter.show_snippets?
+ - if @project
+ - link_to_project = link_to(@project.full_name, @project, class: 'ml-md-1 gl-text-truncate search-wrap-f-md-down')
+ - if @scope == 'blobs'
+ = _("in")
+ .mx-md-1
+ #js-blob-ref-switcher{ data: { "project-id" => @project.id, "ref" => repository_ref(@project), "field-name": "repository_ref" } }
+ = s_('SearchCodeResults|of %{link_to_project}').html_safe % { link_to_project: link_to_project }
+ - else
+ = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
+ - elsif @group
+ - link_to_group = link_to(@group.name, @group, class: 'ml-md-1')
+ = _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
+ - if @search_service_presenter.show_sort_dropdown?
+ .gl-md-display-flex.gl-flex-direction-column
+ #js-search-sort{ data: { "search-sort-options" => search_sort_options.to_json } }
+ %hr.gl-mb-5.gl-mt-0.gl-border-gray-100.gl-w-full
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 04103794e60..808ccfbd716 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -19,7 +19,6 @@
%h1.page-title.gl-font-size-h-display.gl-mr-5= _('Search')
= render_if_exists 'search/form_elasticsearch', attrs: { class: 'mb-2 mb-sm-0 align-self-center' }
-.gl-mt-3
- #js-search-topbar{ data: { "group-initial-json": group_attributes.to_json, "project-initial-json": project_attributes.to_json, "elasticsearch-enabled": @search_service_presenter.advanced_search_enabled?.to_s, "default-branch-name": @project&.default_branch } }
+#js-search-topbar{ data: { "group-initial-json": group_attributes.to_json, "project-initial-json": project_attributes.to_json, "elasticsearch-enabled": @search_service_presenter.advanced_search_enabled?.to_s, "default-branch-name": @project&.default_branch } }
- if @search_term
= render 'search/results'
diff --git a/db/post_migrate/20230223082752_schedule_fk_validation_for_p_ci_builds_metadata_partitions_and_ci_builds.rb b/db/post_migrate/20230223082752_schedule_fk_validation_for_p_ci_builds_metadata_partitions_and_ci_builds.rb
new file mode 100644
index 00000000000..583a9cd31f7
--- /dev/null
+++ b/db/post_migrate/20230223082752_schedule_fk_validation_for_p_ci_builds_metadata_partitions_and_ci_builds.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class ScheduleFkValidationForPCiBuildsMetadataPartitionsAndCiBuilds < Gitlab::Database::Migration[2.1]
+ TABLE_NAME = :p_ci_builds_metadata
+ FK_NAME = :fk_e20479742e_p
+
+ def up
+ prepare_partitioned_async_foreign_key_validation TABLE_NAME, name: FK_NAME
+ end
+
+ def down
+ unprepare_partitioned_async_foreign_key_validation TABLE_NAME, name: FK_NAME
+ end
+end
diff --git a/db/schema_migrations/20230223082752 b/db/schema_migrations/20230223082752
new file mode 100644
index 00000000000..83789c7ffe8
--- /dev/null
+++ b/db/schema_migrations/20230223082752
@@ -0,0 +1 @@
+53f1003eeb8f961b37d90c73a71f75683077b9bcd0e495395033998530a363bd \ No newline at end of file
diff --git a/doc/.vale/gitlab/HeadingDepth.yml b/doc/.vale/gitlab/HeadingDepth.yml
index 5bbe667481c..f29ec0e4ac7 100644
--- a/doc/.vale/gitlab/HeadingDepth.yml
+++ b/doc/.vale/gitlab/HeadingDepth.yml
@@ -6,7 +6,7 @@
# For a list of all options, see https://vale.sh/docs/topics/styles/
extends: existence
message: "Refactor the section or page to avoid headings greater than H5."
-link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#headings-in-markdown
+link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#heading-levels-in-markdown
level: suggestion
scope: raw
raw:
diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt
index a6ac3b0e3b8..8277ffe96ea 100644
--- a/doc/.vale/gitlab/spelling-exceptions.txt
+++ b/doc/.vale/gitlab/spelling-exceptions.txt
@@ -617,6 +617,7 @@ Nurtch
NVMe
nyc
OAuth
+OCP
Octokit
offboarded
offboarding
@@ -625,6 +626,7 @@ OIDs
OKRs
OKRs
Okta
+OLM
OmniAuth
onboarding
OpenID
@@ -668,6 +670,7 @@ Pipfile
Pipfiles
Piwik
plaintext
+podman
Poedit
polyfill
polyfills
diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md
index 6e97ffc3b47..5172a9613ee 100644
--- a/doc/administration/reply_by_email.md
+++ b/doc/administration/reply_by_email.md
@@ -1,6 +1,6 @@
---
-stage: Plan
-group: Certify
+stage: Monitor
+group: Respond
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
diff --git a/doc/api/group_protected_branches.md b/doc/api/group_protected_branches.md
new file mode 100644
index 00000000000..db648f6870e
--- /dev/null
+++ b/doc/api/group_protected_branches.md
@@ -0,0 +1,469 @@
+---
+stage: Create
+group: Source Code
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Group-level protected branches API **(PREMIUM SELF)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110603) in GitLab 15.9 [with a flag](../administration/feature_flags.md) named `group_protected_branches`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `group_protected_branches`.
+On GitLab.com, this feature is not available.
+
+## Valid access levels
+
+The access levels are defined in the `ProtectedRefAccess.allowed_access_levels` method.
+These levels are recognized:
+
+```plaintext
+0 => No access
+30 => Developer access
+40 => Maintainer access
+60 => Admin access
+```
+
+## List protected branches
+
+Gets a list of protected branches from a group. If a wildcard is set, it is returned instead
+of the exact name of the branches that match that wildcard.
+
+```plaintext
+GET /groups/:id/protected_branches
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `search` | string | no | Name or part of the name of protected branches to be searched for. |
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/groups/5/protected_branches"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "master",
+ "push_access_levels": [
+ {
+ "id": 1,
+ "access_level": 40,
+ "user_id": null,
+ "group_id": 1234,
+ "access_level_description": "Maintainers"
+ }
+ ],
+ "merge_access_levels": [
+ {
+ "id": 1,
+ "access_level": 40,
+ "user_id": null,
+ "group_id": 1234,
+ "access_level_description": "Maintainers"
+ }
+ ],
+ "allow_force_push":false,
+ "code_owner_approval_required": false
+ },
+ {
+ "id": 1,
+ "name": "release/*",
+ "push_access_levels": [
+ {
+ "id": 1,
+ "access_level": 40,
+ "user_id": null,
+ "group_id": null,
+ "access_level_description": "Maintainers"
+ }
+ ],
+ "merge_access_levels": [
+ {
+ "id": 1,
+ "access_level": 40,
+ "user_id": null,
+ "group_id": 1234,
+ "access_level_description": "Maintainers"
+ }
+ ],
+ "allow_force_push":false,
+ "code_owner_approval_required": false
+ },
+ ...
+]
+```
+
+## Get a single protected branch or wildcard protected branch
+
+Gets a single protected branch or wildcard protected branch.
+
+```plaintext
+GET /groups/:id/protected_branches/:name
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `name` | string | yes | The name of the branch or wildcard. |
+
+```shell
+curl --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/groups/5/protected_branches/master"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "master",
+ "push_access_levels": [
+ {
+ "id": 1,
+ "access_level": 40,
+ "user_id": null,
+ "group_id": null,
+ "access_level_description": "Maintainers"
+ }
+ ],
+ "merge_access_levels": [
+ {
+ "id": 1,
+ "access_level": null,
+ "user_id": null,
+ "group_id": 1234,
+ "access_level_description": "Example Merge Group"
+ }
+ ],
+ "allow_force_push":false,
+ "code_owner_approval_required": false
+}
+```
+
+## Protect repository branches
+
+Protects a single repository branch using a wildcard protected branch.
+
+```plaintext
+POST /groups/:id/protected_branches
+```
+
+```shell
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/groups/5/protected_branches?name=*-stable&push_access_level=30&merge_access_level=30&unprotect_access_level=40"
+```
+
+| Attribute | Type | Required | Description |
+| -------------------------------------------- | ---- | -------- | ----------- |
+| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `name` | string | yes | The name of the branch or wildcard. |
+| `allow_force_push` | boolean | no | Allow all users with push access to force push. Default: `false`. |
+| `allowed_to_merge` | array | no | Array of access levels allowed to merge, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`. |
+| `allowed_to_push` | array | no | Array of access levels allowed to push, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`. |
+| `allowed_to_unprotect` | array | no | Array of access levels allowed to unprotect, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`. |
+| `code_owner_approval_required` | boolean | no | Prevent pushes to this branch if it matches an item in the [`CODEOWNERS` file](../user/project/code_owners.md). Default: `false`. |
+| `merge_access_level` | integer | no | Access levels allowed to merge. Defaults: `40`, Maintainer role. |
+| `push_access_level` | integer | no | Access levels allowed to push. Defaults: `40`, Maintainer role. |
+| `unprotect_access_level` | integer | no | Access levels allowed to unprotect. Defaults: `40`, Maintainer role. |
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "*-stable",
+ "push_access_levels": [
+ {
+ "id": 1,
+ "access_level": 30,
+ "user_id": null,
+ "group_id": null,
+ "access_level_description": "Developers + Maintainers"
+ }
+ ],
+ "merge_access_levels": [
+ {
+ "id": 1,
+ "access_level": 30,
+ "user_id": null,
+ "group_id": null,
+ "access_level_description": "Developers + Maintainers"
+ }
+ ],
+ "unprotect_access_levels": [
+ {
+ "id": 1,
+ "access_level": 40,
+ "user_id": null,
+ "group_id": null,
+ "access_level_description": "Maintainers"
+ }
+ ],
+ "allow_force_push":false,
+ "code_owner_approval_required": false
+}
+```
+
+### Example with user / group level access
+
+Elements in the `allowed_to_push` / `allowed_to_merge` / `allowed_to_unprotect` array should take the
+form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`. Each user must have
+access to the project and each group must
+[have this project shared](../user/project/members/share_project_with_groups.md). These access levels
+allow [more granular control over protected branch access](../user/project/protected_branches.md).
+
+```shell
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/groups/5/protected_branches?name=*-stable&allowed_to_push%5B%5D%5Buser_id%5D=1"
+```
+
+Example response:
+
+```json
+{
+ "id": 1,
+ "name": "*-stable",
+ "push_access_levels": [
+ {
+ "id": 1,
+ "access_level": null,
+ "user_id": 1,
+ "group_id": null,
+ "access_level_description": "Administrator"
+ }
+ ],
+ "merge_access_levels": [
+ {
+ "id": 1,
+ "access_level": 40,
+ "user_id": null,
+ "group_id": null,
+ "access_level_description": "Maintainers"
+ }
+ ],
+ "unprotect_access_levels": [
+ {
+ "id": 1,
+ "access_level": 40,
+ "user_id": null,
+ "group_id": null,
+ "access_level_description": "Maintainers"
+ }
+ ],
+ "allow_force_push":false,
+ "code_owner_approval_required": false
+}
+```
+
+### Example with allow to push and allow to merge access
+
+Example request:
+
+```shell
+curl --request POST \
+ --header "PRIVATE-TOKEN: <your_access_token>" \
+ --header "Content-Type: application/json" \
+ --data '{
+ "name": "master",
+ "allowed_to_push": [{"access_level": 30}],
+ "allowed_to_merge": [{
+ "access_level": 30
+ },{
+ "access_level": 40
+ }
+ ]}'
+ "https://gitlab.example.com/api/v4/groups/5/protected_branches"
+```
+
+Example response:
+
+```json
+{
+ "id": 5,
+ "name": "master",
+ "push_access_levels": [
+ {
+ "id": 1,
+ "access_level": 30,
+ "access_level_description": "Developers + Maintainers",
+ "user_id": null,
+ "group_id": null
+ }
+ ],
+ "merge_access_levels": [
+ {
+ "id": 1,
+ "access_level": 30,
+ "access_level_description": "Developers + Maintainers",
+ "user_id": null,
+ "group_id": null
+ },
+ {
+ "id": 2,
+ "access_level": 40,
+ "access_level_description": "Maintainers",
+ "user_id": null,
+ "group_id": null
+ }
+ ],
+ "unprotect_access_levels": [
+ {
+ "id": 1,
+ "access_level": 40,
+ "access_level_description": "Maintainers",
+ "user_id": null,
+ "group_id": null
+ }
+ ],
+ "allow_force_push":false,
+ "code_owner_approval_required": false
+}
+```
+
+## Unprotect repository branches
+
+Unprotects the given protected branch or wildcard protected branch.
+
+```plaintext
+DELETE /groups/:id/protected_branches/:name
+```
+
+```shell
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/groups/5/protected_branches/*-stable"
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `name` | string | yes | The name of the branch. |
+
+Example response:
+
+```json
+{
+ "name": "master",
+ "push_access_levels": [
+ {
+ "id": 12,
+ "access_level": 40,
+ "access_level_description": "Maintainers",
+ "user_id": null,
+ "group_id": null
+ }
+ ]
+}
+```
+
+## Update a protected branch
+
+Updates a protected branch.
+
+```plaintext
+PATCH /groups/:id/protected_branches/:name
+```
+
+```shell
+curl --request PATCH --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/groups/5/protected_branches/feature-branch?allow_force_push=true&code_owner_approval_required=true"
+```
+
+| Attribute | Type | Required | Description |
+| -------------------------------------------- | ---- | -------- | ----------- |
+| `id` | integer or string | yes | The ID or [URL-encoded path of the group](rest/index.md#namespaced-path-encoding) owned by the authenticated user. |
+| `name` | string | yes | The name of the branch. |
+| `allow_force_push` | boolean | no | When enabled, members who can push to this branch can also force push. |
+| `allowed_to_push` | array | no | Array of push access levels, with each described by a hash. |
+| `allowed_to_merge` | array | no | Array of merge access levels, with each described by a hash. |
+| `allowed_to_unprotect` | array | no | Array of unprotect access levels, with each described by a hash. |
+| `code_owner_approval_required` | boolean | no | Prevent pushes to this branch if it matches an item in the [`CODEOWNERS` file](../user/project/code_owners.md). Default: `false`. |
+
+Elements in the `allowed_to_push`, `allowed_to_merge` and `allowed_to_unprotect` arrays should:
+
+- Be one of `user_id`, `group_id`, or `access_level`.
+- Take the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`.
+
+To update:
+
+- `user_id`: Ensure the updated user has access to the project. You must also pass the
+ `id` of the `access_level` in the respective hash.
+- `group_id`: Ensure the updated group [has this project shared](../user/project/members/share_project_with_groups.md).
+ You must also pass the `id` of the `access_level` in the respective hash.
+
+To delete:
+
+- You must pass `_destroy` set to `true`. See the following examples.
+
+### Example: create a `push_access_level` record
+
+```shell
+curl --header 'Content-Type: application/json' --request PATCH \
+ --data '{"allowed_to_push": [{access_level: 40}]}' \
+ --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/groups/22034114/protected_branches/master"
+```
+
+Example response:
+
+```json
+{
+ "name": "master",
+ "push_access_levels": [
+ {
+ "id": 12,
+ "access_level": 40,
+ "access_level_description": "Maintainers",
+ "user_id": null,
+ "group_id": null
+ }
+ ]
+}
+```
+
+### Example: update a `push_access_level` record
+
+```shell
+curl --header 'Content-Type: application/json' --request PATCH \
+ --data '{"allowed_to_push": [{"id": 12, "access_level": 0}]' \
+ --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/22034114/protected_branches/master"
+```
+
+Example response:
+
+```json
+{
+ "name": "master",
+ "push_access_levels": [
+ {
+ "id": 12,
+ "access_level": 0,
+ "access_level_description": "No One",
+ "user_id": null,
+ "group_id": null
+ }
+ ]
+}
+```
+
+### Example: delete a `push_access_level` record
+
+```shell
+curl --header 'Content-Type: application/json' --request PATCH \
+ --data '{"allowed_to_push": [{"id": 12, "_destroy": true}]}' \
+ --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/22034114/protected_branches/master"
+```
+
+Example response:
+
+```json
+{
+ "name": "master",
+ "push_access_levels": []
+}
+```
diff --git a/doc/api/protected_branches.md b/doc/api/protected_branches.md
index 649301d6e5e..6c3bd63bbad 100644
--- a/doc/api/protected_branches.md
+++ b/doc/api/protected_branches.md
@@ -208,16 +208,16 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitla
| Attribute | Type | Required | Description |
| -------------------------------------------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `name` | string | yes | The name of the branch or wildcard |
-| `push_access_level` | integer | no | Access levels allowed to push (defaults: `40`, Maintainer role) |
-| `merge_access_level` | integer | no | Access levels allowed to merge (defaults: `40`, Maintainer role) |
-| `unprotect_access_level` | integer | no | Access levels allowed to unprotect (defaults: `40`, Maintainer role) |
-| `allow_force_push` | boolean | no | Allow all users with push access to force push. (default: `false`) |
-| `allowed_to_push` **(PREMIUM)** | array | no | Array of access levels allowed to push, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}` |
-| `allowed_to_merge` **(PREMIUM)** | array | no | Array of access levels allowed to merge, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}` |
-| `allowed_to_unprotect` **(PREMIUM)** | array | no | Array of access levels allowed to unprotect, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}` |
-| `code_owner_approval_required` **(PREMIUM)** | boolean | no | Prevent pushes to this branch if it matches an item in the [`CODEOWNERS` file](../user/project/code_owners.md). (defaults: false) |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user.
+| `name` | string | yes | The name of the branch or wildcard.
+| `allow_force_push` | boolean | no | Allow all users with push access to force push. (default: `false`)
+| `allowed_to_merge` **(PREMIUM)** | array | no | Array of access levels allowed to merge, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`.
+| `allowed_to_push` **(PREMIUM)** | array | no | Array of access levels allowed to push, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`.
+| `allowed_to_unprotect` **(PREMIUM)** | array | no | Array of access levels allowed to unprotect, with each described by a hash of the form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`. The access level `No access` is not available for this field. |
+| `code_owner_approval_required` **(PREMIUM)** | boolean | no | Prevent pushes to this branch if it matches an item in the [`CODEOWNERS` file](../user/project/code_owners.md). (defaults: false)
+| `merge_access_level` | integer | no | Access levels allowed to merge. (defaults: `40`, Maintainer role)
+| `push_access_level` | integer | no | Access levels allowed to push. (defaults: `40`, Maintainer role)
+| `unprotect_access_level` | integer | no | Access levels allowed to unprotect. (defaults: `40`, Maintainer role)
Example response:
@@ -297,8 +297,6 @@ Example response:
Elements in the `allowed_to_push` / `allowed_to_merge` / `allowed_to_unprotect` array should take the
form `{user_id: integer}`, `{group_id: integer}`, or `{access_level: integer}`. Each user must have access to the project and each group must [have this project shared](../user/project/members/share_project_with_groups.md). These access levels allow [more granular control over protected branch access](../user/project/protected_branches.md).
-For `allowed_to_unprotect` the access level `No access` is unavailable.
-
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/protected_branches?name=*-stable&allowed_to_push%5B%5D%5Buser_id%5D=1"
```
@@ -371,7 +369,7 @@ Example response:
"name": "master",
"push_access_levels": [
{
- "id": 1,
+ "id": 1,
"access_level": 30,
"access_level_description": "Developers + Maintainers",
"user_id": null,
@@ -380,14 +378,14 @@ Example response:
],
"merge_access_levels": [
{
- "id": 1,
+ "id": 1,
"access_level": 30,
"access_level_description": "Developers + Maintainers",
"user_id": null,
"group_id": null
},
{
- "id": 2,
+ "id": 2,
"access_level": 40,
"access_level_description": "Maintainers",
"user_id": null,
@@ -396,7 +394,7 @@ Example response:
],
"unprotect_access_levels": [
{
- "id": 1,
+ "id": 1,
"access_level": 40,
"access_level_description": "Maintainers",
"user_id": null,
@@ -439,22 +437,20 @@ PATCH /projects/:id/protected_branches/:name
curl --request PATCH --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/protected_branches/feature-branch?allow_force_push=true&code_owner_approval_required=true"
```
-| Attribute | Type | Required | Description |
+| Attribute | Type | Required | Description |
| -------------------------------------------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user |
-| `name` | string | yes | The name of the branch |
-| `allow_force_push` | boolean | no | When enabled, members who can push to this branch can also force push. |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user.
+| `name` | string | yes | The name of the branch.
+| `allow_force_push` | boolean | no | When enabled, members who can push to this branch can also force push.
+| `allowed_to_push` **(PREMIUM)** | array | no | Array of push access levels, with each described by a hash.
+| `allowed_to_merge` **(PREMIUM)** | array | no | Array of merge access levels, with each described by a hash.
+| `allowed_to_unprotect` **(PREMIUM)** | array | no | Array of unprotect access levels, with each described by a hash. The access level `No access` is not available for this field.
| `code_owner_approval_required` **(PREMIUM)** | boolean | no | Prevent pushes to this branch if it matches an item in the [`CODEOWNERS` file](../user/project/code_owners.md). Defaults to `false`. |
-| `allowed_to_push` **(PREMIUM)** | array | no | Array of push access levels, with each described by a hash. |
-| `allowed_to_merge` **(PREMIUM)** | array | no | Array of merge access levels, with each described by a hash. |
-| `allowed_to_unprotect` **(PREMIUM)** | array | no | Array of unprotect access levels, with each described by a hash. |
Elements in the `allowed_to_push`, `allowed_to_merge` and `allowed_to_unprotect` arrays should be one of `user_id`, `group_id` or
`access_level`, and take the form `{user_id: integer}`, `{group_id: integer}` or
`{access_level: integer}`.
-For `allowed_to_unprotect` the access level `No access` is unavailable.
-
To update:
- `user_id`: Ensure the updated user has access to the project. You must also pass the
diff --git a/doc/ci/test_cases/index.md b/doc/ci/test_cases/index.md
index 4088e5e82c6..84f0d8074a1 100644
--- a/doc/ci/test_cases/index.md
+++ b/doc/ci/test_cases/index.md
@@ -1,6 +1,6 @@
---
stage: Plan
-group: Certify
+group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Test cases in GitLab can help your teams create testing scenarios in their existing development platform.
type: reference
diff --git a/doc/development/fips_compliance.md b/doc/development/fips_compliance.md
index 147ff5fa6e9..1f9abef1f44 100644
--- a/doc/development/fips_compliance.md
+++ b/doc/development/fips_compliance.md
@@ -69,7 +69,6 @@ listed here that also do not work properly in FIPS mode:
when operating in FIPS-compliant mode.
- Advanced Search is currently not included in FIPS mode. It must not be enabled to be FIPS-compliant.
- [Gravatar or Libravatar-based profile images](../administration/libravatar.md) are not FIPS-compliant.
-- [Personal Access Tokens](../user/profile/personal_access_tokens.md) are not available for use or creation.
Additionally, these package repositories are disabled in FIPS mode:
diff --git a/doc/install/installation.md b/doc/install/installation.md
index be8667d5715..e2a8cd1ad23 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -11,8 +11,7 @@ using the source files. To set up a **development installation** or for many
other installation options, see the [main installation page](index.md).
It was created for and tested on **Debian/Ubuntu** operating systems.
Read [requirements.md](requirements.md) for hardware and operating system requirements.
-If you want to install on RHEL/CentOS, we recommend using the
-[Omnibus packages](https://about.gitlab.com/install/).
+If you want to install on RHEL/CentOS, you should use the [Omnibus packages](https://about.gitlab.com/install/).
This guide is long because it covers many cases and includes all commands you
need, this is [one of the few installation scripts that actually work out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880).
diff --git a/doc/user/crm/index.md b/doc/user/crm/index.md
index ebacda506b4..54e87118361 100644
--- a/doc/user/crm/index.md
+++ b/doc/user/crm/index.md
@@ -1,6 +1,6 @@
---
-stage: Plan
-group: Certify
+stage: Monitor
+group: Respond
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
diff --git a/doc/user/group/settings/group_access_tokens.md b/doc/user/group/settings/group_access_tokens.md
index cd50c209b0d..dc499b96f3c 100644
--- a/doc/user/group/settings/group_access_tokens.md
+++ b/doc/user/group/settings/group_access_tokens.md
@@ -48,9 +48,6 @@ You cannot use group access tokens to create other group, project, or personal a
Group access tokens inherit the [default prefix setting](../../admin_area/settings/account_and_limit_settings.md#personal-access-token-prefix)
configured for personal access tokens.
-NOTE:
-Group access tokens are not FIPS compliant and creation and use are disabled when [FIPS mode](../../../development/fips_compliance.md) is enabled.
-
## Create a group access token using UI
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214045) in GitLab 14.7.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index aa292ca18a3..ca2fdeb98ae 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -511,7 +511,7 @@ To remove a custom role from a group member, use the [Group and Project Members
and pass an empty `member_role_id` value.
```shell
-curl --request PUT --header "Content-Type: application/json" --header "Authorization: Bearer $YOUR_ACCESS_TOKEN" --data '{"member_role_id": "", "access_level": 10}' "https://example.gitlab.com/api/v4/groups/$GROUP_PATH/members/$GUEST_USER_ID"
+curl --request DELETE --header "Content-Type: application/json" --header "Authorization: Bearer $YOUR_ACCESS_TOKEN" --data '{"member_role_id": "", "access_level": 10}' "https://example.gitlab.com/api/v4/groups/$GROUP_PATH/members/$GUEST_USER_ID"
```
Now the user is a regular Guest.
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 3826c602fb4..a2aad8a3e27 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -45,9 +45,6 @@ For examples of how you can use a personal access token to authenticate with the
Alternately, GitLab administrators can use the API to create [impersonation tokens](../../api/rest/index.md#impersonation-tokens).
Use impersonation tokens to automate authentication as a specific user.
-NOTE:
-Personal access tokens are not FIPS compliant and creation and use are disabled when [FIPS mode](../../development/fips_compliance.md) is enabled.
-
## Create a personal access token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days is populated in the UI.
diff --git a/doc/user/project/requirements/index.md b/doc/user/project/requirements/index.md
index 4b5a9dcecec..d385daa4fa1 100644
--- a/doc/user/project/requirements/index.md
+++ b/doc/user/project/requirements/index.md
@@ -1,7 +1,7 @@
---
type: reference, howto
stage: Plan
-group: Certify
+group: Product Planning
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
diff --git a/doc/user/project/service_desk.md b/doc/user/project/service_desk.md
index 22297149561..ff9d7769681 100644
--- a/doc/user/project/service_desk.md
+++ b/doc/user/project/service_desk.md
@@ -207,15 +207,12 @@ you can customize the mailbox used by Service Desk. This allows you to have
a separate email address for Service Desk by also configuring a [custom suffix](#configuring-a-custom-email-address-suffix)
in project settings.
-The `address` must include the `+%{key}` placeholder in the 'user'
-portion of the address, before the `@`. The placeholder is used to identify the project
-where the issue should be created.
+Prerequisites:
-NOTE:
-When configuring a custom mailbox, the `service_desk_email` and `incoming_email`
-configurations must always use separate mailboxes. It's important, because
-emails picked from `service_desk_email` mailbox are processed by a different
-worker and it would not recognize `incoming_email` emails.
+- The `address` must include the `+%{key}` placeholder in the `user` portion of the address,
+ before the `@`. The placeholder is used to identify the project where the issue should be created.
+- The `service_desk_email` and `incoming_email` configurations must always use separate mailboxes
+ to make sure Service Desk emails are processed correctly.
To configure a custom mailbox for Service Desk with IMAP, add the following snippets to your configuration file in full:
diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md
index f9218a228ca..19db5032ea9 100644
--- a/doc/user/project/settings/project_access_tokens.md
+++ b/doc/user/project/settings/project_access_tokens.md
@@ -48,9 +48,6 @@ You cannot use project access tokens to create other group, project, or personal
Project access tokens inherit the [default prefix setting](../../admin_area/settings/account_and_limit_settings.md#personal-access-token-prefix)
configured for personal access tokens.
-NOTE:
-Project access tokens are not FIPS compliant and creation and use are disabled when [FIPS mode](../../../development/fips_compliance.md) is enabled.
-
## Create a project access token
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89114) in GitLab 15.1, Owners can select Owner role for project access tokens.
diff --git a/lib/banzai/filter/inline_observability_filter.rb b/lib/banzai/filter/inline_observability_filter.rb
index 334c04f2b59..fdba12b4a0a 100644
--- a/lib/banzai/filter/inline_observability_filter.rb
+++ b/lib/banzai/filter/inline_observability_filter.rb
@@ -4,7 +4,7 @@ module Banzai
module Filter
class InlineObservabilityFilter < ::Banzai::Filter::InlineEmbedsFilter
def call
- return doc unless can_view_observability?
+ return doc unless Gitlab::Observability.enabled?(group)
super
end
@@ -34,10 +34,6 @@ module Banzai
private
- def can_view_observability?
- Feature.enabled?(:observability_group_tab, group)
- end
-
def group
context[:group] || context[:project]&.group
end
diff --git a/lib/banzai/filter/inline_observability_redactor_filter.rb b/lib/banzai/filter/inline_observability_redactor_filter.rb
deleted file mode 100644
index 8cd1084e904..00000000000
--- a/lib/banzai/filter/inline_observability_redactor_filter.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-module Banzai
- module Filter
- class InlineObservabilityRedactorFilter < HTML::Pipeline::Filter
- include Gitlab::Utils::StrongMemoize
-
- CSS_SELECTOR = '.js-render-observability'
-
- XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS_SELECTOR).freeze
- EMBED_LIMIT = 100
-
- def call
- return doc if Gitlab::Utils.to_boolean(ENV.fetch('STANDALONE_OBSERVABILITY_UI', false))
-
- nodes.each do |node|
- group_id = group_ids_by_nodes[node]
- user_has_access = group_id && user_access_by_group_id[group_id]
- node.remove unless user_has_access
- end
-
- doc
- end
-
- private
-
- def user
- context[:current_user]
- end
-
- # Returns all observability embed placeholder nodes
- #
- # Removes any nodes beyond the first 100
- #
- # @return [Nokogiri::XML::NodeSet]
- def nodes
- nodes = doc.xpath(XPATH)
- nodes.drop(EMBED_LIMIT).each(&:remove)
- nodes
- end
- strong_memoize_attr :nodes
-
- # Returns a mapping representing whether the current user has permission to access observability
- # for group-ids linked in by the embed nodes
- #
- # @return [Hash<String, Boolean>]
- def user_access_by_group_id
- user_groups_from_nodes.each_with_object({}) do |group, user_access|
- user_access[group.id] = Gitlab::Observability.allowed?(user, group, :read_observability)
- end
- end
- strong_memoize_attr :user_access_by_group_id
-
- # Maps a node to the group_id linked by the node
- #
- # @return [Hash<Nokogiri::XML::Node, string>]
- def group_ids_by_nodes
- nodes.each_with_object({}) do |node, group_ids|
- url = node.attribute('data-frame-url').to_s
- next unless url
-
- group_id = Gitlab::Observability.group_id_from_url(url)
- group_ids[node] = group_id if group_id
- end
- end
- strong_memoize_attr :group_ids_by_nodes
-
- # Returns the list of groups linked in the embed nodes and readable by the user
- #
- # @return [ActiveRecord_Relation]
- def user_groups_from_nodes
- GroupsFinder.new(user, filter_group_ids: group_ids_by_nodes.values.uniq).execute
- end
- strong_memoize_attr :user_groups_from_nodes
- end
- end
-end
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
index b9cf5b4fbdd..f8035698b9b 100644
--- a/lib/banzai/pipeline/post_process_pipeline.rb
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -16,7 +16,6 @@ module Banzai
[
Filter::ReferenceRedactorFilter,
Filter::InlineMetricsRedactorFilter,
- Filter::InlineObservabilityRedactorFilter,
# UploadLinkFilter must come before RepositoryLinkFilter to
# prevent unnecessary Gitaly calls from being made.
Filter::UploadLinkFilter,
diff --git a/lib/gitlab/no_cache_headers.rb b/lib/gitlab/no_cache_headers.rb
index 2d03741480d..6afb108dcfd 100644
--- a/lib/gitlab/no_cache_headers.rb
+++ b/lib/gitlab/no_cache_headers.rb
@@ -4,7 +4,6 @@ module Gitlab
module NoCacheHeaders
DEFAULT_GITLAB_NO_CACHE_HEADERS = {
'Cache-Control' => "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store, no-cache",
- 'Pragma' => 'no-cache', # HTTP 1.0 compatibility
'Expires' => 'Fri, 01 Jan 1990 00:00:00 GMT'
}.freeze
diff --git a/lib/gitlab/observability.rb b/lib/gitlab/observability.rb
index 853eecc946f..47220e33189 100644
--- a/lib/gitlab/observability.rb
+++ b/lib/gitlab/observability.rb
@@ -19,6 +19,12 @@ module Gitlab
'https://observe.gitlab.com'
end
+ def enabled?(group = nil)
+ return Feature.enabled?(:observability_group_tab, group) if group
+
+ Feature.enabled?(:observability_group_tab)
+ end
+
def valid_observability_url?(url)
uri = URI.parse(url)
observability_uri = URI.parse(Gitlab::Observability.observability_url)
@@ -31,13 +37,6 @@ module Gitlab
false
end
- def group_id_from_url(url)
- return unless valid_observability_url?(url)
-
- group_id = URI.parse(url).path.split('/')[1]
- group_id.to_i unless group_id.to_i <= 0
- end
-
def allowed_for_action?(user, group, action)
return false if action.nil?
diff --git a/lib/tasks/gitlab/tw/codeowners.rake b/lib/tasks/gitlab/tw/codeowners.rake
index 0c6a78ccc77..8861e8574b3 100644
--- a/lib/tasks/gitlab/tw/codeowners.rake
+++ b/lib/tasks/gitlab/tw/codeowners.rake
@@ -58,7 +58,7 @@ namespace :tw do
CodeOwnerRule.new('Pipeline Security', '@marcel.amirault'),
CodeOwnerRule.new('Portfolio Management', '@msedlakjakubowski'),
CodeOwnerRule.new('Product Analytics', '@lciutacu'),
- CodeOwnerRule.new('Product Intelligence', '@dianalogan'),
+ CodeOwnerRule.new('Product Intelligence', '@lciutacu'),
CodeOwnerRule.new('Product Planning', '@msedlakjakubowski'),
CodeOwnerRule.new('Project Management', '@msedlakjakubowski'),
CodeOwnerRule.new('Provision', '@fneill'),
diff --git a/qa/qa/specs/features/api/3_create/repository/files_spec.rb b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
index 71bd03fab17..7e329371745 100644
--- a/qa/qa/specs/features/api/3_create/repository/files_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
@@ -99,7 +99,6 @@ module QA
#
expect(response.headers[:cache_control]).to include("no-store")
expect(response.headers[:cache_control]).to include("no-cache")
- expect(response.headers[:pragma]).to eq("no-cache")
expect(response.headers[:expires]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
expect(response.headers[:content_disposition]).to include("attachment")
expect(response.headers[:content_disposition]).not_to include("inline")
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index f1adb9020fa..35e374d3b7f 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'spec_helper'
-RSpec.describe ApplicationController do
+RSpec.describe ApplicationController, feature_category: :shared do
include TermsHelper
let(:user) { create(:user) }
@@ -736,23 +736,11 @@ RSpec.describe ApplicationController do
end
end
- context 'user not logged in' do
- it 'sets the default headers' do
- get :index
-
- expect(response.headers['Cache-Control']).to be_nil
- expect(response.headers['Pragma']).to be_nil
- end
- end
-
- context 'user logged in' do
- it 'sets the default headers' do
- sign_in(user)
-
- get :index
+ it 'sets the default headers' do
+ get :index
- expect(response.headers['Pragma']).to eq 'no-cache'
- end
+ expect(response.headers['Cache-Control']).to be_nil
+ expect(response.headers['Pragma']).to be_nil
end
end
@@ -779,7 +767,6 @@ RSpec.describe ApplicationController do
subject
expect(response.headers['Cache-Control']).to eq 'private, no-store'
- expect(response.headers['Pragma']).to eq 'no-cache'
expect(response.headers['Expires']).to eq 'Fri, 01 Jan 1990 00:00:00 GMT'
end
diff --git a/spec/features/markdown/observability_spec.rb b/spec/features/markdown/observability_spec.rb
index 0b380c74777..e57bfafe05e 100644
--- a/spec/features/markdown/observability_spec.rb
+++ b/spec/features/markdown/observability_spec.rb
@@ -45,7 +45,11 @@ RSpec.describe 'Observability rendering', :js, feature_category: :metrics do
end
end
- shared_examples 'does not embed observability in issues and MRs' do
+ context 'when feature flag is disabled' do
+ before do
+ stub_feature_flags(observability_group_tab: false)
+ end
+
context 'when embedding in an issue' do
let(:issue) do
create(:issue, project: project, description: observable_url)
@@ -72,18 +76,4 @@ RSpec.describe 'Observability rendering', :js, feature_category: :metrics do
it_behaves_like 'does not embed observability'
end
end
-
- context 'when user is not a developer of the embeded group' do
- it_behaves_like 'does not embed observability in issues and MRs' do
- let_it_be(:observable_url) { "https://observe.gitlab.com/1234/some-dashboard" }
- end
- end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(observability_group_tab: false)
- end
-
- it_behaves_like 'does not embed observability in issues and MRs'
- end
end
diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
index 773df01ada7..2a07957efee 100644
--- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js
@@ -17,6 +17,7 @@ import {
OPTIONS_NONE_ANY,
} from '~/vue_shared/components/filtered_search_bar/constants';
import EmojiToken from '~/vue_shared/components/filtered_search_bar/tokens/emoji_token.vue';
+import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
import { mockReactionEmojiToken, mockEmojis } from '../mock_data';
@@ -60,58 +61,72 @@ describe('EmojiToken', () => {
let mock;
let wrapper;
+ const findBaseToken = () => wrapper.findComponent(BaseToken);
+ const triggerFetchEmojis = (searchTerm = null) => {
+ findBaseToken().vm.$emit('fetch-suggestions', searchTerm);
+ return waitForPromises();
+ };
+
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
- wrapper.destroy();
});
describe('methods', () => {
- beforeEach(() => {
- wrapper = createComponent();
- });
-
describe('fetchEmojis', () => {
- it('calls `config.fetchEmojis` with provided searchTerm param', () => {
- jest.spyOn(wrapper.vm.config, 'fetchEmojis');
-
- wrapper.vm.fetchEmojis('foo');
+ it('sets loading state', async () => {
+ wrapper = createComponent({
+ config: {
+ fetchEmojis: jest.fn().mockResolvedValue(new Promise(() => {})),
+ },
+ });
+ await nextTick();
- expect(wrapper.vm.config.fetchEmojis).toHaveBeenCalledWith('foo');
+ expect(findBaseToken().props('suggestionsLoading')).toBe(true);
});
- it('sets response to `emojis` when request is successful', () => {
- jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockResolvedValue(mockEmojis);
+ describe('when request is successful', () => {
+ const searchTerm = 'foo';
- wrapper.vm.fetchEmojis('foo');
+ beforeEach(async () => {
+ wrapper = createComponent({
+ config: {
+ fetchEmojis: jest.fn().mockResolvedValue({ data: mockEmojis }),
+ },
+ });
+ return triggerFetchEmojis(searchTerm);
+ });
- return waitForPromises().then(() => {
- expect(wrapper.vm.emojis).toEqual(mockEmojis);
+ it('calls `config.fetchEmojis` with provided searchTerm param', () => {
+ expect(findBaseToken().props('config').fetchEmojis).toHaveBeenCalledWith(searchTerm);
});
- });
- it('calls `createAlert` with flash error message when request fails', () => {
- jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockRejectedValue({});
+ it('sets response to `emojis`', () => {
+ expect(findBaseToken().props('suggestions')).toEqual(mockEmojis);
+ });
+ });
- wrapper.vm.fetchEmojis('foo');
+ describe('when request fails', () => {
+ beforeEach(() => {
+ wrapper = createComponent({
+ config: {
+ fetchEmojis: jest.fn().mockRejectedValue({}),
+ },
+ });
+ return triggerFetchEmojis();
+ });
- return waitForPromises().then(() => {
+ it('calls `createAlert` with flash error message', () => {
expect(createAlert).toHaveBeenCalledWith({
message: 'There was a problem fetching emojis.',
});
});
- });
-
- it('sets `loading` to false when request completes', () => {
- jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockRejectedValue({});
-
- wrapper.vm.fetchEmojis('foo');
- return waitForPromises().then(() => {
- expect(wrapper.vm.loading).toBe(false);
+ it('sets `loading` to false when request completes', () => {
+ expect(findBaseToken().props('suggestionsLoading')).toBe(false);
});
});
});
@@ -123,15 +138,10 @@ describe('EmojiToken', () => {
beforeEach(async () => {
wrapper = createComponent({
value: { data: `"${mockEmojis[0].name}"` },
+ config: {
+ initialEmojis: mockEmojis,
+ },
});
-
- // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
- // eslint-disable-next-line no-restricted-syntax
- wrapper.setData({
- emojis: mockEmojis,
- });
-
- await nextTick();
});
it('renders gl-filtered-search-token component', () => {
diff --git a/spec/lib/banzai/filter/inline_observability_filter_spec.rb b/spec/lib/banzai/filter/inline_observability_filter_spec.rb
deleted file mode 100644
index fb1ba46e76c..00000000000
--- a/spec/lib/banzai/filter/inline_observability_filter_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Banzai::Filter::InlineObservabilityFilter do
- include FilterSpecHelper
-
- let(:input) { %(<a href="#{url}">example</a>) }
- let(:doc) { filter(input) }
- let(:group) { create(:group) }
- let(:user) { create(:user) }
-
- describe '#filter?' do
- context 'when the document has an external link' do
- let(:url) { 'https://foo.com' }
-
- it 'leaves regular non-observability links unchanged' do
- expect(doc.to_s).to eq(input)
- end
- end
-
- context 'when the document contains an embeddable observability link' do
- let(:url) { 'https://observe.gitlab.com/12345' }
-
- it 'leaves the original link unchanged' do
- expect(doc.at_css('a').to_s).to eq(input)
- end
-
- it 'appends an observability charts placeholder' do
- node = doc.at_css('.js-render-observability')
-
- expect(node).to be_present
- expect(node.attribute('data-frame-url').to_s).to eq(url)
- end
- end
-
- context 'when feature flag is disabled' do
- let(:url) { 'https://observe.gitlab.com/12345' }
-
- before do
- stub_feature_flags(observability_group_tab: false)
- end
-
- it 'leaves the original link unchanged' do
- expect(doc.at_css('a').to_s).to eq(input)
- end
-
- it 'does not append an observability charts placeholder' do
- node = doc.at_css('.js-render-observability')
-
- expect(node).not_to be_present
- end
- end
- end
-end
diff --git a/spec/lib/banzai/filter/inline_observability_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_observability_redactor_filter_spec.rb
deleted file mode 100644
index ceebe42d1b7..00000000000
--- a/spec/lib/banzai/filter/inline_observability_redactor_filter_spec.rb
+++ /dev/null
@@ -1,85 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Banzai::Filter::InlineObservabilityRedactorFilter, feature_category: :metrics do
- include FilterSpecHelper
-
- let_it_be(:group) { create(:group) }
-
- let(:url) { "#{Gitlab::Observability.observability_url}/#{group.id}/explore" }
- let(:input) { %(<a href="#{url}">example</a>) }
- let(:doc) { filter(input) }
-
- context 'without an observability placeholder' do
- it 'leaves regular links unchanged' do
- expect(doc.to_s).to eq input
- end
- end
-
- shared_examples 'redacts the placeholder' do
- it 'redacts the placeholder' do
- expect(doc.to_s).to be_empty
- end
- end
-
- context 'with an observability placeholder' do
- let(:input) { %(<div class="js-render-observability" data-frame-url="#{url}"></div>) }
-
- context 'when no user is logged in' do
- it_behaves_like 'redacts the placeholder'
- end
-
- context 'with invalid observability url' do
- let(:url) { "#{Gitlab::Observability.observability_url}/foo/explore" }
-
- it_behaves_like 'redacts the placeholder'
- end
-
- context 'with missing observability frame url' do
- let(:input) { %(<div class="js-render-observability"></div>) }
-
- it_behaves_like 'redacts the placeholder'
- end
-
- context 'when the user does not have permission to access the group' do
- let(:user) { create(:user) }
- let(:doc) { filter(input, current_user: user) }
-
- it_behaves_like 'redacts the placeholder'
- end
-
- context 'when the user is not a developer of the group' do
- let(:user) { create(:user) }
- let(:doc) { filter(input, current_user: user) }
-
- before do
- group.add_reporter(user)
- end
-
- it_behaves_like 'redacts the placeholder'
- end
-
- context 'when the user is a developer of the group' do
- let(:user) { create(:user) }
- let(:doc) { filter(input, current_user: user) }
-
- before do
- group.add_developer(user)
- end
-
- it 'leaves the placeholder' do
- expect(CGI.unescapeHTML(doc.to_s)).to eq(input)
- end
-
- context 'with over 100 embeds' do
- let(:embed) { %(<div class="js-render-observability" data-frame-url="#{url}"></div>) }
- let(:input) { embed * 150 }
-
- it 'redacts ill-advised embeds' do
- expect(doc.to_s.length).to eq(embed.length * 100)
- end
- end
- end
- end
-end
diff --git a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
index c8d3844af30..072d77f4112 100644
--- a/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/post_process_pipeline_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe Banzai::Pipeline::PostProcessPipeline, feature_category: :team_pl
end
let(:doc) { HTML::Pipeline.parse(html) }
- let(:non_related_xpath_calls) { 3 }
+ let(:non_related_xpath_calls) { 2 }
it 'searches for attributes only once' do
expect(doc).to receive(:xpath).exactly(non_related_xpath_calls + 1).times
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 056dc1f1966..86a363c2227 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -508,6 +508,7 @@ project:
- project_namespace
- management_clusters
- boards
+- application_setting
- last_event
- integrations
- push_hooks_integrations
diff --git a/spec/lib/gitlab/observability_spec.rb b/spec/lib/gitlab/observability_spec.rb
index e8c79c29830..3be579e088c 100644
--- a/spec/lib/gitlab/observability_spec.rb
+++ b/spec/lib/gitlab/observability_spec.rb
@@ -49,30 +49,6 @@ RSpec.describe Gitlab::Observability do
end
end
- describe '.group_id_from_url' do
- it 'returns the group id extracted from the url' do
- expect(described_class.group_id_from_url('https://observe.gitlab.com/123/explore')).to eq(123)
- expect(described_class.group_id_from_url('https://observe.gitlab.com/123')).to eq(123)
- expect(described_class.group_id_from_url('https://observe.gitlab.com/123/')).to eq(123)
- expect(described_class.group_id_from_url('https://observe.gitlab.com/123/456')).to eq(123)
- end
-
- it 'returns nil if the group id is not valid or missing' do
- expect(described_class.group_id_from_url('https://observe.gitlab.com')).to be_nil
- expect(described_class.group_id_from_url('https://observe.gitlab.com/')).to be_nil
- expect(described_class.group_id_from_url('https://observe.gitlab.com/foo')).to be_nil
- expect(described_class.group_id_from_url('https://observe.gitlab.com/foo/bar')).to be_nil
- expect(described_class.group_id_from_url('https://observe.gitlab.com/0')).to be_nil
- expect(described_class.group_id_from_url('https://observe.gitlab.com/-1')).to be_nil
- end
-
- it 'returns nil if the url is not a valid' do
- expect(described_class.group_id_from_url('https://invalid.gitlab.com/123')).to be_nil
- expect(described_class.group_id_from_url('foo bar')).to be_nil
- expect(described_class.group_id_from_url('foo@@@@bar/1/')).to be_nil
- end
- end
-
describe '.allowed_for_action?' do
let_it_be(:group) { build(:user) }
let_it_be(:user) { build(:group) }
diff --git a/spec/models/alert_management/alert_assignee_spec.rb b/spec/models/alert_management/alert_assignee_spec.rb
index c50a3ec0d01..647195380b3 100644
--- a/spec/models/alert_management/alert_assignee_spec.rb
+++ b/spec/models/alert_management/alert_assignee_spec.rb
@@ -5,7 +5,11 @@ require 'spec_helper'
RSpec.describe AlertManagement::AlertAssignee do
describe 'associations' do
it { is_expected.to belong_to(:alert) }
- it { is_expected.to belong_to(:assignee) }
+
+ it do
+ is_expected.to belong_to(:assignee).class_name('User')
+ .with_foreign_key(:user_id).inverse_of(:alert_assignees)
+ end
end
describe 'validations' do
diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb
index 685ed81ec84..ff77ca2ab64 100644
--- a/spec/models/alert_management/alert_spec.rb
+++ b/spec/models/alert_management/alert_spec.rb
@@ -16,9 +16,13 @@ RSpec.describe AlertManagement::Alert do
it { is_expected.to belong_to(:prometheus_alert).optional }
it { is_expected.to belong_to(:environment).optional }
it { is_expected.to have_many(:assignees).through(:alert_assignees) }
- it { is_expected.to have_many(:notes) }
- it { is_expected.to have_many(:ordered_notes) }
- it { is_expected.to have_many(:user_mentions) }
+ it { is_expected.to have_many(:notes).inverse_of(:noteable) }
+ it { is_expected.to have_many(:ordered_notes).class_name('Note').inverse_of(:noteable) }
+
+ it do
+ is_expected.to have_many(:user_mentions).class_name('AlertManagement::AlertUserMention')
+ .with_foreign_key(:alert_management_alert_id).inverse_of(:alert)
+ end
end
describe 'validations' do
diff --git a/spec/models/alert_management/alert_user_mention_spec.rb b/spec/models/alert_management/alert_user_mention_spec.rb
index 27c3d290dde..083bf667bea 100644
--- a/spec/models/alert_management/alert_user_mention_spec.rb
+++ b/spec/models/alert_management/alert_user_mention_spec.rb
@@ -4,7 +4,11 @@ require 'spec_helper'
RSpec.describe AlertManagement::AlertUserMention do
describe 'associations' do
- it { is_expected.to belong_to(:alert_management_alert) }
+ it do
+ is_expected.to belong_to(:alert).class_name('::AlertManagement::Alert')
+ .with_foreign_key(:alert_management_alert_id).inverse_of(:user_mentions)
+ end
+
it { is_expected.to belong_to(:note) }
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 8de9932a519..de5753bd4e2 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -25,6 +25,20 @@ RSpec.describe ApplicationSetting, feature_category: :not_owned, type: :model do
it { expect(setting.kroki_formats).to eq({}) }
end
+ describe 'associations' do
+ it do
+ is_expected.to belong_to(:self_monitoring_project).class_name('Project')
+ .with_foreign_key(:instance_administration_project_id)
+ .inverse_of(:application_setting)
+ end
+
+ it do
+ is_expected.to belong_to(:instance_group).class_name('Group')
+ .with_foreign_key(:instance_administrators_group_id)
+ .inverse_of(:application_setting)
+ end
+ end
+
describe 'validations' do
let(:http) { 'http://example.com' }
let(:https) { 'https://example.com' }
diff --git a/spec/models/audit_event_spec.rb b/spec/models/audit_event_spec.rb
index 9f2724cebee..9e667836b45 100644
--- a/spec/models/audit_event_spec.rb
+++ b/spec/models/audit_event_spec.rb
@@ -3,6 +3,10 @@
require 'spec_helper'
RSpec.describe AuditEvent do
+ describe 'associations' do
+ it { is_expected.to belong_to(:user).with_foreign_key(:author_id).inverse_of(:audit_events) }
+ end
+
describe 'validations' do
include_examples 'validates IP address' do
let(:attribute) { :ip_address }
diff --git a/spec/models/board_spec.rb b/spec/models/board_spec.rb
index 6017298e85b..f469dee5ba1 100644
--- a/spec/models/board_spec.rb
+++ b/spec/models/board_spec.rb
@@ -8,7 +8,13 @@ RSpec.describe Board do
describe 'relationships' do
it { is_expected.to belong_to(:project) }
- it { is_expected.to have_many(:lists).order(list_type: :asc, position: :asc).dependent(:delete_all) }
+
+ it do
+ is_expected.to have_many(:lists).order(list_type: :asc, position: :asc).dependent(:delete_all)
+ .inverse_of(:board)
+ end
+
+ it { is_expected.to have_many(:destroyable_lists).order(list_type: :asc, position: :asc).inverse_of(:board) }
end
describe 'validations' do
diff --git a/spec/models/bulk_imports/entity_spec.rb b/spec/models/bulk_imports/entity_spec.rb
index 2cb72e74979..db123c00cf6 100644
--- a/spec/models/bulk_imports/entity_spec.rb
+++ b/spec/models/bulk_imports/entity_spec.rb
@@ -6,8 +6,13 @@ RSpec.describe BulkImports::Entity, type: :model, feature_category: :importers d
describe 'associations' do
it { is_expected.to belong_to(:bulk_import).required }
it { is_expected.to belong_to(:parent) }
- it { is_expected.to belong_to(:group) }
+ it { is_expected.to belong_to(:group).optional.with_foreign_key(:namespace_id).inverse_of(:bulk_import_entities) }
it { is_expected.to belong_to(:project) }
+
+ it do
+ is_expected.to have_many(:trackers).class_name('BulkImports::Tracker')
+ .with_foreign_key(:bulk_import_entity_id).inverse_of(:entity)
+ end
end
describe 'validations' do
diff --git a/spec/models/bulk_imports/tracker_spec.rb b/spec/models/bulk_imports/tracker_spec.rb
index 1516ab106cb..a618a12df6b 100644
--- a/spec/models/bulk_imports/tracker_spec.rb
+++ b/spec/models/bulk_imports/tracker_spec.rb
@@ -4,7 +4,10 @@ require 'spec_helper'
RSpec.describe BulkImports::Tracker, type: :model do
describe 'associations' do
- it { is_expected.to belong_to(:entity).required }
+ it do
+ is_expected.to belong_to(:entity).required.class_name('BulkImports::Entity')
+ .with_foreign_key(:bulk_import_entity_id).inverse_of(:trackers)
+ end
end
describe 'validations' do
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 0a05c558d45..882a1f73a03 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -40,7 +40,19 @@ RSpec.describe Group, feature_category: :subgroups do
it { is_expected.to have_many(:debian_distributions).class_name('Packages::Debian::GroupDistribution').dependent(:destroy) }
it { is_expected.to have_many(:daily_build_group_report_results).class_name('Ci::DailyBuildGroupReportResult') }
it { is_expected.to have_many(:group_callouts).class_name('Users::GroupCallout').with_foreign_key(:group_id) }
+
+ it do
+ is_expected.to have_many(:application_setting)
+ .with_foreign_key(:instance_administrators_group_id).inverse_of(:instance_group)
+ end
+
it { is_expected.to have_many(:bulk_import_exports).class_name('BulkImports::Export') }
+
+ it do
+ is_expected.to have_many(:bulk_import_entities).class_name('BulkImports::Entity')
+ .with_foreign_key(:namespace_id).inverse_of(:group)
+ end
+
it { is_expected.to have_many(:contacts).class_name('CustomerRelations::Contact') }
it { is_expected.to have_many(:organizations).class_name('CustomerRelations::Organization') }
it { is_expected.to have_many(:protected_branches).inverse_of(:group).with_foreign_key(:namespace_id) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 360aee8b531..3040fea4178 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -174,6 +174,11 @@ RSpec.describe User, feature_category: :user_profile do
it { is_expected.to have_many(:revoked_user_achievements).class_name('Achievements::UserAchievement').with_foreign_key('revoked_by_user_id').inverse_of(:revoked_by_user) }
it { is_expected.to have_many(:achievements).through(:user_achievements).class_name('Achievements::Achievement').inverse_of(:users) }
it { is_expected.to have_many(:namespace_commit_emails).class_name('Users::NamespaceCommitEmail') }
+ it { is_expected.to have_many(:audit_events).with_foreign_key(:author_id).inverse_of(:user) }
+
+ it do
+ is_expected.to have_many(:alert_assignees).class_name('::AlertManagement::AlertAssignee').inverse_of(:assignee)
+ end
describe 'default values' do
let(:user) { described_class.new }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index f4066c54c47..1ad5233f420 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -829,7 +829,6 @@ RSpec.describe API::Files, feature_category: :source_code_management do
expect_to_send_git_blob(api(url, current_user), params)
expect(response.headers['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store, no-cache')
- expect(response.headers['Pragma']).to eq('no-cache')
expect(response.headers['Expires']).to eq('Fri, 01 Jan 1990 00:00:00 GMT')
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 555ba2bc978..b146dda5030 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -236,7 +236,6 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do
get api(route, current_user)
expect(response.headers["Cache-Control"]).to eq("max-age=0, private, must-revalidate, no-store, no-cache")
- expect(response.headers["Pragma"]).to eq("no-cache")
expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
end
diff --git a/spec/services/packages/mark_package_for_destruction_service_spec.rb b/spec/services/packages/mark_package_for_destruction_service_spec.rb
index 125ec53ad61..80c9ea89414 100644
--- a/spec/services/packages/mark_package_for_destruction_service_spec.rb
+++ b/spec/services/packages/mark_package_for_destruction_service_spec.rb
@@ -36,6 +36,12 @@ RSpec.describe Packages::MarkPackageForDestructionService do
end
it 'returns an error ServiceResponse' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ instance_of(StandardError),
+ project_id: package.project_id,
+ package_id: package.id
+ )
+
response = service.execute
expect(package).not_to receive(:sync_maven_metadata)
diff --git a/spec/services/packages/mark_packages_for_destruction_service_spec.rb b/spec/services/packages/mark_packages_for_destruction_service_spec.rb
index 5c043b89de8..f2c1168747a 100644
--- a/spec/services/packages/mark_packages_for_destruction_service_spec.rb
+++ b/spec/services/packages/mark_packages_for_destruction_service_spec.rb
@@ -76,6 +76,11 @@ RSpec.describe Packages::MarkPackagesForDestructionService, :sidekiq_inline do
it 'returns an error ServiceResponse' do
expect(::Packages::Maven::Metadata::SyncService).not_to receive(:new)
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).with(
+ instance_of(StandardError),
+ package_ids: package_ids
+ )
+
expect { subject }.to not_change { ::Packages::Package.pending_destruction.count }
.and not_change { ::Packages::PackageFile.pending_destruction.count }