Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
552e85a586
commit
fd9a56d56f
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
53f1003eeb8f961b37d90c73a71f75683077b9bcd0e495395033998530a363bd
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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": []
|
||||
}
|
||||
```
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
it('sets loading state', async () => {
|
||||
wrapper = createComponent({
|
||||
config: {
|
||||
fetchEmojis: jest.fn().mockResolvedValue(new Promise(() => {})),
|
||||
},
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
wrapper.vm.fetchEmojis('foo');
|
||||
|
||||
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('sets response to `emojis`', () => {
|
||||
expect(findBaseToken().props('suggestions')).toEqual(mockEmojis);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls `createAlert` with flash error message when request fails', () => {
|
||||
jest.spyOn(wrapper.vm.config, 'fetchEmojis').mockRejectedValue({});
|
||||
describe('when request fails', () => {
|
||||
beforeEach(() => {
|
||||
wrapper = createComponent({
|
||||
config: {
|
||||
fetchEmojis: jest.fn().mockRejectedValue({}),
|
||||
},
|
||||
});
|
||||
return triggerFetchEmojis();
|
||||
});
|
||||
|
||||
wrapper.vm.fetchEmojis('foo');
|
||||
|
||||
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', () => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -508,6 +508,7 @@ project:
|
|||
- project_namespace
|
||||
- management_clusters
|
||||
- boards
|
||||
- application_setting
|
||||
- last_event
|
||||
- integrations
|
||||
- push_hooks_integrations
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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' }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue