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:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-28 21:10:48 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-28 21:10:48 +0300
commit0076bbc67375ff1507e42ce479406daf92c0a6a2 (patch)
treeaa0a1c6f575ac050504c397c7edf8f9789d46046
parent8966e39395e22465ac3ff58407868b872a3ecffe (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/finders/issuable_finder.rb5
-rw-r--r--app/models/ci/build_metadata.rb7
-rw-r--r--app/models/concerns/ci/partitionable.rb9
-rw-r--r--app/models/concerns/ci/partitionable/switch.rb55
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--app/models/concerns/pg_full_text_searchable.rb47
-rw-r--r--app/models/issue.rb6
-rw-r--r--app/services/environments/schedule_to_delete_review_apps_service.rb2
-rw-r--r--app/workers/namespaces/root_statistics_worker.rb1
-rw-r--r--config/feature_flags/development/ci_partitioning_use_ci_builds_metadata_routing_table.yml8
-rw-r--r--config/locales/doorkeeper.zh-cn.yml122
-rw-r--r--db/post_migrate/20220802112102_schedule_migrate_shared_vulnerability_scanners.rb24
-rw-r--r--db/post_migrate/20220919080303_delete_migrate_shared_vulnerability_scanners.rb44
-rw-r--r--db/post_migrate/20220919080304_reschedule_migrate_shared_vulnerability_scanners.rb29
-rw-r--r--db/schema_migrations/202209190803031
-rw-r--r--db/schema_migrations/202209190803041
-rw-r--r--doc/administration/logs/index.md6
-rw-r--r--doc/architecture/blueprints/graphql_api/index.md2
-rw-r--r--doc/ci/runners/saas/macos_saas_runner.md2
-rw-r--r--doc/development/code_review.md2
-rw-r--r--doc/development/contributing/design.md2
-rw-r--r--doc/development/fe_guide/view_component.md2
-rw-r--r--doc/development/integrations/index.md4
-rw-r--r--doc/operations/incident_management/img/timeline_event_for_severity_change_v15_6.pngbin0 -> 9608 bytes
-rw-r--r--doc/operations/incident_management/incident_timeline_events.md15
-rw-r--r--doc/topics/awesome_co.md2
-rw-r--r--doc/user/group/epics/manage_epics.md2
-rw-r--r--doc/user/project/issues/design_management.md2
-rw-r--r--doc/user/project/quick_actions.md2
-rw-r--r--doc/user/project/requirements/index.md2
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/gitlab/quick_actions/issuable_actions.rb60
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb17
-rw-r--r--lib/gitlab/sidekiq_middleware/size_limiter/compressor.rb2
-rw-r--r--lib/gitlab/sidekiq_migrate_jobs.rb8
-rw-r--r--lib/gitlab/sql/pattern.rb26
-rw-r--r--locale/gitlab.pot10
-rw-r--r--package.json2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb2
-rw-r--r--rubocop/cop/gitlab/mark_used_feature_flags.rb2
-rw-r--r--scripts/review_apps/base-config.yaml58
-rw-r--r--spec/db/schema_spec.rb1
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb9
-rw-r--r--spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap2
-rw-r--r--spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb4
-rw-r--r--spec/lib/gitlab/sql/pattern_spec.rb40
-rw-r--r--spec/migrations/delete_migrate_shared_vulnerability_scanners_spec.rb69
-rw-r--r--spec/migrations/reschedule_migrate_shared_vulnerability_scanners_spec.rb41
-rw-r--r--spec/migrations/schedule_migrate_shared_vulnerability_scanners_spec.rb59
-rw-r--r--spec/models/ci/build_metadata_spec.rb22
-rw-r--r--spec/models/concerns/ci/partitionable/switch_spec.rb294
-rw-r--r--spec/models/concerns/ci/partitionable_spec.rb21
-rw-r--r--spec/models/concerns/issuable_spec.rb16
-rw-r--r--spec/models/concerns/pg_full_text_searchable_spec.rb25
-rw-r--r--spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb4
-rw-r--r--spec/support/shared_examples/finders/issues_finder_shared_examples.rb2
-rw-r--r--spec/workers/namespaces/root_statistics_worker_spec.rb8
-rw-r--r--test.html439
-rw-r--r--yarn.lock8
59 files changed, 1437 insertions, 226 deletions
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 9f331d381aa..a6f5c826243 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -335,7 +335,7 @@ class IssuableFinder
return items if items.is_a?(ActiveRecord::NullRelation)
return items if Feature.enabled?(:disable_anonymous_search, type: :ops) && current_user.nil?
- return items.pg_full_text_search(search) if use_full_text_search?
+ return items.pg_full_text_search(search, matched_columns: params[:in].to_s.split(',')) if use_full_text_search?
if use_cte_for_search?
cte = Gitlab::SQL::CTE.new(klass.table_name, items)
@@ -348,8 +348,7 @@ class IssuableFinder
# rubocop: enable CodeReuse/ActiveRecord
def use_full_text_search?
- params[:in].blank? &&
- klass.try(:pg_full_text_searchable_columns).present? &&
+ klass.try(:pg_full_text_searchable_columns).present? &&
params[:search] =~ FULL_TEXT_SEARCH_TERM_REGEX &&
Feature.enabled?(:issues_full_text_search, params.project || params.group)
end
diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb
index ae35e0c8d43..879ff0321e3 100644
--- a/app/models/ci/build_metadata.rb
+++ b/app/models/ci/build_metadata.rb
@@ -5,6 +5,7 @@ module Ci
# Data that should be persisted forever, should be stored with Ci::Build model.
class BuildMetadata < Ci::ApplicationRecord
BuildTimeout = Struct.new(:value, :source)
+ ROUTING_FEATURE_FLAG = :ci_partitioning_use_ci_builds_metadata_routing_table
include Ci::Partitionable
include Presentable
@@ -14,7 +15,11 @@ module Ci
self.table_name = 'ci_builds_metadata'
self.primary_key = 'id'
self.sequence_name = 'ci_builds_metadata_id_seq'
- partitionable scope: :build
+
+ partitionable scope: :build, through: {
+ table: :p_ci_builds_metadata,
+ flag: ROUTING_FEATURE_FLAG
+ }
belongs_to :build, class_name: 'CommitStatus'
belongs_to :project
diff --git a/app/models/concerns/ci/partitionable.rb b/app/models/concerns/ci/partitionable.rb
index df803180e77..68a6714c892 100644
--- a/app/models/concerns/ci/partitionable.rb
+++ b/app/models/concerns/ci/partitionable.rb
@@ -57,9 +57,14 @@ module Ci
end
class_methods do
- private
+ def partitionable(scope:, through: nil)
+ if through
+ define_singleton_method(:routing_table_name) { through[:table] }
+ define_singleton_method(:routing_table_name_flag) { through[:flag] }
+
+ include Partitionable::Switch
+ end
- def partitionable(scope:)
define_method(:partition_scope_value) do
strong_memoize(:partition_scope_value) do
next Ci::Pipeline.current_partition_value if respond_to?(:importing?) && importing?
diff --git a/app/models/concerns/ci/partitionable/switch.rb b/app/models/concerns/ci/partitionable/switch.rb
new file mode 100644
index 00000000000..032f34fba1f
--- /dev/null
+++ b/app/models/concerns/ci/partitionable/switch.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+module Ci
+ module Partitionable
+ module Switch
+ extend ActiveSupport::Concern
+
+ # These methods are cached at the class level and depend on the value
+ # of `table_name`, changing that value resets them.
+ # `cached_find_by_statement` is used to cache SQL statements which can
+ # include the table name.
+ #
+ SWAPABLE_METHODS = %i[table_name quoted_table_name arel_table
+ predicate_builder cached_find_by_statement].freeze
+
+ included do |base|
+ partitioned = Class.new(base) do
+ self.table_name = base.routing_table_name
+
+ def self.routing_class?
+ true
+ end
+ end
+
+ base.const_set(:Partitioned, partitioned)
+ end
+
+ class_methods do
+ def routing_class?
+ false
+ end
+
+ def routing_table_enabled?
+ return false if routing_class?
+
+ ::Feature.enabled?(routing_table_name_flag)
+ end
+
+ # We're delegating them to the `Partitioned` model.
+ # They do not require any check override since they come from AR core
+ # (are always defined) and we're using `super` to get the value.
+ #
+ SWAPABLE_METHODS.each do |name|
+ define_method(name) do |*args, &block|
+ if routing_table_enabled?
+ self::Partitioned.public_send(name, *args, &block) # rubocop: disable GitlabSecurity/PublicSend
+ else
+ super(*args, &block)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index f8389865f91..cdbc7092b63 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -217,6 +217,10 @@ module Issuable
false
end
+ def supports_confidentiality?
+ false
+ end
+
def severity
return IssuableSeverity::DEFAULT unless supports_severity?
diff --git a/app/models/concerns/pg_full_text_searchable.rb b/app/models/concerns/pg_full_text_searchable.rb
index 335fcec2611..f533791c2ee 100644
--- a/app/models/concerns/pg_full_text_searchable.rb
+++ b/app/models/concerns/pg_full_text_searchable.rb
@@ -25,6 +25,7 @@ module PgFullTextSearchable
TSVECTOR_MAX_LENGTH = 1.megabyte.freeze
TEXT_SEARCH_DICTIONARY = 'english'
URL_SCHEME_REGEX = %r{(?<=\A|\W)\w+://(?=\w+)}.freeze
+ TSQUERY_DISALLOWED_CHARACTERS_REGEX = %r{[^a-zA-Z0-9 .@/\-"]}.freeze
def update_search_data!
tsvector_sql_nodes = self.class.pg_full_text_searchable_columns.map do |column, weight|
@@ -102,21 +103,16 @@ module PgFullTextSearchable
end
end
- def pg_full_text_search(search_term)
+ def pg_full_text_search(query, matched_columns: [])
search_data_table = reflect_on_association(:search_data).klass.arel_table
- # This fixes an inconsistency with how to_tsvector and websearch_to_tsquery process URLs
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/354784#note_905431920
- search_term = remove_url_scheme(search_term)
- search_term = ActiveSupport::Inflector.transliterate(search_term)
-
joins(:search_data).where(
Arel::Nodes::InfixOperation.new(
'@@',
search_data_table[:search_vector],
Arel::Nodes::NamedFunction.new(
- 'websearch_to_tsquery',
- [Arel::Nodes.build_quoted(TEXT_SEARCH_DICTIONARY), Arel::Nodes.build_quoted(search_term)]
+ 'to_tsquery',
+ [Arel::Nodes.build_quoted(TEXT_SEARCH_DICTIONARY), build_tsquery(query, matched_columns)]
)
)
)
@@ -124,8 +120,39 @@ module PgFullTextSearchable
private
- def remove_url_scheme(search_term)
- search_term.gsub(URL_SCHEME_REGEX, '')
+ def build_tsquery(query, matched_columns)
+ # URLs get broken up into separate words when : is removed below, so we just remove the whole scheme.
+ query = remove_url_scheme(query)
+ # Remove accents from search term to match indexed data
+ query = ActiveSupport::Inflector.transliterate(query)
+ # Prevent users from using tsquery operators that can cause syntax errors.
+ query = filter_allowed_characters(query)
+
+ weights = matched_columns.map do |column_name|
+ pg_full_text_searchable_columns[column_name]
+ end.compact.join
+ prefix_search_suffix = ":*#{weights}"
+
+ tsquery = Gitlab::SQL::Pattern.split_query_to_search_terms(query).map do |search_term|
+ case search_term
+ when /\A\d+\z/ # Handles https://gitlab.com/gitlab-org/gitlab/-/issues/375337
+ "(#{search_term + prefix_search_suffix} | -#{search_term + prefix_search_suffix})"
+ when /\s/
+ search_term.split.map { |t| "#{t}:#{weights}" }.join(' <-> ')
+ else
+ search_term + prefix_search_suffix
+ end
+ end.join(' & ')
+
+ Arel::Nodes.build_quoted(tsquery)
+ end
+
+ def remove_url_scheme(query)
+ query.gsub(URL_SCHEME_REGEX, '')
+ end
+
+ def filter_allowed_characters(query)
+ query.gsub(TSQUERY_DISALLOWED_CHARACTERS_REGEX, ' ')
end
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 61fc0264360..639bb3140f8 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -272,7 +272,7 @@ class Issue < ApplicationRecord
end
override :pg_full_text_search
- def pg_full_text_search(search_term)
+ def pg_full_text_search(query, matched_columns: [])
super.where('issue_search_data.project_id = issues.project_id')
end
end
@@ -654,6 +654,10 @@ class Issue < ApplicationRecord
Gitlab::EtagCaching::Store.new.touch(key)
end
+ def supports_confidentiality?
+ true
+ end
+
private
def due_date_after_start_date
diff --git a/app/services/environments/schedule_to_delete_review_apps_service.rb b/app/services/environments/schedule_to_delete_review_apps_service.rb
index b3b86689748..041b834f11b 100644
--- a/app/services/environments/schedule_to_delete_review_apps_service.rb
+++ b/app/services/environments/schedule_to_delete_review_apps_service.rb
@@ -58,7 +58,7 @@ module Environments
else
result.set_status(
:bad_request,
- error_message: "Failed to authorize deletions for some or all of the environments. Ask someone with more permissions to delete the environments."
+ error_message: "No environments found for scheduled deletion. Either your query did not match any environments (default parameters match environments that are 30 days or older), or you have insufficient permissions to delete matching environments."
)
result.set_unprocessable_entries(failed)
diff --git a/app/workers/namespaces/root_statistics_worker.rb b/app/workers/namespaces/root_statistics_worker.rb
index e1271dae335..157a779dda6 100644
--- a/app/workers/namespaces/root_statistics_worker.rb
+++ b/app/workers/namespaces/root_statistics_worker.rb
@@ -11,6 +11,7 @@ module Namespaces
queue_namespace :update_namespace_statistics
feature_category :source_code_management
idempotent!
+ deduplicate :until_executed, if_deduplicated: :reschedule_once
def perform(namespace_id)
namespace = Namespace.find(namespace_id)
diff --git a/config/feature_flags/development/ci_partitioning_use_ci_builds_metadata_routing_table.yml b/config/feature_flags/development/ci_partitioning_use_ci_builds_metadata_routing_table.yml
new file mode 100644
index 00000000000..71c2aa735a2
--- /dev/null
+++ b/config/feature_flags/development/ci_partitioning_use_ci_builds_metadata_routing_table.yml
@@ -0,0 +1,8 @@
+---
+name: ci_partitioning_use_ci_builds_metadata_routing_table
+introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/100935"
+rollout_issue_url: "https://gitlab.com/gitlab-org/gitlab/-/issues/378601"
+milestone: '15.6'
+type: development
+group: "group::pipeline execution"
+default_enabled: false
diff --git a/config/locales/doorkeeper.zh-cn.yml b/config/locales/doorkeeper.zh-cn.yml
new file mode 100644
index 00000000000..f9b37c43866
--- /dev/null
+++ b/config/locales/doorkeeper.zh-cn.yml
@@ -0,0 +1,122 @@
+zh-CN:
+ activerecord:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: '不能包含片段。'
+ invalid_uri: '必须是一个有效的 URI。'
+ relative_uri: '必须是一个绝对 URI。'
+ mongoid:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: '不能包含片段。'
+ invalid_uri: '必须是一个有效的 URI。'
+ relative_uri: '必须是一个绝对 URI。'
+ mongo_mapper:
+ errors:
+ models:
+ application:
+ attributes:
+ redirect_uri:
+ fragment_present: '不能包含片段。'
+ invalid_uri: '必须是一个有效的 URI。'
+ relative_uri: '必须是一个绝对 URI。'
+ doorkeeper:
+ errors:
+ messages:
+ # Common error messages
+ invalid_redirect_uri: '包含的重定向 URI 无效。'
+ unauthorized_client: '客户端无权使用此方法执行此请求。'
+ access_denied: '资源所有者或授权服务器拒绝了该请求。'
+ invalid_scope: '请求的范围无效、未知或格式不正确。'
+ server_error: '授权服务器遇到了意外情况,导致无法完成请求。'
+ unconfirmed_email: '在您登录之前,验证您的帐户配置文件中的电子邮件地址。'
+ temporarily_unavailable: '由于服务器临时超载或维护,授权服务器目前无法处理请求。'
+
+ #configuration error messages
+ credential_flow_not_configured: '由于 Doorkeeper.configure.resource_owner_from_credentials 未配置,资源所有者密码凭证授予工作流失败。'
+ resource_owner_authenticator_not_configured: '由于 Doorkeeper.configure.resource_owner_authenticator 未配置,资源所有者查找失败。'
+
+ # Access grant errors
+ unsupported_response_type: '授权服务器不支持此响应类型。'
+
+ # Access token errors
+ invalid_client: '由于未知客户端、不包括客户端身份验证或不支持的身份验证方法,客户端身份验证失败。'
+ invalid_grant: '所提供的授权无效、过期、被撤销、与授权请求中使用的重定向 URI 不匹配,或者已向另一个客户端发出。'
+ unsupported_grant_type: '授权服务器不支持授权授予类型。'
+
+ # Password Access token errors
+ invalid_resource_owner: '所提供的资源所有者凭证无效,或找不到资源所有者。'
+
+ invalid_request:
+ unknown: '该请求缺少一个必需的参数,包括一个不支持的参数值,或在其他方面是错误的。'
+ missing_param: '缺少所需的参数:%{value}。'
+ not_support_pkce: '无效的 code_verifier 参数。服务器不支持 pkce。'
+ request_not_authorized: '请求需要授权。授权请求所需的参数缺失或无效。'
+
+ invalid_token:
+ revoked: "访问令牌被撤销"
+ expired: "访问令牌过期"
+ unknown: "访问令牌无效"
+ scopes:
+ api: 访问经过验证的用户的 API
+ read_user: 读取已验证用户的个人信息
+ read_repository: 允许对仓库进行只读访问
+ write_repository: 允许对仓库进行读写访问
+ read_registry: 授予读取容器镜像库镜像的权限
+ openid: 使用 OpenID Connect 进行身份验证
+ sudo: 作为系统中的任何用户执行 API 操作
+ profile: 允许使用 OpenID Connect 只读访问用户的个人信息
+ email: 允许使用 OpenID Connect 只读访问用户的主要电子邮件地址
+ scope_desc:
+ api:
+ 授予对 API 的完全读/写访问权,包括所有群组和项目、容器镜像库和软件包库。
+ read_api:
+ 授予对 API 的读访问权,包括所有群组和项目、容器镜像库和软件包库。
+ read_user:
+ 通过 /user API端点授予对通过身份验证的用户概要的只读访问权,该端点包括用户名、公共电子邮件和全名。还授予对 /users 下的只读 API 端点的访问权。
+ read_repository:
+ 使用 Git-over-HTTP 或 Repository Files API 授予对私有项目仓库的只读访问权。
+ write_repository:
+ 使用 Git-over-HTTP (不使用 API)授予对私有项目上的仓库的读写访问权。
+ read_registry:
+ 授予对私有项目上的容器镜像库镜像的只读访问权。
+ write_registry:
+ 授予对私有项目上的容器镜像库镜像的写访问权。
+ openid:
+ 授予使用 OpenID Connect 与 GitLab 进行身份验证的权限。还提供对用户配置文件和组成员关系的只读访问权限。
+ sudo:
+ 当以管理员用户身份进行身份验证时,授予作为系统中任何用户执行 API 操作的权限。
+ profile:
+ 使用 OpenID Connect 授予对用户配置文件数据的只读访问权。
+ email:
+ 使用 OpenID Connect 授予对用户主电子邮件地址的只读访问权。
+ project_access_token_scope_desc:
+ api:
+ 授予对限定范围的项目 API 的完全读写访问权。
+ read_api:
+ 授予对限定范围的项目 API 的读访问权。
+ read_repository:
+ 允许只读访问(拉取)到仓库。
+ write_repository:
+ 允许对仓库的读写访问(拉取、推送)。
+ read_registry:
+ 如果项目是私有的且需要授权,则允许读取(拉取)容器镜像库镜像。
+ write_registry:
+ 允许写访问(推送)到容器镜像库。
+ flash:
+ applications:
+ create:
+ notice: '创建应用成功。'
+ destroy:
+ notice: '删除应用成功。'
+ update:
+ notice: '更新应用成功。'
+ authorized_applications:
+ destroy:
+ notice: '应用被撤销访问权限。'
diff --git a/db/post_migrate/20220802112102_schedule_migrate_shared_vulnerability_scanners.rb b/db/post_migrate/20220802112102_schedule_migrate_shared_vulnerability_scanners.rb
index 92ca0998bae..724bd323169 100644
--- a/db/post_migrate/20220802112102_schedule_migrate_shared_vulnerability_scanners.rb
+++ b/db/post_migrate/20220802112102_schedule_migrate_shared_vulnerability_scanners.rb
@@ -1,34 +1,14 @@
# frozen_string_literal: true
class ScheduleMigrateSharedVulnerabilityScanners < Gitlab::Database::Migration[2.0]
- MIGRATION = "MigrateSharedVulnerabilityScanners"
- TABLE_NAME = :vulnerability_occurrences
- BATCH_COLUMN = :id
- DELAY_INTERVAL = 5.minutes
- BATCH_SIZE = 1000
- SUB_BATCH_SIZE = 100
-
- BATCH_MIN_VALUE = 23658505
- BATCH_MAX_VALUE = 204428752
-
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
- queue_batched_background_migration(
- MIGRATION,
- TABLE_NAME,
- BATCH_COLUMN,
- job_interval: DELAY_INTERVAL,
- batch_size: BATCH_SIZE,
- max_batch_size: BATCH_SIZE,
- sub_batch_size: SUB_BATCH_SIZE,
- batch_min_value: BATCH_MIN_VALUE,
- batch_max_value: BATCH_MAX_VALUE
- )
+ # no-op
end
def down
- delete_batched_background_migration(MIGRATION, TABLE_NAME, BATCH_COLUMN, [])
+ # no-op
end
end
diff --git a/db/post_migrate/20220919080303_delete_migrate_shared_vulnerability_scanners.rb b/db/post_migrate/20220919080303_delete_migrate_shared_vulnerability_scanners.rb
new file mode 100644
index 00000000000..4aedfcf1699
--- /dev/null
+++ b/db/post_migrate/20220919080303_delete_migrate_shared_vulnerability_scanners.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+class DeleteMigrateSharedVulnerabilityScanners < Gitlab::Database::Migration[2.0]
+ disable_ddl_transaction!
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ MIGRATION = "MigrateSharedVulnerabilityScanners"
+ TABLE_NAME = :vulnerability_occurrences
+ BATCH_COLUMN = :id
+ BATCH_SIZE = 250
+
+ class BatchedBackgroundMigration < MigrationRecord
+ self.table_name = "batched_background_migrations"
+ end
+
+ class BatchedBackgroundMigrationJob < MigrationRecord
+ include ::EachBatch
+
+ self.table_name = "batched_background_migration_jobs"
+
+ belongs_to :batched_background_migration
+ end
+
+ def up
+ return unless migration_id = BatchedBackgroundMigration.find_by(job_class_name: MIGRATION)&.id
+
+ # rubocop:disable Style/SymbolProc
+ BatchedBackgroundMigrationJob
+ .where(batched_background_migration_id: migration_id)
+ .each_batch(of: BATCH_SIZE) do |relation|
+ relation.delete_all
+ end
+ # rubocop:enable Style/SymbolProc
+
+ delete_batched_background_migration(MIGRATION,
+ TABLE_NAME,
+ BATCH_COLUMN,
+ [])
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20220919080304_reschedule_migrate_shared_vulnerability_scanners.rb b/db/post_migrate/20220919080304_reschedule_migrate_shared_vulnerability_scanners.rb
new file mode 100644
index 00000000000..69757085587
--- /dev/null
+++ b/db/post_migrate/20220919080304_reschedule_migrate_shared_vulnerability_scanners.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class RescheduleMigrateSharedVulnerabilityScanners < Gitlab::Database::Migration[2.0]
+ MIGRATION = "MigrateSharedVulnerabilityScanners"
+ TABLE_NAME = :vulnerability_occurrences
+ BATCH_COLUMN = :id
+ DELAY_INTERVAL = 5.minutes
+ BATCH_SIZE = 1000
+ SUB_BATCH_SIZE = 100
+
+ disable_ddl_transaction!
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ TABLE_NAME,
+ BATCH_COLUMN,
+ job_interval: DELAY_INTERVAL,
+ batch_size: BATCH_SIZE,
+ max_batch_size: BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE
+ )
+ end
+
+ def down
+ delete_batched_background_migration(MIGRATION, TABLE_NAME, BATCH_COLUMN, [])
+ end
+end
diff --git a/db/schema_migrations/20220919080303 b/db/schema_migrations/20220919080303
new file mode 100644
index 00000000000..081e25c4ed5
--- /dev/null
+++ b/db/schema_migrations/20220919080303
@@ -0,0 +1 @@
+9a5ba202075e0022defd834184aa59c60980cdccf7f4111834af6a119713b4c2 \ No newline at end of file
diff --git a/db/schema_migrations/20220919080304 b/db/schema_migrations/20220919080304
new file mode 100644
index 00000000000..263128018ca
--- /dev/null
+++ b/db/schema_migrations/20220919080304
@@ -0,0 +1 @@
+d5883d3edad5d8cc130f26feb4cc6fdb63e3b46c513ce463bdf7e45a8d7ffcdf \ No newline at end of file
diff --git a/doc/administration/logs/index.md b/doc/administration/logs/index.md
index 2bcda759442..8512d19c30c 100644
--- a/doc/administration/logs/index.md
+++ b/doc/administration/logs/index.md
@@ -340,6 +340,12 @@ associated SSH key can download the project in question by using a `git fetch` o
- `params`: Key-value pairs passed in a query string or HTTP body (sensitive parameters, such as passwords and tokens, are filtered out)
- `ua`: The User-Agent of the requester
+NOTE:
+As of [`Grape Logging`](https://github.com/aserafin/grape_logging) v1.8.4,
+the `view_duration_s` is calculated by [`duration_s - db_duration_s`](https://github.com/aserafin/grape_logging/blob/v1.8.4/lib/grape_logging/middleware/request_logger.rb#L117-L119).
+Therefore, `view_duration_s` can be affected by multiple different factors, like read-write
+process on Redis or external HTTP, not only the serialization process.
+
## `application.log`
Depending on your installation method, this file is located at:
diff --git a/doc/architecture/blueprints/graphql_api/index.md b/doc/architecture/blueprints/graphql_api/index.md
index baa926c5847..4b446a78541 100644
--- a/doc/architecture/blueprints/graphql_api/index.md
+++ b/doc/architecture/blueprints/graphql_api/index.md
@@ -4,7 +4,7 @@ creation-date: "2021-01-07"
authors: [ "@grzesiek" ]
coach: "@kamil"
approvers: [ "@dsatcher", "@deuley" ]
-owning-stage: "~devops::ecosystem"
+owning-stage: "~devops::manage"
participating-stages: []
---
diff --git a/doc/ci/runners/saas/macos_saas_runner.md b/doc/ci/runners/saas/macos_saas_runner.md
index 50c24349712..cd40aac25bc 100644
--- a/doc/ci/runners/saas/macos_saas_runner.md
+++ b/doc/ci/runners/saas/macos_saas_runner.md
@@ -14,7 +14,7 @@ Use these runners to build, test, and deploy apps for the Apple ecosystem (macOS
of all the capabilities of the GitLab single DevOps platform and not have to manage or operate a
build environment.
-Jobs handled by macOS shared runners on GitLab.com **time out after 2 hours**, regardless of the timeout configured in a project.
+Jobs handled by macOS shared runners on GitLab.com **time out after 3 hours**, regardless of the timeout configured in a project.
## Access request process
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index d0569b9ca6c..280af21a864 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -147,7 +147,7 @@ with [domain expertise](#domain-experts).
| `~workhorse` changes | [Workhorse maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_workhorse). |
| `~frontend` changes (*1*) | [Frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_maintainers_frontend). |
| `~UX` user-facing changes (*3*) | [Product Designer](https://about.gitlab.com/handbook/engineering/projects/#gitlab_reviewers_UX). Refer to the [design and user interface guidelines](contributing/design.md) for details. |
-| Adding a new JavaScript library (*1*) | - [Frontend foundations member](https://about.gitlab.com/direction/ecosystem/foundations/) if the library significantly increases the [bundle size](https://gitlab.com/gitlab-org/frontend/playground/webpack-memory-metrics/-/blob/master/doc/report.md).<br/>- A [legal department member](https://about.gitlab.com/handbook/legal/) if the license used by the new library hasn't been approved for use in GitLab.<br/><br/>More information about license compatibility can be found in our [GitLab Licensing and Compatibility documentation](licensing.md). |
+| Adding a new JavaScript library (*1*) | - [Frontend foundations member](https://about.gitlab.com/direction/manage/foundations/) if the library significantly increases the [bundle size](https://gitlab.com/gitlab-org/frontend/playground/webpack-memory-metrics/-/blob/master/doc/report.md).<br/>- A [legal department member](https://about.gitlab.com/handbook/legal/) if the license used by the new library hasn't been approved for use in GitLab.<br/><br/>More information about license compatibility can be found in our [GitLab Licensing and Compatibility documentation](licensing.md). |
| A new dependency or a file system change | - [Distribution team member](https://about.gitlab.com/company/team/). See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/development/enablement/systems/distribution/#how-to-work-with-distribution) for more details.<br/>- For Rubygems, request an [AppSec review](gemfile.md#request-an-appsec-review). |
| `~documentation` or `~UI text` changes | [Technical writer](https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments) based on assignments in the appropriate [DevOps stage group](https://about.gitlab.com/handbook/product/categories/#devops-stages). |
| Changes to development guidelines | Follow the [review process](development_processes.md#development-guidelines-review) and get the approvals accordingly. |
diff --git a/doc/development/contributing/design.md b/doc/development/contributing/design.md
index 9e54b92337a..a1f9fa2e457 100644
--- a/doc/development/contributing/design.md
+++ b/doc/development/contributing/design.md
@@ -64,7 +64,7 @@ Check visual design properties using your browser's _elements inspector_ ([Chrom
guidelines.
- _Optionally_ consider [dark mode](../../user/profile/preferences.md#dark-mode). [^1]
- [^1]: You're not required to design for [dark mode](../../user/profile/preferences.md#dark-mode) while the feature is in [alpha](../../policy/alpha-beta-support.md#alpha-features). The [UX Foundations team](https://about.gitlab.com/direction/ecosystem/foundations/) plans to improve the dark mode in the future. Until we integrate [Pajamas](https://design.gitlab.com/) components into the product and the underlying design strategy is in place to support dark mode, we cannot guarantee that we won't introduce bugs and debt to this mode. At your discretion, evaluate the need to create dark mode patches.
+ [^1]: You're not required to design for [dark mode](../../user/profile/preferences.md#dark-mode) while the feature is in [alpha](../../policy/alpha-beta-support.md#alpha-features). The [UX Foundations team](https://about.gitlab.com/direction/manage/foundations/) plans to improve the dark mode in the future. Until we integrate [Pajamas](https://design.gitlab.com/) components into the product and the underlying design strategy is in place to support dark mode, we cannot guarantee that we won't introduce bugs and debt to this mode. At your discretion, evaluate the need to create dark mode patches.
### States
diff --git a/doc/development/fe_guide/view_component.md b/doc/development/fe_guide/view_component.md
index 662d1ad32fc..d9d2b6707f7 100644
--- a/doc/development/fe_guide/view_component.md
+++ b/doc/development/fe_guide/view_component.md
@@ -24,7 +24,7 @@ available as a ViewComponent in `app/components/pajamas`.
NOTE:
We are still in the process of creating these components, so not every Pajamas component is available as ViewComponent.
-Reach out to the [Foundations team](https://about.gitlab.com/handbook/engineering/development/dev/ecosystem/foundations/)
+Reach out to the [Foundations team](https://about.gitlab.com/handbook/engineering/development/dev/manage/foundations/)
if the component you are looking for is not yet available.
### Available components
diff --git a/doc/development/integrations/index.md b/doc/development/integrations/index.md
index a0a81775391..2639da818c6 100644
--- a/doc/development/integrations/index.md
+++ b/doc/development/integrations/index.md
@@ -10,9 +10,9 @@ description: "GitLab's development guidelines for Integrations"
This page provides development guidelines for implementing [GitLab integrations](../../user/project/integrations/index.md),
which are part of our [main Rails project](https://gitlab.com/gitlab-org/gitlab).
-Also see our [direction page](https://about.gitlab.com/direction/ecosystem/integrations/) for an overview of our strategy around integrations.
+Also see our [direction page](https://about.gitlab.com/direction/manage/integrations/) for an overview of our strategy around integrations.
-This guide is a work in progress. You're welcome to ping `@gitlab-org/ecosystem-stage/integrations`
+This guide is a work in progress. You're welcome to ping `@gitlab-org/manage/integrations`
if you need clarification or spot any outdated information.
## Add a new integration
diff --git a/doc/operations/incident_management/img/timeline_event_for_severity_change_v15_6.png b/doc/operations/incident_management/img/timeline_event_for_severity_change_v15_6.png
new file mode 100644
index 00000000000..121cc4b23cb
--- /dev/null
+++ b/doc/operations/incident_management/img/timeline_event_for_severity_change_v15_6.png
Binary files differ
diff --git a/doc/operations/incident_management/incident_timeline_events.md b/doc/operations/incident_management/incident_timeline_events.md
index 5f98335d7aa..7d31569b6a1 100644
--- a/doc/operations/incident_management/incident_timeline_events.md
+++ b/doc/operations/incident_management/incident_timeline_events.md
@@ -74,6 +74,21 @@ To create a timeline event from a comment on the incident:
The comment is shown on the incident timeline as a timeline event.
+### When incident severity changes
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/375280) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `incident_timeline_events_for_severity`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is unavailable. To show the feature per user,
+ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `incident_timeline_events_for_severity`.
+On GitLab.com, this feature is not available.
+This feature is not ready for production use.
+
+A new timeline event is created when someone [changes the severity](incidents.md#change-severity)
+of an incident.
+
+![Incident timeline event for severity change](img/timeline_event_for_severity_change_v15_6.png)
+
## Delete an event
You can also delete timeline events.
diff --git a/doc/topics/awesome_co.md b/doc/topics/awesome_co.md
index 0d725f64f3a..49e39542b2b 100644
--- a/doc/topics/awesome_co.md
+++ b/doc/topics/awesome_co.md
@@ -1,5 +1,5 @@
---
-stage: Ecosystem
+stage: Manage
group: Foundations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
description: AwesomeCo test data harness created by the Test Data Working Group https://about.gitlab.com/company/team/structure/working-groups/demo-test-data/
diff --git a/doc/user/group/epics/manage_epics.md b/doc/user/group/epics/manage_epics.md
index d810724e130..b96670669a0 100644
--- a/doc/user/group/epics/manage_epics.md
+++ b/doc/user/group/epics/manage_epics.md
@@ -311,6 +311,8 @@ To make an epic confidential:
- **In an existing epic:** on the right sidebar, select **Edit** next to **Confidentiality**, and then
select **Turn on**.
+In GitLab 15.6 and later, you can also use the `/confidential` [quick action](../../../user/project/quick_actions.md).
+
## Manage issues assigned to an epic
This section collects instructions for all the things you can do with [issues](../../project/issues/index.md)
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
index 593557967ed..27d935d0ed1 100644
--- a/doc/user/project/issues/design_management.md
+++ b/doc/user/project/issues/design_management.md
@@ -4,7 +4,7 @@ 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
---
-# Design Management **(FREE)**
+# Design management **(FREE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/660) in GitLab 12.2.
> - Support for SVGs [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12771) in GitLab 12.4.
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 2ff579b8f8f..7d59d93df56 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -65,7 +65,7 @@ threads. Some quick actions might not be available to all subscription tiers.
| `/clear_weight` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clear weight. |
| `/clone <path/to/project> [--with_notes]` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Clone the issue to given project, or the current one if no arguments are given ([introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9421) in GitLab 13.7). Copies as much data as possible as long as the target project contains equivalent labels, milestones, and so on. Does not copy comments or system notes unless `--with_notes` is provided as an argument. |
| `/close` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Close. |
-| `/confidential` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Make confidential. |
+| `/confidential` | **{check-circle}** Yes | **{dotted-circle}** No | **{check-circle}** Yes | Mark issue or epic as confidential. Support for epics [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213741) in GitLab 15.6. |
| `/copy_metadata <!merge_request>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another merge request in the project. |
| `/copy_metadata <#issue>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another issue in the project. |
| `/create_merge_request <branch name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Create a new merge request starting from the current issue. |
diff --git a/doc/user/project/requirements/index.md b/doc/user/project/requirements/index.md
index 400f03ef0fe..922accf9d28 100644
--- a/doc/user/project/requirements/index.md
+++ b/doc/user/project/requirements/index.md
@@ -5,7 +5,7 @@ group: Certify
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
---
-# Requirements Management **(ULTIMATE)**
+# Requirements management **(ULTIMATE)**
NOTE:
In 14.4, Requirements was moved under **Issues**.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 9d3e568074a..d07e0bcf33c 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -177,6 +177,7 @@ module API
mount ::API::UserCounts
mount ::API::ProjectRepositoryStorageMoves
mount ::API::SnippetRepositoryStorageMoves
+ mount ::API::Statistics
add_open_api_documentation!
end
@@ -307,7 +308,6 @@ module API
mount ::API::Settings
mount ::API::SidekiqMetrics
mount ::API::Snippets
- mount ::API::Statistics
mount ::API::Submodules
mount ::API::Subscriptions
mount ::API::Suggestions
diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb
index 3b85d6952a1..0b37c80dc5f 100644
--- a/lib/gitlab/quick_actions/issuable_actions.rb
+++ b/lib/gitlab/quick_actions/issuable_actions.rb
@@ -12,16 +12,13 @@ module Gitlab
included do
# Issue, MergeRequest, Epic: quick actions definitions
desc do
- _('Close this %{quick_action_target}') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ _('Close this %{quick_action_target}') % { quick_action_target: target_issuable_name }
end
explanation do
- _('Closes this %{quick_action_target}.') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ _('Closes this %{quick_action_target}.') % { quick_action_target: target_issuable_name }
end
execution_message do
- _('Closed this %{quick_action_target}.') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ _('Closed this %{quick_action_target}.') % { quick_action_target: target_issuable_name }
end
types ::Issuable
condition do
@@ -35,15 +32,15 @@ module Gitlab
desc do
_('Reopen this %{quick_action_target}') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ { quick_action_target: target_issuable_name }
end
explanation do
_('Reopens this %{quick_action_target}.') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ { quick_action_target: target_issuable_name }
end
execution_message do
_('Reopened this %{quick_action_target}.') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ { quick_action_target: target_issuable_name }
end
types ::Issuable
condition do
@@ -170,12 +167,10 @@ module Gitlab
desc { _('Subscribe') }
explanation do
- _('Subscribes to this %{quick_action_target}.') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ _('Subscribes to this %{quick_action_target}.') % { quick_action_target: target_issuable_name }
end
execution_message do
- _('Subscribed to this %{quick_action_target}.') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ _('Subscribed to this %{quick_action_target}.') % { quick_action_target: target_issuable_name }
end
types ::Issuable
condition do
@@ -188,12 +183,10 @@ module Gitlab
desc { _('Unsubscribe') }
explanation do
- _('Unsubscribes from this %{quick_action_target}.') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ _('Unsubscribes from this %{quick_action_target}.') % { quick_action_target: target_issuable_name }
end
execution_message do
- _('Unsubscribed from this %{quick_action_target}.') %
- { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ _('Unsubscribed from this %{quick_action_target}.') % { quick_action_target: target_issuable_name }
end
types ::Issuable
condition do
@@ -266,6 +259,16 @@ module Gitlab
end
end
+ desc { _("Make %{type} confidential") % { type: target_issuable_name } }
+ explanation { _("Makes this %{type} confidential.") % { type: target_issuable_name } }
+ types ::Issuable
+ condition { quick_action_target.supports_confidentiality? && can_make_confidential? }
+ command :confidential do
+ @updates[:confidential] = true
+
+ @execution_message[:confidential] = confidential_execution_message
+ end
+
private
def find_severity(severity_param)
@@ -315,6 +318,29 @@ module Gitlab
_('Removed all labels.')
end
end
+
+ def target_issuable_name
+ quick_action_target.to_ability_name.humanize(capitalize: false)
+ end
+
+ def can_make_confidential?
+ confidentiality_not_supported = quick_action_target.respond_to?(:issue_type_supports?) &&
+ !quick_action_target.issue_type_supports?(:confidentiality)
+
+ return false if confidentiality_not_supported
+
+ !quick_action_target.confidential? && current_user.can?(:set_confidentiality, quick_action_target)
+ end
+
+ def confidential_execution_message
+ confidential_error_message.presence || _("Made this %{type} confidential.") % { type: target_issuable_name }
+ end
+
+ def confidential_error_message
+ return unless quick_action_target.respond_to?(:confidentiality_errors)
+
+ quick_action_target.confidentiality_errors.join("\n")
+ end
end
end
end
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
index 4883c649a62..e74c58e45b1 100644
--- a/lib/gitlab/quick_actions/issue_actions.rb
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -161,23 +161,6 @@ module Gitlab
@execution_message[:move] = message
end
- desc { _('Make issue confidential') }
- explanation do
- _('Makes this issue confidential.')
- end
- execution_message do
- _('Made this issue confidential.')
- end
- types Issue
- condition do
- quick_action_target.issue_type_supports?(:confidentiality) &&
- !quick_action_target.confidential? &&
- current_user.can?(:set_confidentiality, quick_action_target)
- end
- command :confidential do
- @updates[:confidential] = true
- end
-
desc { _('Create a merge request') }
explanation do |branch_name = nil|
if branch_name
diff --git a/lib/gitlab/sidekiq_middleware/size_limiter/compressor.rb b/lib/gitlab/sidekiq_middleware/size_limiter/compressor.rb
index bce295d8ba5..f7e0553e536 100644
--- a/lib/gitlab/sidekiq_middleware/size_limiter/compressor.rb
+++ b/lib/gitlab/sidekiq_middleware/size_limiter/compressor.rb
@@ -33,7 +33,7 @@ module Gitlab
validate_args!(job)
job.except!(ORIGINAL_SIZE_KEY, COMPRESSED_KEY)
- job['args'] = Sidekiq.load_json(Zlib::Inflate.inflate(Base64.strict_decode64(job['args'].first)))
+ job['args'] = Gitlab::Json.load(Zlib::Inflate.inflate(Base64.strict_decode64(job['args'].first)))
rescue Zlib::Error
raise PayloadDecompressionError, 'Fail to decompress Sidekiq job payload'
end
diff --git a/lib/gitlab/sidekiq_migrate_jobs.rb b/lib/gitlab/sidekiq_migrate_jobs.rb
index 8c1153d6112..9811e1d53d2 100644
--- a/lib/gitlab/sidekiq_migrate_jobs.rb
+++ b/lib/gitlab/sidekiq_migrate_jobs.rb
@@ -34,7 +34,7 @@ module Gitlab
next unless job.match?(source_queues_regex)
- job_hash = Sidekiq.load_json(job)
+ job_hash = Gitlab::Json.load(job)
destination_queue = mappings[job_hash['class']]
next unless mappings.has_key?(job_hash['class'])
@@ -77,12 +77,12 @@ module Gitlab
end
job = conn.rpop "queue:#{queue_from}"
- job_hash = Sidekiq.load_json job
+ job_hash = Gitlab::Json.load(job)
next unless mappings.has_key?(job_hash['class'])
destination_queue = mappings[job_hash['class']]
job_hash['queue'] = destination_queue
- conn.lpush("queue:#{destination_queue}", Sidekiq.dump_json(job_hash))
+ conn.lpush("queue:#{destination_queue}", Gitlab::Json.dump(job_hash))
migrated += 1
rescue JSON::ParserError
logger&.error("Unmarshal JSON payload from SidekiqMigrateJobs failed. Job: #{job}")
@@ -101,7 +101,7 @@ module Gitlab
removed = connection.zrem(sidekiq_set, job)
if removed
- connection.zadd(sidekiq_set, score, Sidekiq.dump_json(job_hash))
+ connection.zadd(sidekiq_set, score, Gitlab::Json.dump(job_hash))
1
else
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
index ca7ae429986..0a8fb04dfc6 100644
--- a/lib/gitlab/sql/pattern.rb
+++ b/lib/gitlab/sql/pattern.rb
@@ -6,7 +6,7 @@ module Gitlab
extend ActiveSupport::Concern
MIN_CHARS_FOR_PARTIAL_MATCHING = 3
- REGEX_QUOTED_WORD = /(?<=\A| )"[^"]+"(?= |\z)/.freeze
+ REGEX_QUOTED_TERM = /(?<=\A| )"[^"]+"(?= |\z)/.freeze
class_methods do
def fuzzy_search(query, columns, use_minimum_char_limit: true)
@@ -45,7 +45,7 @@ module Gitlab
arel_column = column.is_a?(Arel::Attributes::Attribute) ? column : arel_table[column]
- words = select_fuzzy_words(query, use_minimum_char_limit: use_minimum_char_limit)
+ words = select_fuzzy_terms(query, use_minimum_char_limit: use_minimum_char_limit)
if words.any?
words.map { |word| arel_column.matches(to_pattern(word, use_minimum_char_limit: use_minimum_char_limit)) }.reduce(:and)
@@ -62,19 +62,21 @@ module Gitlab
end
end
- def select_fuzzy_words(query, use_minimum_char_limit: true)
- quoted_words = query.scan(REGEX_QUOTED_WORD)
-
- query = quoted_words.reduce(query) { |q, quoted_word| q.sub(quoted_word, '') }
-
- words = query.split
-
- quoted_words.map! { |quoted_word| quoted_word[1..-2] }
+ def select_fuzzy_terms(query, use_minimum_char_limit: true)
+ terms = Gitlab::SQL::Pattern.split_query_to_search_terms(query)
+ terms.select { |term| partial_matching?(term, use_minimum_char_limit: use_minimum_char_limit) }
+ end
+ end
- words.concat(quoted_words)
+ def self.split_query_to_search_terms(query)
+ quoted_terms = []
- words.select { |word| partial_matching?(word, use_minimum_char_limit: use_minimum_char_limit) }
+ query = query.gsub(REGEX_QUOTED_TERM) do |quoted_term|
+ quoted_terms << quoted_term
+ ""
end
+
+ query.split + quoted_terms.map { |quoted_term| quoted_term[1..-2] }
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 41e6d115263..7d9663108bf 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -24645,7 +24645,7 @@ msgstr ""
msgid "MRDiff|Show full file"
msgstr ""
-msgid "Made this issue confidential."
+msgid "Made this %{type} confidential."
msgstr ""
msgid "Mailgun"
@@ -24666,6 +24666,9 @@ msgstr ""
msgid "Maintenance mode"
msgstr ""
+msgid "Make %{type} confidential"
+msgstr ""
+
msgid "Make adjustments to how your GitLab instance is set up."
msgstr ""
@@ -24675,9 +24678,6 @@ msgstr ""
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr ""
-msgid "Make issue confidential"
-msgstr ""
-
msgid "Make sure you choose a strong, unique password."
msgstr ""
@@ -24687,7 +24687,7 @@ msgstr ""
msgid "Make sure you save it - you won't be able to access it again."
msgstr ""
-msgid "Makes this issue confidential."
+msgid "Makes this %{type} confidential."
msgstr ""
msgid "Manage %{workspace} labels"
diff --git a/package.json b/package.json
index 3e4f06437a5..363c5bd3763 100644
--- a/package.json
+++ b/package.json
@@ -54,7 +54,7 @@
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "3.5.0",
- "@gitlab/ui": "49.0.2",
+ "@gitlab/ui": "49.2.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20220815034418",
"@rails/actioncable": "6.1.4-7",
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
index 6e751100096..2f177d12389 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
@@ -5,7 +5,7 @@ module QA
let!(:user) do
Resource::User.fabricate_via_api! do |user|
user.name = "QA User <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;"
- user.password = "test1234"
+ user.password = "pw_#{SecureRandom.hex(12)}"
user.api_client = Runtime::API::Client.as_admin
end
end
diff --git a/rubocop/cop/gitlab/mark_used_feature_flags.rb b/rubocop/cop/gitlab/mark_used_feature_flags.rb
index 23de0644385..d1722a47c8a 100644
--- a/rubocop/cop/gitlab/mark_used_feature_flags.rb
+++ b/rubocop/cop/gitlab/mark_used_feature_flags.rb
@@ -59,7 +59,7 @@ module RuboCop
def on_casgn(node)
_, lhs_name, rhs = *node
- save_used_feature_flag(rhs.value) if lhs_name == :FEATURE_FLAG
+ save_used_feature_flag(rhs.value) if lhs_name.to_s.end_with?('FEATURE_FLAG')
end
def on_send(node)
diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml
index b1de18fae48..954f1a7297d 100644
--- a/scripts/review_apps/base-config.yaml
+++ b/scripts/review_apps/base-config.yaml
@@ -23,12 +23,12 @@ gitlab:
resources:
requests:
cpu: 1200m
- memory: 600M
+ memory: 600Mi
limits:
cpu: 1800m
- memory: 1000M
+ memory: 1000Mi
persistence:
- size: 10G
+ size: 10Gi
storageClass: ssd
nodeSelector:
preemptible: "false"
@@ -42,18 +42,18 @@ gitlab:
resources:
requests:
cpu: 200m
- memory: 450M
+ memory: 450Mi
limits:
cpu: 400m
- memory: 900M
+ memory: 900Mi
gitlab-shell:
resources:
requests:
cpu: 500m
- memory: 100M
+ memory: 100Mi
limits:
cpu: 750m
- memory: 150M
+ memory: 150Mi
maxReplicas: 3
hpa:
targetAverageValue: 500m
@@ -64,28 +64,28 @@ gitlab:
resources:
requests:
cpu: 855m
- memory: 1927M
+ memory: 1927Mi
limits:
cpu: 1282m
- memory: 2890M
+ memory: 2890Mi
hpa:
targetAverageValue: 650m
toolbox:
resources:
requests:
cpu: 300m
- memory: 1927M
+ memory: 1927Mi
limits:
cpu: 450m
- memory: 2890M
+ memory: 2890Mi
webservice:
resources:
requests:
cpu: 746m
- memory: 2809M
+ memory: 2809Mi
limits:
cpu: 1119m
- memory: 4214M
+ memory: 4214Mi
deployment:
readinessProbe:
initialDelaySeconds: 5 # Default is 0
@@ -95,10 +95,10 @@ gitlab:
resources:
requests:
cpu: 400m
- memory: 75M
+ memory: 75Mi
limits:
cpu: 600m
- memory: 113M
+ memory: 113Mi
readinessProbe:
initialDelaySeconds: 5 # Default is 0
periodSeconds: 15 # Default is 10
@@ -107,10 +107,10 @@ gitlab-runner:
resources:
requests:
cpu: 675m
- memory: 100M
+ memory: 100Mi
limits:
cpu: 1015m
- memory: 150M
+ memory: 150Mi
nodeSelector:
preemptible: "true"
podAnnotations:
@@ -119,10 +119,10 @@ minio:
resources:
requests:
cpu: 9m
- memory: 128M
+ memory: 128Mi
limits:
cpu: 15m
- memory: 280M
+ memory: 280Mi
nodeSelector:
preemptible: "true"
podAnnotations:
@@ -134,10 +134,10 @@ nginx-ingress:
resources:
requests:
cpu: 300m
- memory: 450M
+ memory: 450Mi
limits:
cpu: 600m
- memory: 675M
+ memory: 675Mi
service:
enableHttp: false
livenessProbe:
@@ -150,10 +150,10 @@ nginx-ingress:
resources:
requests:
cpu: 5m
- memory: 12M
+ memory: 12Mi
limits:
cpu: 10m
- memory: 24M
+ memory: 24Mi
nodeSelector:
preemptible: "true"
postgresql:
@@ -162,10 +162,10 @@ postgresql:
resources:
requests:
cpu: 600m
- memory: 1000M
+ memory: 1000Mi
limits:
cpu: 1300m
- memory: 1600M
+ memory: 1600Mi
master:
nodeSelector:
preemptible: "false"
@@ -179,10 +179,10 @@ redis:
resources:
requests:
cpu: 100m
- memory: 60M
+ memory: 60Mi
limits:
cpu: 200m
- memory: 130M
+ memory: 130Mi
master:
nodeSelector:
preemptible: "true"
@@ -194,9 +194,9 @@ registry:
resources:
requests:
cpu: 100m
- memory: 30M
+ memory: 30Mi
limits:
cpu: 200m
- memory: 45M
+ memory: 45Mi
nodeSelector:
preemptible: "true"
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index d21bde758d0..c383c4b6f24 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -214,6 +214,7 @@ RSpec.describe 'Database schema' do
"ApplicationSetting" => %w[repository_storages_weighted],
"AlertManagement::Alert" => %w[payload],
"Ci::BuildMetadata" => %w[config_options config_variables],
+ "Ci::BuildMetadata::Partitioned" => %w[config_options config_variables id_tokens runtime_runner_features secrets],
"ExperimentSubject" => %w[context],
"ExperimentUser" => %w[context],
"Geo::Event" => %w[payload],
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 1190b0f3558..eabbcd5e38e 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -342,11 +342,18 @@ RSpec.describe 'Pipelines', :js do
end
context 'when user played a delayed job immediately' do
+ let(:manual_action_selector) { '[data-testid="pipelines-manual-actions-dropdown"]' }
+
before do
- find('[data-testid="pipelines-manual-actions-dropdown"]').click
+ find(manual_action_selector).click
accept_gl_confirm do
click_button 'delayed job 1'
end
+
+ # Wait for UI to transition to ensure a request has been made
+ within(manual_action_selector) { find('.gl-spinner') }
+ within(manual_action_selector) { find('[data-testid="play-icon"]') }
+
wait_for_requests
end
diff --git a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
index 08424077269..41220aaf306 100644
--- a/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
+++ b/spec/frontend/vue_merge_request_widget/components/widget/__snapshots__/dynamic_content_spec.js.snap
@@ -7,7 +7,7 @@ exports[`~/vue_merge_request_widget/components/widget/dynamic_content.vue render
<p class=\\"gl-mb-0\\">Main text for the row</p>
<gl-link-stub href=\\"https://gitlab.com\\">Optional link to display after text</gl-link-stub>
<!---->
- <gl-badge-stub size=\\"md\\" variant=\\"info\\">
+ <gl-badge-stub size=\\"md\\" variant=\\"info\\" iconsize=\\"md\\">
Badge is optional. Text to be displayed inside badge
</gl-badge-stub>
<actions-stub widget=\\"MyWidget\\" tertiarybuttons=\\"\\" class=\\"gl-ml-auto gl-pl-3\\"></actions-stub>
diff --git a/spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb b/spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb
index e5c3ea5c27b..db6ae1fc45a 100644
--- a/spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb
+++ b/spec/lib/gitlab/sidekiq_migrate_jobs_spec.rb
@@ -22,7 +22,7 @@ RSpec.describe Gitlab::SidekiqMigrateJobs, :clean_gitlab_redis_queues do
let(:set_after) do
Sidekiq.redis { |c| c.zrange(set_name, 0, -1, with_scores: true) }
- .map { |item, score| [Sidekiq.load_json(item), score] }
+ .map { |item, score| [Gitlab::Json.load(item), score] }
end
context 'when the set is empty' do
@@ -233,7 +233,7 @@ RSpec.describe Gitlab::SidekiqMigrateJobs, :clean_gitlab_redis_queues do
def list_jobs(queue_name)
Sidekiq.redis { |conn| conn.lrange("queue:#{queue_name}", 0, -1) }
- .map { |item| Sidekiq.load_json item }
+ .map { |item| Gitlab::Json.load(item) }
end
def pre_migrate_checks; end
diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb
index c12cdab0b9b..dd33fc9d764 100644
--- a/spec/lib/gitlab/sql/pattern_spec.rb
+++ b/spec/lib/gitlab/sql/pattern_spec.rb
@@ -104,14 +104,14 @@ RSpec.describe Gitlab::SQL::Pattern do
end
end
- describe '.select_fuzzy_words' do
- subject(:select_fuzzy_words) { Issue.select_fuzzy_words(query) }
+ describe '.select_fuzzy_terms' do
+ subject(:select_fuzzy_terms) { Issue.select_fuzzy_terms(query) }
context 'with a word equal to 3 chars' do
let(:query) { 'foo' }
it 'returns array containing a word' do
- expect(select_fuzzy_words).to match_array(['foo'])
+ expect(select_fuzzy_terms).to match_array(['foo'])
end
end
@@ -119,7 +119,7 @@ RSpec.describe Gitlab::SQL::Pattern do
let(:query) { 'fo' }
it 'returns empty array' do
- expect(select_fuzzy_words).to match_array([])
+ expect(select_fuzzy_terms).to match_array([])
end
end
@@ -127,7 +127,7 @@ RSpec.describe Gitlab::SQL::Pattern do
let(:query) { 'foo baz' }
it 'returns array containing two words' do
- expect(select_fuzzy_words).to match_array(%w[foo baz])
+ expect(select_fuzzy_terms).to match_array(%w[foo baz])
end
end
@@ -135,7 +135,7 @@ RSpec.describe Gitlab::SQL::Pattern do
let(:query) { 'foo baz' }
it 'returns array containing two words' do
- expect(select_fuzzy_words).to match_array(%w[foo baz])
+ expect(select_fuzzy_terms).to match_array(%w[foo baz])
end
end
@@ -143,7 +143,19 @@ RSpec.describe Gitlab::SQL::Pattern do
let(:query) { 'foo ba' }
it 'returns array containing a word' do
- expect(select_fuzzy_words).to match_array(['foo'])
+ expect(select_fuzzy_terms).to match_array(['foo'])
+ end
+ end
+ end
+
+ describe '.split_query_to_search_terms' do
+ subject(:split_query_to_search_terms) { described_class.split_query_to_search_terms(query) }
+
+ context 'with words separated by spaces' do
+ let(:query) { 'really bar baz' }
+
+ it 'returns array containing individual words' do
+ expect(split_query_to_search_terms).to match_array(%w[really bar baz])
end
end
@@ -151,15 +163,15 @@ RSpec.describe Gitlab::SQL::Pattern do
let(:query) { '"really bar"' }
it 'returns array containing a multi-word' do
- expect(select_fuzzy_words).to match_array(['really bar'])
+ expect(split_query_to_search_terms).to match_array(['really bar'])
end
end
context 'with a multi-word surrounded by double quote and two words' do
let(:query) { 'foo "really bar" baz' }
- it 'returns array containing a multi-word and tow words' do
- expect(select_fuzzy_words).to match_array(['foo', 'really bar', 'baz'])
+ it 'returns array containing a multi-word and two words' do
+ expect(split_query_to_search_terms).to match_array(['foo', 'really bar', 'baz'])
end
end
@@ -167,7 +179,7 @@ RSpec.describe Gitlab::SQL::Pattern do
let(:query) { 'foo"really bar"' }
it 'returns array containing two words with double quote' do
- expect(select_fuzzy_words).to match_array(['foo"really', 'bar"'])
+ expect(split_query_to_search_terms).to match_array(['foo"really', 'bar"'])
end
end
@@ -175,15 +187,15 @@ RSpec.describe Gitlab::SQL::Pattern do
let(:query) { '"really bar"baz' }
it 'returns array containing two words with double quote' do
- expect(select_fuzzy_words).to match_array(['"really', 'bar"baz'])
+ expect(split_query_to_search_terms).to match_array(['"really', 'bar"baz'])
end
end
context 'with two multi-word surrounded by double quote and two words' do
let(:query) { 'foo "really bar" baz "awesome feature"' }
- it 'returns array containing two multi-words and tow words' do
- expect(select_fuzzy_words).to match_array(['foo', 'really bar', 'baz', 'awesome feature'])
+ it 'returns array containing two multi-words and two words' do
+ expect(split_query_to_search_terms).to match_array(['foo', 'really bar', 'baz', 'awesome feature'])
end
end
end
diff --git a/spec/migrations/delete_migrate_shared_vulnerability_scanners_spec.rb b/spec/migrations/delete_migrate_shared_vulnerability_scanners_spec.rb
new file mode 100644
index 00000000000..259b175cd19
--- /dev/null
+++ b/spec/migrations/delete_migrate_shared_vulnerability_scanners_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+require_migration!
+
+RSpec.describe DeleteMigrateSharedVulnerabilityScanners, :migration do
+ let(:batched_background_migrations) { table(:batched_background_migrations) }
+ let(:batched_background_migration_jobs) { table(:batched_background_migration_jobs) }
+
+ let(:migration) do
+ batched_background_migrations.create!(created_at: Time.zone.now,
+ updated_at: Time.zone.now,
+ min_value: 1,
+ max_value: 1,
+ batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: 100,
+ interval: 300,
+ status: 3,
+ job_class_name: described_class::MIGRATION,
+ batch_class_name: "PrimaryKeyBatchingStrategy",
+ table_name: described_class::TABLE_NAME,
+ column_name: described_class::BATCH_COLUMN,
+ job_arguments: [],
+ pause_ms: 100,
+ max_batch_size: 1000,
+ gitlab_schema: "gitlab_main")
+ end
+
+ let(:jobs) do
+ Array.new(10) do
+ batched_background_migration_jobs.create!(batched_background_migration_id: migration.id,
+ created_at: Time.zone.now,
+ updated_at: Time.zone.now,
+ min_value: 1,
+ max_value: 1,
+ batch_size: 1,
+ sub_batch_size: 1,
+ status: 0,
+ attempts: 0,
+ metrics: {},
+ pause_ms: 100)
+ end
+ end
+
+ describe "#up" do
+ it "deletes jobs" do
+ expect { migrate! }.to change(batched_background_migration_jobs, :count).from(jobs.count).to(0)
+ end
+
+ it "deletes the migration" do
+ expect { migrate! }.to change { batched_background_migrations.find_by(id: migration.id) }.from(migration).to(nil)
+ end
+
+ context "when background migration does not exist" do
+ before do
+ migration.destroy!
+ end
+
+ it "does not delete jobs" do
+ expect { migrate! }.not_to change(batched_background_migration_jobs, :count)
+ end
+
+ it "does not delete the migration" do
+ expect { migrate! }.not_to change { batched_background_migrations.find_by(id: migration.id) }
+ end
+ end
+ end
+end
diff --git a/spec/migrations/reschedule_migrate_shared_vulnerability_scanners_spec.rb b/spec/migrations/reschedule_migrate_shared_vulnerability_scanners_spec.rb
new file mode 100644
index 00000000000..e8253f39c68
--- /dev/null
+++ b/spec/migrations/reschedule_migrate_shared_vulnerability_scanners_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+
+require_migration!
+
+RSpec.describe RescheduleMigrateSharedVulnerabilityScanners, :migration do
+ include Gitlab::Database::Migrations::BatchedBackgroundMigrationHelpers
+
+ def connection
+ ApplicationRecord.connection
+ end
+
+ describe "#up" do
+ before do
+ migrate!
+ end
+
+ it "schedules" do
+ expect(described_class::MIGRATION).to have_scheduled_batched_migration(
+ table_name: described_class::TABLE_NAME,
+ column_name: described_class::BATCH_COLUMN,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ max_batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE,
+ gitlab_schema: :gitlab_main
+ )
+ end
+ end
+
+ describe '#down' do
+ before do
+ schema_migrate_down!
+ end
+
+ it "deletes" do
+ expect(described_class::MIGRATION).not_to have_scheduled_batched_migration
+ end
+ end
+end
diff --git a/spec/migrations/schedule_migrate_shared_vulnerability_scanners_spec.rb b/spec/migrations/schedule_migrate_shared_vulnerability_scanners_spec.rb
deleted file mode 100644
index f00d6568b67..00000000000
--- a/spec/migrations/schedule_migrate_shared_vulnerability_scanners_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-require "spec_helper"
-
-require_migration!
-
-RSpec.describe ScheduleMigrateSharedVulnerabilityScanners, :migration do
- describe "#up" do
- before do
- migrate!
- end
-
- it "schedules" do
- expect(described_class::MIGRATION).to have_scheduled_batched_migration(
- table_name: described_class::TABLE_NAME,
- column_name: described_class::BATCH_COLUMN,
- interval: described_class::DELAY_INTERVAL,
- batch_size: described_class::BATCH_SIZE,
- max_batch_size: described_class::BATCH_SIZE,
- sub_batch_size: described_class::SUB_BATCH_SIZE,
- gitlab_schema: :gitlab_main
- )
- end
-
- describe "ID range" do
- let(:expected_range) do
- { min_value: described_class::BATCH_MIN_VALUE,
- max_value: described_class::BATCH_MAX_VALUE }
- end
-
- subject do
- Gitlab::Database::BackgroundMigration::BatchedMigration
- .for_configuration(:gitlab_main,
- described_class::MIGRATION,
- described_class::TABLE_NAME,
- described_class::BATCH_COLUMN,
- [])
- end
-
- it "is set" do
- # The `have_scheduled_batched_migration` matcher accepts the
- # `batch_min_value` and `batch_max_value` keywords. However the respective
- # column names are `min_value` and `max_value`. Hence the matcher cannot
- # be used in this case, as it asserts the wrong attributes.
- expect(subject).to all(have_attributes(expected_range))
- end
- end
- end
-
- describe '#down' do
- before do
- schema_migrate_down!
- end
-
- it "deletes" do
- expect(described_class::MIGRATION).not_to have_scheduled_batched_migration
- end
- end
-end
diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb
index 16cff72db64..b4c20637ce2 100644
--- a/spec/models/ci/build_metadata_spec.rb
+++ b/spec/models/ci/build_metadata_spec.rb
@@ -182,4 +182,26 @@ RSpec.describe Ci::BuildMetadata do
end
end
end
+
+ describe 'routing table switch' do
+ context 'with ff disabled' do
+ before do
+ stub_feature_flags(ci_partitioning_use_ci_builds_metadata_routing_table: false)
+ end
+
+ it 'uses the legacy table' do
+ expect(described_class.table_name).to eq('ci_builds_metadata')
+ end
+ end
+
+ context 'with ff enabled' do
+ before do
+ stub_feature_flags(ci_partitioning_use_ci_builds_metadata_routing_table: true)
+ end
+
+ it 'uses the routing table' do
+ expect(described_class.table_name).to eq('p_ci_builds_metadata')
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/ci/partitionable/switch_spec.rb b/spec/models/concerns/ci/partitionable/switch_spec.rb
new file mode 100644
index 00000000000..09005489268
--- /dev/null
+++ b/spec/models/concerns/ci/partitionable/switch_spec.rb
@@ -0,0 +1,294 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::Partitionable::Switch, :aggregate_failures do
+ let(:model) do
+ Class.new(Ci::ApplicationRecord) do
+ self.primary_key = :id
+ self.table_name = :_test_ci_jobs_metadata
+ self.sequence_name = :_test_ci_jobs_metadata_id_seq
+
+ def self.name
+ 'TestSwitchJobMetadata'
+ end
+ end
+ end
+
+ let(:table_rollout_flag) { :ci_partitioning_use_test_routing_table }
+
+ let(:partitioned_model) { model::Partitioned }
+
+ let(:jobs_model) do
+ Class.new(Ci::ApplicationRecord) do
+ self.primary_key = :id
+ self.table_name = :_test_ci_jobs
+
+ def self.name
+ 'TestSwitchJob'
+ end
+ end
+ end
+
+ before do
+ allow(ActiveSupport::DescendantsTracker).to receive(:store_inherited)
+
+ create_tables(<<~SQL)
+ CREATE TABLE _test_ci_jobs_metadata(
+ id serial NOT NULL PRIMARY KEY,
+ job_id int,
+ partition_id int NOT NULL DEFAULT 1,
+ expanded_environment_name text);
+
+ CREATE TABLE _test_p_ci_jobs_metadata (
+ LIKE _test_ci_jobs_metadata INCLUDING DEFAULTS
+ ) PARTITION BY LIST(partition_id);
+
+ ALTER TABLE _test_p_ci_jobs_metadata
+ ADD CONSTRAINT _test_p_ci_jobs_metadata_id_partition_id
+ UNIQUE (id, partition_id);
+
+ ALTER TABLE _test_p_ci_jobs_metadata
+ ATTACH PARTITION _test_ci_jobs_metadata FOR VALUES IN (1);
+
+ CREATE TABLE _test_ci_jobs(id serial NOT NULL PRIMARY KEY);
+ SQL
+
+ stub_const('Ci::Partitionable::Testing::PARTITIONABLE_MODELS', [model.name])
+
+ model.include(Ci::Partitionable)
+
+ model.partitionable scope: ->(r) { 1 },
+ through: { table: :_test_p_ci_jobs_metadata, flag: table_rollout_flag }
+
+ model.belongs_to :job, anonymous_class: jobs_model
+
+ jobs_model.has_one :metadata, anonymous_class: model,
+ foreign_key: :job_id, inverse_of: :job,
+ dependent: :destroy
+
+ allow(Feature::Definition).to receive(:get).and_call_original
+ allow(Feature::Definition).to receive(:get).with(table_rollout_flag)
+ .and_return(
+ Feature::Definition.new("development/#{table_rollout_flag}.yml",
+ { type: 'development', name: table_rollout_flag }
+ )
+ )
+ end
+
+ it { expect(model).not_to be_routing_class }
+
+ it { expect(partitioned_model).to be_routing_class }
+
+ it { expect(partitioned_model.table_name).to eq('_test_p_ci_jobs_metadata') }
+
+ it { expect(partitioned_model.quoted_table_name).to eq('"_test_p_ci_jobs_metadata"') }
+
+ it { expect(partitioned_model.arel_table.name).to eq('_test_p_ci_jobs_metadata') }
+
+ it { expect(partitioned_model.sequence_name).to eq('_test_ci_jobs_metadata_id_seq') }
+
+ context 'when switching the tables' do
+ before do
+ stub_feature_flags(table_rollout_flag => false)
+ end
+
+ %i[table_name quoted_table_name arel_table predicate_builder].each do |name|
+ it "switches #{name} to routing table and rollbacks" do
+ old_value = model.public_send(name)
+ routing_value = partitioned_model.public_send(name)
+
+ expect(old_value).not_to eq(routing_value)
+
+ expect { stub_feature_flags(table_rollout_flag => true) }
+ .to change(model, name).from(old_value).to(routing_value)
+
+ expect { stub_feature_flags(table_rollout_flag => false) }
+ .to change(model, name).from(routing_value).to(old_value)
+ end
+ end
+
+ it 'can switch aggregate methods' do
+ rollout_and_rollback_flag(
+ -> { expect(sql { model.count }).to all match(/FROM "_test_ci_jobs_metadata"/) },
+ -> { expect(sql { model.count }).to all match(/FROM "_test_p_ci_jobs_metadata"/) }
+ )
+ end
+
+ it 'can switch reads' do
+ rollout_and_rollback_flag(
+ -> { expect(sql { model.last }).to all match(/FROM "_test_ci_jobs_metadata"/) },
+ -> { expect(sql { model.last }).to all match(/FROM "_test_p_ci_jobs_metadata"/) }
+ )
+ end
+
+ it 'can switch inserts' do
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /INSERT/) { model.create! })
+ .to all match(/INSERT INTO "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /INSERT/) { model.create! })
+ .to all match(/INSERT INTO "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'can switch deletes' do
+ 3.times { model.create! }
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /DELETE/) { model.last.destroy! })
+ .to all match(/DELETE FROM "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /DELETE/) { model.last.destroy! })
+ .to all match(/DELETE FROM "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ context 'with associations' do
+ let(:job) { jobs_model.create! }
+
+ it 'reads' do
+ model.create!(job_id: job.id)
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /jobs_metadata/) { jobs_model.find(job.id).metadata })
+ .to all match(/FROM "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /jobs_metadata/) { jobs_model.find(job.id).metadata })
+ .to all match(/FROM "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'writes' do
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /INSERT .* jobs_metadata/) { jobs_model.find(job.id).create_metadata! })
+ .to all match(/INSERT INTO "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /INSERT .* jobs_metadata/) { jobs_model.find(job.id).create_metadata! })
+ .to all match(/INSERT INTO "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'deletes' do
+ 3.times do
+ job = jobs_model.create!
+ job.create_metadata!
+ end
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /DELETE .* jobs_metadata/) { jobs_model.last.destroy! })
+ .to all match(/DELETE FROM "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /DELETE .* jobs_metadata/) { jobs_model.last.destroy! })
+ .to all match(/DELETE FROM "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'can switch joins from jobs' do
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql { jobs_model.joins(:metadata).last })
+ .to all match(/INNER JOIN "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql { jobs_model.joins(:metadata).last })
+ .to all match(/INNER JOIN "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ it 'can switch joins from metadata' do
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql { model.joins(:job).last })
+ .to all match(/FROM "_test_ci_jobs_metadata" INNER JOIN "_test_ci_jobs"/)
+ },
+ -> {
+ expect(sql { model.joins(:job).last })
+ .to all match(/FROM "_test_p_ci_jobs_metadata" INNER JOIN "_test_ci_jobs"/)
+ }
+ )
+ end
+
+ it 'preloads' do
+ job = jobs_model.create!
+ job.create_metadata!
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /jobs_metadata/) { jobs_model.preload(:metadata).last })
+ .to all match(/FROM "_test_ci_jobs_metadata"/)
+ },
+ -> {
+ expect(sql(filter: /jobs_metadata/) { jobs_model.preload(:metadata).last })
+ .to all match(/FROM "_test_p_ci_jobs_metadata"/)
+ }
+ )
+ end
+
+ context 'with nested attributes' do
+ before do
+ jobs_model.accepts_nested_attributes_for :metadata
+ end
+
+ it 'writes' do
+ attrs = { metadata_attributes: { expanded_environment_name: 'test_env_name' } }
+
+ rollout_and_rollback_flag(
+ -> {
+ expect(sql(filter: /INSERT .* jobs_metadata/) { jobs_model.create!(attrs) })
+ .to all match(/INSERT INTO "_test_ci_jobs_metadata" .* 'test_env_name'/)
+ },
+ -> {
+ expect(sql(filter: /INSERT .* jobs_metadata/) { jobs_model.create!(attrs) })
+ .to all match(/INSERT INTO "_test_p_ci_jobs_metadata" .* 'test_env_name'/)
+ }
+ )
+ end
+ end
+ end
+ end
+
+ def rollout_and_rollback_flag(old, new)
+ # Load class and SQL statements cache
+ old.call
+
+ stub_feature_flags(table_rollout_flag => true)
+
+ # Test switch
+ new.call
+
+ stub_feature_flags(table_rollout_flag => false)
+
+ # Test that it can switch back in the same process
+ old.call
+ end
+
+ def create_tables(table_sql)
+ Ci::ApplicationRecord.connection.execute(table_sql)
+ end
+
+ def sql(filter: nil, &block)
+ result = ActiveRecord::QueryRecorder.new(&block)
+ result = result.log
+
+ return result unless filter
+
+ result.select { |statement| statement.match?(filter) }
+ end
+end
diff --git a/spec/models/concerns/ci/partitionable_spec.rb b/spec/models/concerns/ci/partitionable_spec.rb
index d53501ccc3d..f3d33c971c7 100644
--- a/spec/models/concerns/ci/partitionable_spec.rb
+++ b/spec/models/concerns/ci/partitionable_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
RSpec.describe Ci::Partitionable do
- describe 'partitionable models inclusion' do
- let(:ci_model) { Class.new(Ci::ApplicationRecord) }
+ let(:ci_model) { Class.new(Ci::ApplicationRecord) }
+ describe 'partitionable models inclusion' do
subject { ci_model.include(described_class) }
it 'raises an exception' do
@@ -23,4 +23,21 @@ RSpec.describe Ci::Partitionable do
end
end
end
+
+ context 'with through options' do
+ before do
+ allow(ActiveSupport::DescendantsTracker).to receive(:store_inherited)
+ stub_const("#{described_class}::Testing::PARTITIONABLE_MODELS", [ci_model.name])
+
+ ci_model.include(described_class)
+ ci_model.partitionable scope: ->(r) { 1 },
+ through: { table: :_test_table_name, flag: :some_flag }
+ end
+
+ it { expect(ci_model.routing_table_name).to eq(:_test_table_name) }
+
+ it { expect(ci_model.routing_table_name_flag).to eq(:some_flag) }
+
+ it { expect(ci_model.ancestors).to include(described_class::Switch) }
+ end
end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 8842a36f40a..43ec0559eb3 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -1055,6 +1055,22 @@ RSpec.describe Issuable do
end
end
+ describe '#supports_confidentiality?' do
+ where(:issuable_type, :supports_confidentiality) do
+ :issue | true
+ :incident | true
+ :merge_request | false
+ end
+
+ with_them do
+ let(:issuable) { build_stubbed(issuable_type) }
+
+ subject { issuable.supports_confidentiality? }
+
+ it { is_expected.to eq(supports_confidentiality) }
+ end
+ end
+
describe '#severity' do
subject { issuable.severity }
diff --git a/spec/models/concerns/pg_full_text_searchable_spec.rb b/spec/models/concerns/pg_full_text_searchable_spec.rb
index 3e42a3504ac..5a693f084e6 100644
--- a/spec/models/concerns/pg_full_text_searchable_spec.rb
+++ b/spec/models/concerns/pg_full_text_searchable_spec.rb
@@ -76,7 +76,7 @@ RSpec.describe PgFullTextSearchable do
end
describe '.pg_full_text_search' do
- let(:english) { model_class.create!(project: project, title: 'title', description: 'something english') }
+ let(:english) { model_class.create!(project: project, title: 'title', description: 'something description english') }
let(:with_accent) { model_class.create!(project: project, title: 'Jürgen', description: 'Ærøskøbing') }
let(:japanese) { model_class.create!(project: project, title: '日本語 title', description: 'another english description') }
@@ -90,8 +90,19 @@ RSpec.describe PgFullTextSearchable do
expect(model_class.pg_full_text_search('title english')).to contain_exactly(english, japanese)
end
+ it 'searches specified columns only' do
+ matching_object = model_class.create!(project: project, title: 'english', description: 'some description')
+ matching_object.update_search_data!
+
+ expect(model_class.pg_full_text_search('english', matched_columns: %w(title))).to contain_exactly(matching_object)
+ end
+
+ it 'uses prefix matching' do
+ expect(model_class.pg_full_text_search('tit eng')).to contain_exactly(english, japanese)
+ end
+
it 'searches for exact term with quotes' do
- expect(model_class.pg_full_text_search('"something english"')).to contain_exactly(english)
+ expect(model_class.pg_full_text_search('"description english"')).to contain_exactly(english)
end
it 'ignores accents' do
@@ -113,6 +124,16 @@ RSpec.describe PgFullTextSearchable do
expect(model_class.pg_full_text_search('gopher://gitlab.com/gitlab-org/gitlab')).to contain_exactly(with_url)
end
end
+
+ context 'when text has numbers preceded by a dash' do
+ let(:with_dash) { model_class.create!(project: project, title: 'issue with dash', description: 'ABC-123') }
+
+ it 'allows searching by numbers only' do
+ with_dash.update_search_data!
+
+ expect(model_class.pg_full_text_search('123')).to contain_exactly(with_dash)
+ end
+ end
end
describe '#update_search_data!' do
diff --git a/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb b/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb
index a3c9ae8916e..6e60889f737 100644
--- a/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb
+++ b/spec/rubocop/cop/gitlab/mark_used_feature_flags_spec.rb
@@ -194,6 +194,10 @@ RSpec.describe RuboCop::Cop::Gitlab::MarkUsedFeatureFlags do
include_examples 'sets flag as used', 'FEATURE_FLAG = :foo', 'foo'
end
+ describe 'ROUTING_FEATURE_FLAG = :foo' do
+ include_examples 'sets flag as used', 'ROUTING_FEATURE_FLAG = :foo', 'foo'
+ end
+
describe 'Worker `data_consistency` method' do
include_examples 'sets flag as used', 'data_consistency :delayed, feature_flag: :foo', 'foo'
include_examples 'does not set any flags as used', 'data_consistency :delayed'
diff --git a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
index f62c9c00006..8b3a344a841 100644
--- a/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
+++ b/spec/support/shared_examples/finders/issues_finder_shared_examples.rb
@@ -585,7 +585,7 @@ RSpec.shared_examples 'issues or work items finder' do |factory, execute_context
end
context 'when full-text search is disabled' do
- let(:search_term) { 'somet' }
+ let(:search_term) { 'ometh' }
before do
stub_feature_flags(issues_full_text_search: false)
diff --git a/spec/workers/namespaces/root_statistics_worker_spec.rb b/spec/workers/namespaces/root_statistics_worker_spec.rb
index 7b774da0bdc..f28a0815025 100644
--- a/spec/workers/namespaces/root_statistics_worker_spec.rb
+++ b/spec/workers/namespaces/root_statistics_worker_spec.rb
@@ -89,4 +89,12 @@ RSpec.describe Namespaces::RootStatisticsWorker, '#perform' do
.not_to change { Namespace::AggregationSchedule.count }
end
end
+
+ it 'has the `until_executed` deduplicate strategy' do
+ expect(described_class.get_deduplicate_strategy).to eq(:until_executed)
+ end
+
+ it 'has an option to reschedule once if deduplicated' do
+ expect(described_class.get_deduplication_options).to include({ if_deduplicated: :reschedule_once })
+ end
end
diff --git a/test.html b/test.html
new file mode 100644
index 00000000000..9c508e519c5
--- /dev/null
+++ b/test.html
@@ -0,0 +1,439 @@
+<!DOCTYPE html>
+<html class="" lang="en">
+<head prefix="og: http://ogp.me/ns#">
+<meta charset="utf-8">
+<title>term · Search · GitLab</title>
+<link rel="preload" href="/assets/application_utilities-08432cf9120e4223aaf60df81aa67b3a688203198905c5ee86fc3c7e2133dd8b.css" as="style" type="text/css" nonce="HoEnOW5qBBDwC4FvyjlrqA==">
+<link rel="preload" href="/assets/application-83d43ac2aff2d407da96a5fd6a410aa784c1ada12f2e8e02b328764d02324432.css" as="style" type="text/css" nonce="HoEnOW5qBBDwC4FvyjlrqA==">
+<link rel="preload" href="/assets/highlight/themes/white-557ba28a0d83a177dd5f4cdaa59e208f666e026683c63c59f494ece39cb34f98.css" as="style" type="text/css" nonce="HoEnOW5qBBDwC4FvyjlrqA==">
+<link crossorigin="" href="https://localhost" rel="preconnect">
+
+<meta content="IE=edge" http-equiv="X-UA-Compatible">
+
+
+<link rel="shortcut icon" type="image/png" href="/assets/favicon-72a2cad5025aa931d6ea56c3201d1f18e68a8cd39788c7c80d5b2b82aa5143ef.png" id="favicon" data-original-href="/assets/favicon-72a2cad5025aa931d6ea56c3201d1f18e68a8cd39788c7c80d5b2b82aa5143ef.png" />
+<style>
+@keyframes blinking-dot{0%{opacity:1}25%{opacity:0.4}75%{opacity:0.4}100%{opacity:1}}@keyframes blinking-scroll-button{0%{opacity:0.2}50%{opacity:1}100%{opacity:0.2}}@keyframes gl-spinner-rotate{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}body.ui-indigo{--gl-theme-accent: #6666c4}body.ui-indigo .navbar-gitlab{background-color:#292961}body.ui-indigo .navbar-gitlab .navbar-collapse{color:#d1d1f0}body.ui-indigo .navbar-gitlab .container-fluid .navbar-toggler{border-left:1px solid #6868b9;color:#d1d1f0}body.ui-indigo .navbar-gitlab .navbar-sub-nav>li>a:hover,body.ui-indigo .navbar-gitlab .navbar-sub-nav>li>a:focus,body.ui-indigo .navbar-gitlab .navbar-sub-nav>li>button:hover,body.ui-indigo .navbar-gitlab .navbar-sub-nav>li>button:focus,body.ui-indigo .navbar-gitlab .navbar-nav>li>a:hover,body.ui-indigo .navbar-gitlab .navbar-nav>li>a:focus,body.ui-indigo .navbar-gitlab .navbar-nav>li>button:hover,body.ui-indigo .navbar-gitlab .navbar-nav>li>button:focus{background-color:rgba(209,209,240,0.2)}body.ui-indigo .navbar-gitlab .navbar-sub-nav>li.active>a,body.ui-indigo .navbar-gitlab .navbar-sub-nav>li.active>button,body.ui-indigo .navbar-gitlab .navbar-sub-nav>li.dropdown.show>a,body.ui-indigo .navbar-gitlab .navbar-sub-nav>li.dropdown.show>button,body.ui-indigo .navbar-gitlab .navbar-nav>li.active>a,body.ui-indigo .navbar-gitlab .navbar-nav>li.active>button,body.ui-indigo .navbar-gitlab .navbar-nav>li.dropdown.show>a,body.ui-indigo .navbar-gitlab .navbar-nav>li.dropdown.show>button{color:#292961;background-color:#fff}body.ui-indigo .navbar-gitlab .navbar-sub-nav>li.line-separator,body.ui-indigo .navbar-gitlab .navbar-nav>li.line-separator{border-left:1px solid rgba(209,209,240,0.2)}body.ui-indigo .navbar-gitlab .navbar-sub-nav{color:#d1d1f0}body.ui-indigo .navbar-gitlab .nav>li{color:#d1d1f0}body.ui-indigo .navbar-gitlab .nav>li.header-search-new{color:#303030}body.ui-indigo .navbar-gitlab .nav>li>a .notification-dot{border:2px solid #292961}body.ui-indigo .navbar-gitlab .nav>li>a.header-help-dropdown-toggle .notification-dot{background-color:#d1d1f0}body.ui-indigo .navbar-gitlab .nav>li>a.header-user-dropdown-toggle .header-user-avatar{border-color:#d1d1f0}@media (min-width: 576px){body.ui-indigo .navbar-gitlab .nav>li>a:hover,body.ui-indigo .navbar-gitlab .nav>li>a:focus{background-color:rgba(209,209,240,0.2)}}body.ui-indigo .navbar-gitlab .nav>li>a:hover svg,body.ui-indigo .navbar-gitlab .nav>li>a:focus svg{fill:currentColor}body.ui-indigo .navbar-gitlab .nav>li>a:hover .notification-dot,body.ui-indigo .navbar-gitlab .nav>li>a:focus .notification-dot{will-change:border-color, background-color;border-color:#4a4a82}body.ui-indigo .navbar-gitlab .nav>li>a.header-help-dropdown-toggle:hover .notification-dot,body.ui-indigo .navbar-gitlab .nav>li>a.header-help-dropdown-toggle:focus .notification-dot{background-color:#fff}body.ui-indigo .navbar-gitlab .nav>li.active>a,body.ui-indigo .navbar-gitlab .nav>li.dropdown.show>a{color:#292961;background-color:#fff}body.ui-indigo .navbar-gitlab .nav>li.active>a:hover svg,body.ui-indigo .navbar-gitlab .nav>li.dropdown.show>a:hover svg{fill:#292961}body.ui-indigo .navbar-gitlab .nav>li.active>a .notification-dot,body.ui-indigo .navbar-gitlab .nav>li.dropdown.show>a .notification-dot{border-color:#fff}body.ui-indigo .navbar-gitlab .nav>li.active>a.header-help-dropdown-toggle .notification-dot,body.ui-indigo .navbar-gitlab .nav>li.dropdown.show>a.header-help-dropdown-toggle .notification-dot{background-color:#292961}body.ui-indigo .navbar-gitlab .nav>li .impersonated-user svg,body.ui-indigo .navbar-gitlab .nav>li .impersonated-user:hover svg{fill:#292961}body.ui-indigo .navbar .title>a:hover,body.ui-indigo .navbar .title>a:focus{background-color:rgba(209,209,240,0.2)}body.ui-indigo .header-search{background-color:rgba(209,209,240,0.2) !important;border-radius:4px}body.ui-indigo .header-search:hover{background-color:rgba(209,209,240,0.3) !important}body.ui-indigo .header-search svg.gl-search-box-by-type-search-icon{color:rgba(209,209,240,0.8)}body.ui-indigo .header-search input{background-color:transparent;color:rgba(209,209,240,0.8);box-shadow:inset 0 0 0 1px rgba(209,209,240,0.4)}body.ui-indigo .header-search input::placeholder{color:rgba(209,209,240,0.8)}body.ui-indigo .header-search input:focus::placeholder,body.ui-indigo .header-search input:active::placeholder{color:#868686}body.ui-indigo .header-search .keyboard-shortcut-helper{color:#d1d1f0;background-color:rgba(209,209,240,0.2)}body.ui-indigo .search form{background-color:rgba(209,209,240,0.2)}body.ui-indigo .search form:hover{background-color:rgba(209,209,240,0.3)}body.ui-indigo .search .search-input::placeholder{color:rgba(209,209,240,0.8)}body.ui-indigo .search .search-input-wrap .search-icon,body.ui-indigo .search .search-input-wrap .clear-icon{fill:rgba(209,209,240,0.8)}body.ui-indigo .search.search-active form{background-color:#fff}body.ui-indigo .search.search-active .search-input-wrap .search-icon{fill:rgba(209,209,240,0.8)}body.ui-indigo .nav-sidebar li.active>a{color:#303030}body.ui-indigo .nav-sidebar .fly-out-top-item a,body.ui-indigo .nav-sidebar .fly-out-top-item a:hover,body.ui-indigo .nav-sidebar .fly-out-top-item.active a,body.ui-indigo .nav-sidebar .fly-out-top-item .fly-out-top-item-container{background-color:var(--gray-100, #f0f0f0);color:var(--gray-900, #303030)}body.ui-indigo .branch-header-title{color:#4b4ba3}body.ui-indigo .ide-sidebar-link.active{color:#4b4ba3}body.ui-indigo .ide-sidebar-link.active.is-right{box-shadow:inset -3px 0 #4b4ba3}
+
+*,*::before,*::after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15}aside,header{display:block}body{margin:0;font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#303030;text-align:left;background-color:#fff}ul{margin-top:0;margin-bottom:1rem}ul ul{margin-bottom:0}strong{font-weight:bolder}a{color:#1f75cb;text-decoration:none;background-color:transparent}a:not([href]):not([class]){color:inherit;text-decoration:none}kbd{font-family:"Menlo", "DejaVu Sans Mono", "Liberation Mono", "Consolas", "Ubuntu Mono", "Courier New", "andale mono", "lucida console", monospace;font-size:1em}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}button{border-radius:0}input,button{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button{text-transform:none}[role="button"]{cursor:pointer}button:not(:disabled),[type="button"]:not(:disabled){cursor:pointer}button::-moz-focus-inner,[type="button"]::-moz-focus-inner{padding:0;border-style:none}[type="search"]{outline-offset:-2px}.list-unstyled{padding-left:0;list-style:none}kbd{padding:0.2rem 0.4rem;font-size:90%;color:#fff;background-color:#303030;border-radius:0.2rem}kbd kbd{padding:0;font-size:100%;font-weight:600}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.form-control{display:block;width:100%;height:34px;padding:0.375rem 0.75rem;font-size:0.875rem;font-weight:400;line-height:1.5;color:#303030;background-color:#fff;background-clip:padding-box;border:1px solid #868686;border-radius:0.25rem}.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #303030}.form-control::placeholder{color:#5e5e5e;opacity:1}.form-control:disabled{background-color:#fafafa;opacity:1}.form-inline{display:flex;flex-flow:row wrap;align-items:center}@media (min-width: 576px){.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}}.btn{display:inline-block;font-weight:400;color:#303030;text-align:center;vertical-align:middle;-webkit-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:0.375rem 0.75rem;font-size:1rem;line-height:20px;border-radius:0.25rem}.btn:disabled{opacity:0.65}.btn:not(:disabled):not(.disabled){cursor:pointer}.collapse:not(.show){display:none}.dropdown{position:relative}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:0.5rem 0;margin:0.125rem 0 0;font-size:1rem;color:#303030;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.15);border-radius:0.25rem}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding:0.25rem 0.5rem}.navbar .container-fluid{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:0.25rem 0.75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:0.25rem}@media (max-width: 575.98px){.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width: 576px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm>.container-fluid{flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:flex !important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}.badge{display:inline-block;padding:0.25em 0.4em;font-size:75%;font-weight:600;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:0.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:0.6em;padding-left:0.6em;border-radius:10rem}.badge-success{color:#fff;background-color:#108548}.badge-info{color:#fff;background-color:#1f75cb}.badge-warning{color:#fff;background-color:#ab6100}.rounded-circle{border-radius:50% !important}.d-none{display:none !important}.d-block{display:block !important}@media (min-width: 576px){.d-sm-none{display:none !important}.d-sm-inline-block{display:inline-block !important}}@media (min-width: 768px){.d-md-block{display:block !important}}@media (min-width: 992px){.d-lg-none{display:none !important}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0}.gl-avatar{border-width:1px;border-style:solid;border-color:rgba(0,0,0,0.08);overflow:hidden;flex-shrink:0}.gl-avatar-s24{width:1.5rem;height:1.5rem;font-size:0.75rem;line-height:1rem;border-radius:0.25rem}.gl-avatar-circle{border-radius:50%}.gl-badge{display:inline-flex;align-items:center;font-size:0.75rem;font-weight:400;line-height:1rem;padding-top:0.25rem;padding-bottom:0.25rem;padding-left:0.5rem;padding-right:0.5rem}.gl-badge.sm{padding-top:0;padding-bottom:0}.gl-badge.badge-info{background-color:#cbe2f9;color:#0b5cad}a.gl-badge.badge-info.active,a.gl-badge.badge-info:active{color:#033464;background-color:#9dc7f1}a.gl-badge.badge-info:active{box-shadow:0 0 0 1px #fff, 0 0 0 3px #428fdc;outline:none}.gl-badge.badge-success{background-color:#c3e6cd;color:#24663b}a.gl-badge.badge-success.active,a.gl-badge.badge-success:active{color:#0a4020;background-color:#91d4a8}a.gl-badge.badge-success:active{box-shadow:0 0 0 1px #fff, 0 0 0 3px #428fdc;outline:none}.gl-badge.badge-warning{background-color:#f5d9a8;color:#8f4700}a.gl-badge.badge-warning.active,a.gl-badge.badge-warning:active{color:#5c2900;background-color:#e9be74}a.gl-badge.badge-warning:active{box-shadow:0 0 0 1px #fff, 0 0 0 3px #428fdc;outline:none}.gl-button .gl-badge{top:0}.gl-form-input,.gl-form-input.form-control{background-color:#fff;font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans", Ubuntu, Cantarell, "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";font-size:0.875rem;line-height:1rem;padding-top:0.5rem;padding-bottom:0.5rem;padding-left:0.75rem;padding-right:0.75rem;height:auto;color:#303030;box-shadow:inset 0 0 0 1px #868686;border-style:none;-webkit-appearance:none;appearance:none;-moz-appearance:none}.gl-form-input:disabled,.gl-form-input:not(.form-control-plaintext):not([type="color"]):read-only,.gl-form-input.form-control:disabled,.gl-form-input.form-control:not(.form-control-plaintext):not([type="color"]):read-only{background-color:#f5f5f5;box-shadow:inset 0 0 0 1px #dbdbdb}.gl-form-input:disabled,.gl-form-input.form-control:disabled{cursor:not-allowed;color:#666}.gl-form-input::placeholder,.gl-form-input.form-control::placeholder{color:#868686}.gl-icon{fill:currentColor}.gl-icon.s12{width:12px;height:12px}.gl-icon.s16{width:16px;height:16px}.gl-icon.s32{width:32px;height:32px}.gl-link{font-size:0.875rem;color:#1f75cb}.gl-link:active{color:#0b5cad}.gl-link:active{text-decoration:underline;outline:2px solid #428fdc;outline-offset:2px}.gl-button{display:inline-flex}.gl-button:not(.btn-link):active{text-decoration:none}.gl-button.gl-button{border-width:0;padding-top:0.5rem;padding-bottom:0.5rem;padding-left:0.75rem;padding-right:0.75rem;background-color:transparent;line-height:1rem;color:#303030;fill:currentColor;box-shadow:inset 0 0 0 1px #bfbfbf;justify-content:center;align-items:center;font-size:0.875rem;border-radius:0.25rem}.gl-button.gl-button.btn-default{background-color:#fff}.gl-button.gl-button.btn-default:active,.gl-button.gl-button.btn-default.active{box-shadow:inset 0 0 0 1px #5e5e5e, 0 0 0 1px #fff, 0 0 0 3px #428fdc;outline:none;background-color:#dbdbdb}.gl-button.gl-button.btn-default:active .gl-icon,.gl-button.gl-button.btn-default.active .gl-icon{color:#303030}.gl-button.gl-button.btn-default .gl-icon{color:#666}.gl-search-box-by-type-search-icon{margin:0.5rem;color:#666;width:1rem;position:absolute}.gl-search-box-by-type{display:flex;position:relative}.gl-search-box-by-type-input,.gl-search-box-by-type-input.gl-form-input{height:2rem;padding-right:2rem;padding-left:1.75rem}body{font-size:0.875rem}button,html [type="button"],[role="button"]{cursor:pointer}strong{font-weight:bold}svg{vertical-align:baseline}.form-control,.search form{font-size:0.875rem}.hidden{display:none !important;visibility:hidden !important}.hide{display:none}.badge:not(.gl-badge){padding:4px 5px;font-size:12px;font-style:normal;font-weight:400;display:inline-block}.divider{height:0;margin:4px 0;overflow:hidden;border-top:1px solid #dbdbdb}.toggle-sidebar-button .collapse-text,.toggle-sidebar-button .icon-chevron-double-lg-left{color:#666}html{overflow-y:scroll}.btn{border-radius:4px;font-size:0.875rem;font-weight:400;padding:6px 10px;background-color:#fff;border-color:#dbdbdb;color:#303030;color:#303030;white-space:nowrap}.btn:active{background-color:#f0f0f0;box-shadow:none}.btn:active,.btn.active{background-color:#eaeaea;border-color:#e3e3e3;color:#303030}.btn svg{height:15px;width:15px}.btn svg:not(:last-child){margin-right:5px}.badge.badge-pill:not(.gl-badge){font-weight:400;background-color:rgba(0,0,0,0.07);color:#525252;vertical-align:baseline}.gl-font-sm{font-size:12px}.dropdown{position:relative}.dropdown-menu-toggle:active{box-shadow:0 0 0 1px #fff, 0 0 0 3px #428fdc;outline:none}.search-input-container .dropdown-menu{margin-top:11px}.dropdown-menu-toggle{padding:6px 8px 6px 10px;background-color:#fff;color:#303030;font-size:14px;text-align:left;border:1px solid #dbdbdb;border-radius:0.25rem;white-space:nowrap}.dropdown-menu-toggle.no-outline{outline:0}.dropdown-menu-toggle.dropdown-menu-toggle{justify-content:flex-start;overflow:hidden;padding-right:25px;position:relative;text-overflow:ellipsis;width:160px}.dropdown-menu{display:none;position:absolute;width:auto;top:100%;z-index:300;min-width:240px;max-width:500px;margin-top:4px;margin-bottom:24px;font-size:0.875rem;font-weight:400;padding:8px 0;background-color:#fff;border:1px solid #dbdbdb;border-radius:0.25rem;box-shadow:0 2px 4px rgba(0,0,0,0.1)}.dropdown-menu ul{margin:0;padding:0}.dropdown-menu li{display:block;text-align:left;list-style:none}.dropdown-menu li>a,.dropdown-menu li button{background:transparent;border:0;border-radius:0;box-shadow:none;display:block;font-weight:400;position:relative;padding:8px 12px;color:#303030;line-height:16px;white-space:normal;overflow:hidden;text-align:left;width:100%}.dropdown-menu li>a:active,.dropdown-menu li button:active{background-color:#eee;color:#303030;outline:0;text-decoration:none}.dropdown-menu li>a:active,.dropdown-menu li button:active{box-shadow:inset 0 0 0 2px #428fdc, inset 0 0 0 3px #fff, inset 0 0 0 1px #fff;outline:none}.dropdown-menu .divider{height:1px;margin:0.25rem 0;padding:0;background-color:#dbdbdb}.dropdown-menu .badge.badge-pill+span:not(.badge):not(.badge-pill){margin-right:40px}@media (max-width: 575.98px){.navbar-gitlab li.dropdown{position:static}.navbar-gitlab li.dropdown.user-counter{margin-left:8px !important}.navbar-gitlab li.dropdown.user-counter>a{padding:0 4px !important}header.navbar-gitlab .dropdown .dropdown-menu{width:100%;min-width:100%}}@media (max-width: 767.98px){.dropdown-menu-toggle.dropdown-menu-toggle{width:100%}}input{border-radius:0.25rem;color:#303030;background-color:#fff}.form-control{border-radius:4px;padding:6px 10px}.form-control::placeholder{color:#868686}kbd{display:inline-block;padding:3px 5px;font-size:0.6875rem;line-height:10px;color:var(--gray-700, #525252);vertical-align:middle;background-color:var(--gray-10, #f5f5f5);border-width:1px;border-style:solid;border-color:var(--gray-100, #dbdbdb) var(--gray-100, #dbdbdb) var(--gray-200, #bfbfbf);border-image:none;border-radius:3px;box-shadow:0 -1px 0 var(--gray-200, #bfbfbf) inset}.navbar-gitlab{padding:0 16px;z-index:1000;margin-bottom:0;min-height:var(--header-height, 48px);border:0;position:fixed;top:0;left:0;right:0;border-radius:0}.navbar-gitlab .close-icon{display:none}.navbar-gitlab .header-content{width:100%;display:flex;justify-content:space-between;position:relative;min-height:var(--header-height, 48px);padding-left:0}.navbar-gitlab .header-content .title{padding-right:0;color:currentColor;display:flex;position:relative;margin:0;font-size:18px;vertical-align:top;white-space:nowrap}.navbar-gitlab .header-content .title img{height:24px}.navbar-gitlab .header-content .title a:not(.canary-badge){display:flex;align-items:center;padding:2px 8px;margin:4px 2px 4px -8px;border-radius:4px}.navbar-gitlab .header-content .title a:not(.canary-badge):active{box-shadow:0 0 0 1px rgba(0,0,0,0.6),0 0 0 3px #63a6e9;outline:none}.navbar-gitlab .header-content .navbar-collapse>ul.nav>li:not(.d-none){margin:0 2px}.navbar-gitlab .navbar-collapse{flex:0 0 auto;border-top:0;padding:0}@media (max-width: 575.98px){.navbar-gitlab .navbar-collapse{flex:1 1 auto}}.navbar-gitlab .navbar-collapse .nav{flex-wrap:nowrap}@media (max-width: 575.98px){.navbar-gitlab .navbar-collapse .nav>li:not(.d-none) a{margin-left:0}}.navbar-gitlab .container-fluid{padding:0}.navbar-gitlab .container-fluid .user-counter svg{margin-right:3px}.navbar-gitlab .container-fluid .navbar-toggler{position:relative;right:-10px;border-radius:0;min-width:45px;padding:0;margin:8px 8px 8px 0;font-size:14px;text-align:center;color:currentColor}.navbar-gitlab .container-fluid .navbar-toggler.active{color:currentColor;background-color:transparent}@media (max-width: 575.98px){.navbar-gitlab .container-fluid .navbar-nav{display:flex;padding-right:10px;flex-direction:row}}.navbar-gitlab .container-fluid .navbar-nav li .badge.badge-pill:not(.gl-badge){box-shadow:none;font-weight:600}@media (max-width: 575.98px){.navbar-gitlab .container-fluid .nav>li.header-user{padding-left:10px}}.navbar-gitlab .container-fluid .nav>li>a{will-change:color;margin:4px 0;padding:6px 8px;height:32px}@media (max-width: 575.98px){.navbar-gitlab .container-fluid .nav>li>a{padding:0}}.navbar-gitlab .container-fluid .nav>li>a.header-user-dropdown-toggle{margin-left:2px}.navbar-gitlab .container-fluid .nav>li>a.header-user-dropdown-toggle .header-user-avatar{margin-right:0}.navbar-gitlab .container-fluid .nav>li .header-new-dropdown-toggle{margin-right:0}.navbar-sub-nav>li>a,.navbar-sub-nav>li>button,.navbar-nav>li>a,.navbar-nav>li>button{display:flex;align-items:center;justify-content:center;padding:6px 8px;margin:4px 2px;font-size:12px;color:currentColor;border-radius:4px;height:32px;font-weight:600}.navbar-sub-nav>li>a:active,.navbar-sub-nav>li>button:active,.navbar-nav>li>a:active,.navbar-nav>li>button:active{box-shadow:0 0 0 1px rgba(0,0,0,0.6),0 0 0 3px #63a6e9;outline:none}.navbar-sub-nav>li .top-nav-toggle,.navbar-sub-nav>li>button,.navbar-nav>li .top-nav-toggle,.navbar-nav>li>button{background:transparent;border:0}.navbar-sub-nav .dropdown-menu,.navbar-nav .dropdown-menu{position:absolute}.navbar-sub-nav{display:flex;align-items:center;height:100%;margin:0 0 0 6px}.caret-down,.btn .caret-down{top:0;height:11px;width:11px;margin-left:4px;fill:currentColor}.header-user .dropdown-menu,.header-new .dropdown-menu{margin-top:4px}.btn-sign-in{background-color:#ebebfa;color:#292961;font-weight:600;line-height:18px;margin:4px 0 4px 2px}@media (max-width: 575.98px){.navbar-gitlab .container-fluid{font-size:18px}.navbar-gitlab .container-fluid .navbar-nav{table-layout:fixed;width:100%;margin:0;text-align:right}.navbar-gitlab .container-fluid .navbar-collapse{margin-left:-8px;margin-right:-10px}.navbar-gitlab .container-fluid .navbar-collapse .nav>li:not(.d-none){flex:1}.header-user-dropdown-toggle{text-align:center}.header-user-avatar{float:none}}.header-user-avatar{float:left;margin-right:5px;border-radius:50%;border:1px solid #f5f5f5}.notification-dot{background-color:#d99530;height:12px;width:12px;pointer-events:none;visibility:hidden;top:3px}.tanuki-logo .tanuki{fill:#e24329}.tanuki-logo .left-cheek,.tanuki-logo .right-cheek{fill:#fc6d26}.tanuki-logo .chin{fill:#fca326}.context-header{position:relative;margin-right:2px;width:256px}.context-header>a,.context-header>button{font-weight:600;display:flex;width:100%;align-items:center;padding:10px 16px 10px 10px;color:#303030;background-color:transparent;border:0;text-align:left}.context-header .avatar-container{flex:0 0 32px;background-color:#fff}.context-header .sidebar-context-title{overflow:hidden;text-overflow:ellipsis;color:#303030}@media (min-width: 768px){.page-with-contextual-sidebar{padding-left:56px}}@media (min-width: 1200px){.page-with-contextual-sidebar{padding-left:256px}}@media (min-width: 768px){.page-with-icon-sidebar{padding-left:56px}}.nav-sidebar{position:fixed;bottom:0;left:0;z-index:600;width:256px;top:var(--header-height, 48px);background-color:#f5f5f5;border-right:1px solid #e9e9e9;transform:translate3d(0, 0, 0)}.nav-sidebar.sidebar-collapsed-desktop{width:56px}.nav-sidebar.sidebar-collapsed-desktop .nav-sidebar-inner-scroll{overflow-x:hidden}.nav-sidebar.sidebar-collapsed-desktop .badge.badge-pill:not(.fly-out-badge),.nav-sidebar.sidebar-collapsed-desktop .nav-item-name,.nav-sidebar.sidebar-collapsed-desktop .collapse-text{border:0;clip:rect(0, 0, 0, 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.nav-sidebar.sidebar-collapsed-desktop .sidebar-top-level-items>li>a{min-height:unset}.nav-sidebar.sidebar-collapsed-desktop .fly-out-top-item:not(.divider){display:block !important}.nav-sidebar.sidebar-collapsed-desktop .avatar-container{margin:0 auto}.nav-sidebar.sidebar-collapsed-desktop li.active:not(.fly-out-top-item)>a{background-color:rgba(41,41,97,0.08)}.nav-sidebar a{text-decoration:none;color:#303030}.nav-sidebar li{white-space:nowrap}.nav-sidebar li .nav-item-name{flex:1;overflow:hidden;text-overflow:ellipsis}.nav-sidebar li>a,.nav-sidebar li>.fly-out-top-item-container{padding-left:0.75rem;padding-right:0.75rem;padding-top:0.5rem;padding-bottom:0.5rem;display:flex;align-items:center;border-radius:0.25rem;width:auto;line-height:1rem;margin:1px 8px}.nav-sidebar li.active>a{font-weight:600}.nav-sidebar li.active:not(.fly-out-top-item)>a:not(.has-sub-items){background-color:rgba(0,0,0,0.08)}.nav-sidebar ul{padding-left:0;list-style:none}@media (max-width: 767.98px){.nav-sidebar{left:-256px}}.nav-sidebar .nav-icon-container{display:flex;margin-right:8px}.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item{display:none}.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item a,.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item.active a,.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item .fly-out-top-item-container{margin-left:0;margin-right:0;padding-left:1rem;padding-right:1rem;cursor:default;pointer-events:none;font-size:0.75rem;margin-top:-0.25rem;margin-bottom:-0.25rem;margin-top:0;position:relative;color:#fff;background:var(--black, #000)}.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item a strong,.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item.active a strong,.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item .fly-out-top-item-container strong{font-weight:400}.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item a::before,.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item.active a::before,.nav-sidebar a:not(.has-sub-items)+.sidebar-sub-level-items .fly-out-top-item .fly-out-top-item-container::before{position:absolute;content:"";display:block;top:50%;left:-0.25rem;margin-top:-0.25rem;width:0;height:0;border-top:0.25rem solid transparent;border-bottom:0.25rem solid transparent;border-right:0.25rem solid #000;border-right-color:var(--black, #000)}@media (min-width: 576px){.nav-sidebar a.has-sub-items+.sidebar-sub-level-items{min-width:150px}}.nav-sidebar a.has-sub-items+.sidebar-sub-level-items .fly-out-top-item{display:none}.nav-sidebar a.has-sub-items+.sidebar-sub-level-items .fly-out-top-item a,.nav-sidebar a.has-sub-items+.sidebar-sub-level-items .fly-out-top-item.active a,.nav-sidebar a.has-sub-items+.sidebar-sub-level-items .fly-out-top-item .fly-out-top-item-container{margin-left:0;margin-right:0;padding-left:1rem;padding-right:1rem;cursor:default;pointer-events:none;font-size:0.75rem;margin-top:0;border-bottom-left-radius:0;border-bottom-right-radius:0}@media (min-width: 768px) and (max-width: 1199px){.nav-sidebar:not(.sidebar-expanded-mobile){width:56px}.nav-sidebar:not(.sidebar-expanded-mobile) .nav-sidebar-inner-scroll{overflow-x:hidden}.nav-sidebar:not(.sidebar-expanded-mobile) .badge.badge-pill:not(.fly-out-badge),.nav-sidebar:not(.sidebar-expanded-mobile) .nav-item-name,.nav-sidebar:not(.sidebar-expanded-mobile) .collapse-text{border:0;clip:rect(0, 0, 0, 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.nav-sidebar:not(.sidebar-expanded-mobile) .sidebar-top-level-items>li>a{min-height:unset}.nav-sidebar:not(.sidebar-expanded-mobile) .fly-out-top-item:not(.divider){display:block !important}.nav-sidebar:not(.sidebar-expanded-mobile) .avatar-container{margin:0 auto}.nav-sidebar:not(.sidebar-expanded-mobile) li.active:not(.fly-out-top-item)>a{background-color:rgba(41,41,97,0.08)}.nav-sidebar:not(.sidebar-expanded-mobile) .context-header{height:60px;width:56px}.nav-sidebar:not(.sidebar-expanded-mobile) .context-header a{padding:10px 4px}.nav-sidebar:not(.sidebar-expanded-mobile) .sidebar-context-title{border:0;clip:rect(0, 0, 0, 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.nav-sidebar:not(.sidebar-expanded-mobile) .context-header{height:auto}.nav-sidebar:not(.sidebar-expanded-mobile) .context-header a{padding:0.25rem}.nav-sidebar:not(.sidebar-expanded-mobile) .sidebar-top-level-items>li .sidebar-sub-level-items:not(.flyout-list){display:none}.nav-sidebar:not(.sidebar-expanded-mobile) .nav-icon-container{margin-right:0}.nav-sidebar:not(.sidebar-expanded-mobile) .toggle-sidebar-button{width:55px;padding:0 21px}.nav-sidebar:not(.sidebar-expanded-mobile) .toggle-sidebar-button .collapse-text{display:none}.nav-sidebar:not(.sidebar-expanded-mobile) .toggle-sidebar-button .icon-chevron-double-lg-left{transform:rotate(180deg);margin:0}}.nav-sidebar-inner-scroll{height:100%;width:100%;overflow-x:hidden;overflow-y:auto}.nav-sidebar-inner-scroll>div.context-header{margin-top:0.25rem}.nav-sidebar-inner-scroll>div.context-header a{padding-left:0.75rem;padding-right:0.75rem;padding-top:0.5rem;padding-bottom:0.5rem;display:flex;align-items:center;border-radius:0.25rem;width:auto;line-height:1rem;margin:1px 8px;padding:0.25rem;margin-bottom:0.25rem;margin-top:0.125rem}.nav-sidebar-inner-scroll>div.context-header a .avatar-container{font-weight:400;flex:none}.sidebar-top-level-items{margin-bottom:60px}.sidebar-top-level-items .context-header a{padding:0.25rem;margin-bottom:0.25rem;margin-top:0.125rem}.sidebar-top-level-items .context-header a .avatar-container{font-weight:400;flex:none}.sidebar-top-level-items>li.active .sidebar-sub-level-items:not(.is-fly-out-only){display:block}.sidebar-top-level-items li>a.gl-link{color:#303030}.sidebar-top-level-items li>a.gl-link:active{text-decoration:none}.sidebar-sub-level-items{padding-top:0;padding-bottom:0;display:none}.sidebar-sub-level-items:not(.fly-out-list) li>a{padding-left:2.25rem}.toggle-sidebar-button,.close-nav-button{height:48px;padding:0 16px;background-color:#fafafa;border:0;color:#666;display:flex;align-items:center;background-color:#f5f5f5;position:fixed;bottom:0;width:255px}.toggle-sidebar-button .collapse-text,.toggle-sidebar-button .icon-chevron-double-lg-left,.close-nav-button .collapse-text,.close-nav-button .icon-chevron-double-lg-left{color:inherit}.collapse-text{white-space:nowrap;overflow:hidden}.sidebar-collapsed-desktop .context-header{height:60px;width:56px}.sidebar-collapsed-desktop .context-header a{padding:10px 4px}.sidebar-collapsed-desktop .sidebar-context-title{border:0;clip:rect(0, 0, 0, 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.sidebar-collapsed-desktop .context-header{height:auto}.sidebar-collapsed-desktop .context-header a{padding:0.25rem}.sidebar-collapsed-desktop .sidebar-top-level-items>li .sidebar-sub-level-items:not(.flyout-list){display:none}.sidebar-collapsed-desktop .nav-icon-container{margin-right:0}.sidebar-collapsed-desktop .toggle-sidebar-button{width:55px;padding:0 21px}.sidebar-collapsed-desktop .toggle-sidebar-button .collapse-text{display:none}.sidebar-collapsed-desktop .toggle-sidebar-button .icon-chevron-double-lg-left{transform:rotate(180deg);margin:0}.close-nav-button{display:none}@media (max-width: 767.98px){.close-nav-button{display:flex}.toggle-sidebar-button{display:none}}input::-moz-placeholder{color:#868686;opacity:1}input::-ms-input-placeholder{color:#868686}input:-ms-input-placeholder{color:#868686}svg{fill:currentColor}svg.s12{width:12px;height:12px}svg.s16{width:16px;height:16px}svg.s32{width:32px;height:32px}svg.s12{vertical-align:-1px}svg.s16{vertical-align:-3px}.header-content .header-search-new{max-width:640px}.header-search{min-width:320px}@media (min-width: 768px) and (max-width: 1199.98px){.header-search{min-width:200px}}.header-search .keyboard-shortcut-helper{transform:translateY(calc(50% - 2px));box-shadow:none;border-color:transparent}.search{margin:0 8px}.search form{display:block;margin:0;padding:4px;width:200px;line-height:24px;height:32px;border:0;border-radius:4px}@media (min-width: 1200px){.search form{width:320px}}.search .search-input{border:0;font-size:14px;padding:0 20px 0 0;margin-left:5px;line-height:25px;width:98%;color:#fff;background:none}.search .search-input-container{display:flex;position:relative}.search .search-input-wrap{width:100%}.search .search-input-wrap .search-icon,.search .search-input-wrap .clear-icon{position:absolute;right:5px;top:4px}.search .search-input-wrap .search-icon{-webkit-user-select:none;user-select:none}.search .search-input-wrap .clear-icon{display:none}.search .search-input-wrap .dropdown{position:static}.search .search-input-wrap .dropdown-menu{left:-5px;max-height:400px;overflow:auto}@media (min-width: 1200px){.search .search-input-wrap .dropdown-menu{width:320px}}.search .identicon{flex-basis:16px;flex-shrink:0;margin-right:4px}.avatar,.avatar-container{float:left;margin-right:16px;border-radius:50%}.avatar.s16,.avatar-container.s16{width:16px;height:16px;margin-right:8px}.avatar.s32,.avatar-container.s32{width:32px;height:32px;margin-right:8px}.avatar{transition-property:none;width:40px;height:40px;padding:0;background:#fdfdfd;overflow:hidden;box-shadow:inset 0 0 0 1px rgba(31,31,31,0.1)}.avatar.avatar-tile{border-radius:0;border:0}.identicon{text-align:center;vertical-align:top;color:#303030;background-color:#f0f0f0}.identicon.s16{font-size:10px;line-height:16px}.identicon.s32{font-size:14px;line-height:32px}.identicon.bg1{background-color:#fcf1ef}.identicon.bg2{background-color:#f4f0ff}.identicon.bg3{background-color:#f1f1ff}.identicon.bg4{background-color:#e9f3fc}.identicon.bg5{background-color:#ecf4ee}.identicon.bg6{background-color:#fdf1dd}.identicon.bg7{background-color:#f0f0f0}.avatar-container{overflow:hidden;display:flex}.avatar-container a{width:100%;height:100%;display:flex;text-decoration:none}.avatar-container .avatar{border-radius:0;border:0;height:auto;width:100%;margin:0;align-self:center}.rect-avatar{border-radius:2px}.rect-avatar.s16{border-radius:2px}.rect-avatar.s16 .avatar{border-radius:2px}.rect-avatar.s32{border-radius:4px}.rect-avatar.s32 .avatar{border-radius:4px}.tab-width-8{tab-size:8}.gl-sr-only{border:0;clip:rect(0, 0, 0, 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.gl-border-none\!{border-style:none !important}.gl-display-none{display:none}.gl-display-flex{display:flex}@media (min-width: 576px){.gl-sm-display-block{display:block}}@media (min-width: 992px){.gl-lg-display-block{display:block}}.gl-display-inline-block\!{display:inline-block !important}.gl-align-items-center{align-items:center}.gl-align-items-stretch{align-items:stretch}.gl-flex-grow-1{flex-grow:1}.gl-justify-content-end{justify-content:flex-end}.gl-relative{position:relative}.gl-absolute{position:absolute}.gl-top-0{top:0}.gl-right-3{right:0.5rem}.gl-w-full{width:100%}.gl-px-3{padding-left:0.5rem;padding-right:0.5rem}.gl-pr-2{padding-right:0.25rem}.gl-pt-0{padding-top:0}.gl-mr-auto{margin-right:auto}.gl-mr-3{margin-right:0.5rem}.gl-ml-n2{margin-left:-0.25rem}.gl-ml-3{margin-left:0.5rem}.gl-mx-0\!{margin-left:0 !important;margin-right:0 !important}.gl-text-right{text-align:right}.gl-white-space-nowrap{white-space:nowrap}.gl-font-sm{font-size:0.75rem}.gl-font-weight-bold{font-weight:600}.gl-z-index-1{z-index:1}.cloak-startup,.content-wrapper>.alert-wrapper,#content-body,.modal-dialog{display:none}
+
+</style>
+
+
+<link rel="stylesheet" media="print" href="/assets/application-83d43ac2aff2d407da96a5fd6a410aa784c1ada12f2e8e02b328764d02324432.css" />
+
+<link rel="stylesheet" media="print" href="/assets/application_utilities-08432cf9120e4223aaf60df81aa67b3a688203198905c5ee86fc3c7e2133dd8b.css" />
+<link rel="stylesheet" media="all" href="/assets/disable_animations-3d7c8bec9ad25c81043c6c75ec12cd989c713cafd18037f1d311a6d293005d10.css" />
+<link rel="stylesheet" media="all" href="/assets/test_environment-17f80ceba7066f139c30ca70bbac619c342c7ab09bcc7c5ba0085aa4b072a50a.css" />
+<link rel="stylesheet" media="print" href="/assets/highlight/themes/white-557ba28a0d83a177dd5f4cdaa59e208f666e026683c63c59f494ece39cb34f98.css" />
+<script nonce="HoEnOW5qBBDwC4FvyjlrqA==">
+//<![CDATA[
+document.querySelectorAll('link[media="print"]').forEach(linkTag => {
+ linkTag.setAttribute('data-startupcss', 'loading');
+ const startupLinkLoadedEvent = new CustomEvent('CSSStartupLinkLoaded');
+ linkTag.addEventListener('load',function(){this.media='all';this.setAttribute('data-startupcss', 'loaded');document.dispatchEvent(startupLinkLoadedEvent);},{once: true});
+})
+
+//]]>
+</script>
+
+<script nonce="HoEnOW5qBBDwC4FvyjlrqA==">
+//<![CDATA[
+window.gon={};gon.api_version="v4";gon.default_avatar_url="http://localhost/assets/no_avatar-849f9c04a3a0d0cea2424ae97b27447dc64a7dbfae83c036c45b403392f0e8ba.png";gon.max_file_size=10;gon.asset_host=null;gon.webpack_public_path="/assets/webpack/";gon.relative_url_root="";gon.user_color_scheme="white";gon.markdown_surround_selection=true;gon.markdown_automatic_lists=true;gon.recaptcha_api_server_url="https://www.google.com/recaptcha/api.js";gon.recaptcha_sitekey=null;gon.gitlab_url="http://localhost";gon.revision="67337076eae";gon.feature_category="global_search";gon.gitlab_logo="/assets/gitlab_logo-2957169c8ef64c58616a1ac3f4fc626e8a35ce4eb3ed31bb0d873712f2a041a0.png";gon.secure=false;gon.sprite_icons="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg";gon.sprite_file_icons="/assets/file_icons-958d18a1c33aa82a81e2eb1ffbffc33131d501c41ad95838a70b089e5ffbd7a0.svg";gon.emoji_sprites_css_path="/assets/emoji_sprites-e1b1ba2d7a86a445dcb1110d1b6e7dd0200ecaa993a445df77a07537dbf8f475.css";gon.select2_css_path="/assets/lazy_bundles/select2-972cb11866a2afb07749efdf63c646325d6ad61bac72ad794042166dcbecfc81.css";gon.test_env=true;gon.disable_animations=null;gon.suggested_label_colors={"#009966":"Green-cyan","#8fbc8f":"Dark sea green","#3cb371":"Medium sea green","#00b140":"Green screen","#013220":"Dark green","#6699cc":"Blue-gray","#0000ff":"Blue","#e6e6fa":"Lavender","#9400d3":"Dark violet","#330066":"Deep violet","#808080":"Gray","#36454f":"Charcoal grey","#f7e7ce":"Champagne","#c21e56":"Rose red","#cc338b":"Magenta-pink","#dc143c":"Crimson","#ff0000":"Red","#cd5b45":"Dark coral","#eee600":"Titanium yellow","#ed9121":"Carrot orange","#c39953":"Aztec Gold"};gon.first_day_of_week=0;gon.time_display_relative=true;gon.ee=true;gon.jh=false;gon.dot_com=false;gon.current_user_id=5;gon.current_username="user1";gon.current_user_fullname="Sidney Jones2";gon.current_user_avatar_url="https://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=80\u0026d=identicon";gon.features={"usageDataApi":true,"securityAutoFix":true,"newHeaderSearch":true,"sourceEditorToolbar":true,"integrationSlackAppNotifications":true,"searchPageVerticalNav":false};gon.roadmap_epics_limit=1000;gon.subscriptions_url="https://customers.staging.gitlab.com";gon.payment_form_url="https://customers.staging.gitlab.com/payment_forms/cc_validation";gon.payment_validation_form_id="payment_method_validation";gon.registration_validation_form_url="https://customers.staging.gitlab.com/payment_forms/cc_registration_validation";
+//]]>
+</script>
+
+
+
+
+
+<script src="/assets/webpack/runtime.bundle.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/main.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/monaco.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/commons-default-jira_connect_app-pages.projects.blob.show-pages.projects.show-pages.root-pages.searc-c71db27a.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/commons-default-jira_connect_app-pages.projects.blob.show-pages.projects.show-pages.root-pages.searc-a2563177.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/commons-default-jira_connect_app-pages.projects.blob.show-pages.projects.show-pages.root-pages.searc-e08f2dc5.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/commons-default-jira_connect_app-pages.projects.blob.show-pages.projects.show-pages.root-pages.searc-177cacb5.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/commons-default-jira_connect_app-pages.projects.blob.show-pages.projects.show-pages.root-pages.searc-ba62a390.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/commons-default-jira_connect_app-pages.projects.blob.show-pages.projects.show-pages.root-pages.searc-439692c5.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/commons-default-jira_connect_app-pages.projects.blob.show-pages.projects.show-pages.root-pages.searc-de960d17.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/commons-default-pages.projects.blob.show-pages.projects.show-pages.root-pages.search.show-pages.sess-63a811d4.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/1.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/commons-default-pages.projects.blob.show-pages.projects.show-pages.root-pages.search.show-pages.sessions.new.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+<script src="/assets/webpack/pages.search.show.chunk.js" defer="defer" nonce="HoEnOW5qBBDwC4FvyjlrqA=="></script>
+
+<meta content="object" property="og:type">
+<meta content="GitLab" property="og:site_name">
+<meta content="term · Search · GitLab" property="og:title">
+<meta content="issues results for term &#39;term&#39;" property="og:description">
+<meta content="http://test.host/assets/twitter_card-570ddb06edf56a2312253c5872489847a0f385112ddbcd71ccfa1570febab5d2.jpg" property="og:image">
+<meta content="64" property="og:image:width">
+<meta content="64" property="og:image:height">
+<meta content="http://test.host/search.html?scope=issues&amp;search=term" property="og:url">
+<meta content="summary" property="twitter:card">
+<meta content="term · Search · GitLab" property="twitter:title">
+<meta content="issues results for term &#39;term&#39;" property="twitter:description">
+<meta content="http://test.host/assets/twitter_card-570ddb06edf56a2312253c5872489847a0f385112ddbcd71ccfa1570febab5d2.jpg" property="twitter:image">
+
+<meta content="issues results for term &#39;term&#39;" name="description">
+<link href="/-/manifest.json" rel="manifest">
+<meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
+<meta content="#292961" name="theme-color">
+
+<meta name="csp-nonce" content="HoEnOW5qBBDwC4FvyjlrqA==" />
+<meta name="action-cable-url" content="/-/cable" />
+<link rel="apple-touch-icon" type="image/x-icon" href="/assets/apple-touch-icon-b049d4bc0dd9626f31db825d61880737befc7835982586d015bded10b4435460.png" />
+<link href="/search/opensearch.xml" rel="search" title="Search GitLab" type="application/opensearchdescription+xml">
+
+
+
+<script nonce="HoEnOW5qBBDwC4FvyjlrqA==">
+//<![CDATA[
+;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
+p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
+};p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
+n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","http://test.host/assets/snowplow/sp-871a2a8782c3dbf48f9f2bcc8642417934befe14cbfd7922f7e80e90d4cfe8f9.js","snowplow"));
+
+window.snowplowOptions = {"namespace":"gl","hostname":"localhost","cookieDomain":null,"appId":null,"formTracking":true,"linkClickTracking":true}
+
+gl = window.gl || {};
+gl.snowplowStandardContext = {"schema":"iglu:com.gitlab/gitlab_standard/jsonschema/1-0-8","data":{"environment":"development","source":"gitlab-rails","plan":null,"extra":{},"user_id":5,"namespace_id":null,"project_id":null,"context_generated_at":"2022-10-18T14:24:54.133Z"}}
+gl.snowplowPseudonymizedPageUrl = "http://localhost/search.html?scope=issues&search=masked_search";
+
+
+//]]>
+</script>
+
+</head>
+
+<body class="ui-indigo tab-width-8 gl-browser-generic gl-platform-other" data-page="search:show">
+
+<script nonce="HoEnOW5qBBDwC4FvyjlrqA==">
+//<![CDATA[
+gl = window.gl || {};
+gl.client = {"isGeneric":true,"isOther":true};
+
+
+//]]>
+</script>
+
+
+
+<header class="navbar navbar-gitlab navbar-expand-sm js-navbar" data-qa-selector="navbar">
+<a class="gl-sr-only gl-accessibility" href="#content-body">Skip to content</a>
+<div class="container-fluid">
+<div class="header-content js-header-content">
+<div class="title-container hide-when-top-nav-responsive-open gl-transition-medium gl-display-flex gl-align-items-stretch gl-pt-0 gl-mr-3">
+<div class="title">
+<span class="gl-sr-only">GitLab</span>
+<a title="Dashboard" id="logo" class="has-tooltip" data-track-label="main_navigation" data-track-action="click_gitlab_logo_link" data-track-property="navigation" href="/"><svg class="tanuki-logo" width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+ <path class="tanuki-shape tanuki" d="m24.507 9.5-.034-.09L21.082.562a.896.896 0 0 0-1.694.091l-2.29 7.01H7.825L5.535.653a.898.898 0 0 0-1.694-.09L.451 9.411.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 2.56 1.935 1.554 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
+ fill="#E24329"/>
+ <path class="tanuki-shape right-cheek" d="m24.507 9.5-.034-.09a11.44 11.44 0 0 0-4.56 2.051l-7.447 5.632 4.742 3.584 5.197-3.89.014-.01A6.297 6.297 0 0 0 24.507 9.5Z"
+ fill="#FC6D26"/>
+ <path class="tanuki-shape chin" d="m7.707 20.677 2.56 1.935 1.555 1.176a1.051 1.051 0 0 0 1.268 0l1.555-1.176 2.56-1.935-4.743-3.584-4.755 3.584Z"
+ fill="#FCA326"/>
+ <path class="tanuki-shape left-cheek" d="M5.01 11.461a11.43 11.43 0 0 0-4.56-2.05L.416 9.5a6.297 6.297 0 0 0 2.09 7.278l.012.01.03.022 5.16 3.867 4.745-3.584-7.444-5.632Z"
+ fill="#FC6D26"/>
+</svg>
+
+</a></div>
+<div class="gl-display-flex gl-align-items-center">
+</div>
+<div class="gl-display-none gl-sm-display-block">
+<ul class="list-unstyled nav navbar-sub-nav" data-view-model="{&quot;primary&quot;:[{&quot;type&quot;:&quot;header&quot;,&quot;title&quot;:&quot;Switch to&quot;},{&quot;id&quot;:&quot;project&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Projects&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;project&quot;,&quot;href&quot;:&quot;&quot;,&quot;view&quot;:&quot;projects&quot;,&quot;css_class&quot;:&quot;qa-projects-dropdown&quot;,&quot;data&quot;:{&quot;track_label&quot;:&quot;projects_dropdown&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;groups&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Groups&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;group&quot;,&quot;href&quot;:&quot;&quot;,&quot;view&quot;:&quot;groups&quot;,&quot;css_class&quot;:&quot;qa-groups-dropdown&quot;,&quot;data&quot;:{&quot;track_label&quot;:&quot;groups_dropdown&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;},&quot;emoji&quot;:null},{&quot;type&quot;:&quot;header&quot;,&quot;title&quot;:&quot;Explore&quot;},{&quot;id&quot;:&quot;milestones&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Milestones&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;clock&quot;,&quot;href&quot;:&quot;/dashboard/milestones&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;milestones_link&quot;,&quot;track_label&quot;:&quot;menu_milestones&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;snippets&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Snippets&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;snippet&quot;,&quot;href&quot;:&quot;/dashboard/snippets&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;snippets_link&quot;,&quot;track_label&quot;:&quot;menu_snippets&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;activity&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Activity&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;history&quot;,&quot;href&quot;:&quot;/dashboard/activity&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;activity_link&quot;,&quot;track_label&quot;:&quot;menu_activity&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null}],&quot;secondary&quot;:[],&quot;views&quot;:{&quot;projects&quot;:{&quot;namespace&quot;:&quot;projects&quot;,&quot;currentUserName&quot;:&quot;user1&quot;,&quot;currentItem&quot;:{},&quot;linksPrimary&quot;:[{&quot;id&quot;:&quot;your&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;View all projects&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/projects&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;View all projects&quot;,&quot;track_label&quot;:&quot;menu_view_all_projects&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null}],&quot;linksSecondary&quot;:[]},&quot;groups&quot;:{&quot;namespace&quot;:&quot;groups&quot;,&quot;currentUserName&quot;:&quot;user1&quot;,&quot;currentItem&quot;:{},&quot;linksPrimary&quot;:[{&quot;id&quot;:&quot;your&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;View all groups&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/groups&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;View all groups&quot;,&quot;track_label&quot;:&quot;menu_view_all_groups&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null}],&quot;linksSecondary&quot;:[]}},&quot;shortcuts&quot;:[{&quot;id&quot;:&quot;project-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Projects&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/projects&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-projects&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Projects&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;groups-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Groups&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/groups&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-groups&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Groups&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;milestones-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Milestones&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/milestones&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-milestones&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Milestones&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;snippets-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Snippets&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/snippets&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-snippets&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Snippets&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;activity-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Activity&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/activity&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-activity&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Activity&quot;},&quot;emoji&quot;:null}],&quot;menuTooltip&quot;:&quot;Main menu&quot;}" id="js-top-nav">
+<li>
+<a class="top-nav-toggle" data-toggle="dropdown" href="#" type="button">
+<svg class="s16" data-testid="hamburger-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#hamburger"></use></svg>
+</a>
+</li>
+</ul>
+<div class="hidden">
+<a class="dashboard-shortcuts-projects" href="/dashboard/projects">Projects
+</a><a class="dashboard-shortcuts-groups" href="/dashboard/groups">Groups
+</a><a class="dashboard-shortcuts-milestones" href="/dashboard/milestones">Milestones
+</a><a class="dashboard-shortcuts-snippets" href="/dashboard/snippets">Snippets
+</a><a class="dashboard-shortcuts-activity" href="/dashboard/activity">Activity
+</a></div>
+
+</div>
+</div>
+<div class="navbar-collapse gl-transition-medium collapse gl-mr-auto global-search-container hide-when-top-nav-responsive-open">
+<ul class="nav navbar-nav gl-w-full gl-align-items-center">
+<li class="nav-item header-search-new gl-display-none gl-lg-display-block gl-w-full">
+</li>
+<li class="nav-item d-none d-sm-inline-block d-lg-none">
+<a title="Search" aria-label="Search" data-toggle="tooltip" data-placement="bottom" data-container="body" href="/search"><svg class="s16" data-testid="search-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#search"></use></svg>
+</a></li>
+</ul>
+</div>
+<div class="navbar-collapse gl-transition-medium collapse">
+<ul class="nav navbar-nav gl-w-full gl-align-items-center gl-justify-content-end">
+<li class="header-new gl-flex-grow-1 gl-flex-shrink-1 dropdown gl-display-none gl-sm-display-block gl-white-space-nowrap gl-text-right" data-track-action="click_dropdown" data-track-label="new_dropdown">
+<a class="header-new-dropdown-toggle has-tooltip gl-display-inline-block!" id="js-onboarding-new-project-link" title="Create new..." ref="tooltip" aria-label="Create new..." data-toggle="dropdown" data-placement="bottom" data-container="body" data-display="static" data-qa-selector="new_menu_toggle" href="/projects/new"><svg class="s16" data-testid="plus-square-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#plus-square"></use></svg>
+<svg class="s16 caret-down" data-testid="chevron-down-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#chevron-down"></use></svg>
+</a><div class="dropdown-menu dropdown-menu-right dropdown-extended-height">
+<ul>
+<li><a data-track-action="click_link_new_project" data-track-label="plus_menu_dropdown" data-qa-selector="global_new_project_link" href="/projects/new">New project/repository</a></li>
+<li><a data-track-action="click_link_new_group" data-track-label="plus_menu_dropdown" data-qa-selector="global_new_group_link" href="/groups/new">New group</a></li>
+<li><a data-track-action="click_link_new_snippet_parent" data-track-label="plus_menu_dropdown" data-qa-selector="global_new_snippet_link" href="/-/snippets/new">New snippet</a></li>
+</ul>
+</div>
+</li>
+
+<li class="user-counter"><a title="Issues" class="dashboard-shortcuts-issues js-prefetch-document" aria-label="Issues" data-qa-selector="issues_shortcut_button" data-toggle="tooltip" data-placement="bottom" data-track-label="main_navigation" data-track-action="click_issues_link" data-track-property="navigation" data-container="body" href="/dashboard/issues?assignee_username=user1"><svg class="s16" data-testid="issues-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#issues"></use></svg>
+<span aria-label="0 assigned issues" class="gl-badge badge badge-pill badge-success sm gl-ml-n2 gl-display-none">0
+</span></a></li><li class="user-counter dropdown"><a class="dashboard-shortcuts-merge_requests has-tooltip" title="Merge requests" aria-label="Merge requests" data-qa-selector="merge_requests_shortcut_button" data-toggle="dropdown" data-placement="bottom" data-track-label="main_navigation" data-track-action="click_merge_link" data-track-property="navigation" data-container="body" href="/dashboard/merge_requests?assignee_username=user1"><svg class="s16" data-testid="git-merge-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#git-merge"></use></svg>
+<span aria-label="0 merge requests" class="gl-badge badge badge-pill badge-warning sm js-merge-requests-count gl-ml-n2 gl-display-none">0
+</span><svg class="s16 caret-down gl-mx-0!" data-testid="chevron-down-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#chevron-down"></use></svg>
+</a><div class="dropdown-menu dropdown-menu-right">
+<ul>
+<li class="dropdown-header">
+Merge requests
+</li>
+<li>
+<a class="gl-display-flex! gl-align-items-center js-prefetch-document" href="/dashboard/merge_requests?assignee_username=user1">Assigned to you
+<span class="gl-badge badge badge-pill badge-neutral sm js-assigned-mr-count gl-ml-auto">0
+</span></a></li>
+<li>
+<a class="gl-display-flex! gl-align-items-center js-prefetch-document" href="/dashboard/merge_requests?reviewer_username=user1">Review requests for you
+<span class="gl-badge badge badge-pill badge-neutral sm js-reviewer-mr-count gl-ml-auto">0
+</span></a></li>
+</ul>
+</div>
+</li><li class="user-counter"><a title="To-Do List" aria-label="To-Do List" class="shortcuts-todos js-prefetch-document" data-qa-selector="todos_shortcut_button" data-toggle="tooltip" data-placement="bottom" data-track-label="main_navigation" data-track-action="click_to_do_link" data-track-property="navigation" data-container="body" href="/dashboard/todos"><svg class="s16" data-testid="todo-done-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#todo-done"></use></svg>
+<span aria-label="Todos count" class="gl-badge badge badge-pill badge-info sm js-todos-count gl-ml-n2 hidden">0
+</span></a></li><li class="nav-item header-help dropdown d-none d-md-block" data-track-action="click_question_mark_link" data-track-experiment="cross_stage_fdm" data-track-label="main_navigation" data-track-property="navigation">
+<a class="header-help-dropdown-toggle gl-relative" data-toggle="dropdown" href="/help"><span class="gl-sr-only">
+Help
+</span>
+<svg class="s16" data-testid="question-o-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#question-o"></use></svg>
+<span class="notification-dot rounded-circle gl-absolute"></span>
+<svg class="s16 caret-down" data-testid="chevron-down-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#chevron-down"></use></svg>
+</a><div class="dropdown-menu dropdown-menu-right">
+<ul>
+<li>
+
+</li>
+
+<li>
+<button class="gl-justify-content-space-between gl-align-items-center js-whats-new-trigger gl-display-flex!" type="button">
+What&#39;s new
+<span class="gl-badge badge badge-pill badge-muted sm js-whats-new-notification-count">4</span>
+</button>
+</li>
+
+<li>
+<a href="/help">Help</a>
+</li>
+<li>
+<a href="https://about.gitlab.com/getting-help/">Support</a>
+</li>
+<li>
+<a target="_blank" class="text-nowrap" rel="noopener noreferrer" data-track-action="click_forum" data-track-property="question_menu" href="https://forum.gitlab.com">Community forum</a>
+
+</li>
+<li>
+<button class="js-shortcuts-modal-trigger" type="button">
+Keyboard shortcuts
+<kbd aria-hidden="true" class="flat float-right">?</kbd>
+</button>
+</li>
+<li class="divider"></li>
+<li>
+<a href="https://about.gitlab.com/submit-feedback">Submit feedback</a>
+</li>
+<li>
+
+</li>
+
+</ul>
+
+</div>
+</li>
+<li class="nav-item header-user js-nav-user-dropdown dropdown" data-qa-selector="user_menu" data-testid="user-menu" data-track-action="click_dropdown" data-track-label="profile_dropdown" data-track-value="">
+<a class="header-user-dropdown-toggle" data-toggle="dropdown" href="/user1"><img srcset="https://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=48&amp;d=identicon 1x, https://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=48&amp;d=identicon 2x" alt="Sidney Jones2" class="gl-avatar gl-avatar-s24 header-user-avatar gl-avatar-circle" height="24" width="24" loading="lazy" data-qa-selector="user_avatar_content" src="https://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=48&amp;d=identicon" />
+
+
+<svg class="s16 caret-down" data-testid="chevron-down-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#chevron-down"></use></svg>
+</a><div class="dropdown-menu dropdown-menu-right">
+<ul>
+<li class="current-user">
+<a class="gl-line-height-20!" data-user="user1" data-testid="user-profile-link" data-qa-selector="user_profile_link" href="/user1"><div class="gl-font-weight-bold">
+Sidney Jones2
+</div>
+@user1
+
+</a></li>
+<li class="divider"></li>
+<li>
+<button class="gl-button btn btn-link menu-item js-set-status-modal-trigger" type="button">
+Set status
+</button>
+</li>
+<li>
+<a data-qa-selector="edit_profile_link" href="/-/profile">Edit profile</a>
+</li>
+<li>
+<a href="/-/profile/preferences">Preferences</a>
+</li>
+
+<li class="divider d-md-none"></li>
+<li class="d-md-none">
+<a href="/help">Help</a>
+</li>
+<li class="d-md-none">
+<a href="https://about.gitlab.com/getting-help/">Support</a>
+</li>
+<li class="d-md-none">
+<a target="_blank" class="text-nowrap" rel="noopener noreferrer" data-track-action="click_forum" data-track-property="question_menu" href="https://forum.gitlab.com">Community forum</a>
+
+</li>
+<li class="d-md-none">
+<a href="https://about.gitlab.com/submit-feedback">Submit feedback</a>
+</li>
+<li class="d-md-none">
+
+</li>
+
+<li class="divider"></li>
+<li>
+<a class="sign-out-link" data-qa-selector="sign_out_link" rel="nofollow" data-method="post" href="/users/sign_out">Sign out</a>
+</li>
+</ul>
+
+</div>
+</li>
+</ul>
+</div>
+<button class="navbar-toggler d-block d-sm-none gl-border-none!" data-qa-selector="mobile_navbar_button" data-testid="top-nav-responsive-toggle" type="button">
+<span class="sr-only">Toggle navigation</span>
+<span class="more-icon gl-px-3 gl-font-sm gl-font-weight-bold">
+<span class="gl-pr-2">Menu</span>
+<svg class="s16" data-testid="hamburger-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#hamburger"></use></svg>
+</span>
+<svg class="s12 close-icon" data-testid="close-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#close"></use></svg>
+</button>
+</div>
+</div>
+</header>
+<div data-version-digest="b0774e3a70a678d0f08ccf2ca4c3dcfc3ef576b938236bb4e7951682a80b3086" id="whats-new-app"></div>
+<div class="js-set-status-modal-wrapper" data-current-emoji="" data-current-message="" data-default-emoji="speech_balloon"></div>
+
+<div class="layout-page hide-when-top-nav-responsive-open">
+<div class="content-wrapper content-wrapper-margin">
+<div class="mobile-overlay"></div>
+
+<div class="alert-wrapper gl-force-block-formatting-context">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+</div>
+<div class="container-fluid container-limited ">
+<main class="content" id="content-body">
+<div class="flash-container flash-container-page sticky" data-qa-selector="flash_container">
+</div>
+
+
+
+<div class="page-title-holder gl-display-flex gl-flex-wrap gl-justify-content-space-between">
+<h1 class="page-title gl-font-size-h-display gl-mr-5">Search</h1>
+
+</div>
+<div class="gl-mt-3">
+<div data-group-initial-data="null" data-project-initial-data="null" id="js-search-topbar"></div>
+</div>
+<div class="scrolling-tabs-container inner-page-scroll-tabs is-smaller">
+<div class="fade-left"><svg class="s12" data-testid="chevron-lg-left-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#chevron-lg-left"></use></svg></div>
+<div class="fade-right"><svg class="s12" data-testid="chevron-lg-right-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#chevron-lg-right"></use></svg></div>
+<ul class="search-filter scrolling-tabs nav-links nav gl-tabs-nav"><li data-qa-selector="projects_tab"><a href="/search?scope=projects&amp;search=term">Projects <span class="gl-badge badge badge-pill badge-muted sm js-search-count hidden" data-url="/search/count?scope=projects&amp;search=term"></span></a></li>
+
+
+<li class="active"><a href="/search?scope=issues&amp;search=term">Issues <span class="gl-badge badge badge-pill badge-muted sm">0</span></a></li>
+<li><a href="/search?scope=merge_requests&amp;search=term">Merge requests <span class="gl-badge badge badge-pill badge-muted sm js-search-count hidden" data-url="/search/count?scope=merge_requests&amp;search=term"></span></a></li>
+
+
+<li><a href="/search?scope=milestones&amp;search=term">Milestones <span class="gl-badge badge badge-pill badge-muted sm js-search-count hidden" data-url="/search/count?scope=milestones&amp;search=term"></span></a></li>
+<li><a href="/search?scope=users&amp;search=term">Users <span class="gl-badge badge badge-pill badge-muted sm js-search-count hidden" data-url="/search/count?scope=users&amp;search=term"></span></a></li>
+
+</ul></div>
+
+
+
+<div class="results gl-md-display-flex gl-mt-3">
+<div class="search-sidebar gl-display-flex gl-flex-direction-column gl-mr-4" data-navigation="{&quot;projects&quot;:{&quot;label&quot;:&quot;Projects&quot;,&quot;scope&quot;:&quot;projects&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;projects_tab&quot;},&quot;link&quot;:&quot;/search?scope=projects&amp;search=term&quot;,&quot;active&quot;:false,&quot;count_link&quot;:&quot;/search/count?scope=projects&amp;search=term&quot;},&quot;blobs&quot;:{&quot;label&quot;:&quot;Code&quot;,&quot;scope&quot;:&quot;blobs&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;code_tab&quot;},&quot;link&quot;:&quot;/search?scope=blobs&amp;search=term&quot;,&quot;active&quot;:false,&quot;count_link&quot;:&quot;/search/count?scope=blobs&amp;search=term&quot;},&quot;issues&quot;:{&quot;label&quot;:&quot;Issues&quot;,&quot;scope&quot;:&quot;issues&quot;,&quot;data&quot;:null,&quot;link&quot;:&quot;/search?scope=issues&amp;search=term&quot;,&quot;active&quot;:true,&quot;count&quot;:&quot;0&quot;},&quot;merge_requests&quot;:{&quot;label&quot;:&quot;Merge requests&quot;,&quot;scope&quot;:&quot;merge_requests&quot;,&quot;data&quot;:null,&quot;link&quot;:&quot;/search?scope=merge_requests&amp;search=term&quot;,&quot;active&quot;:false,&quot;count_link&quot;:&quot;/search/count?scope=merge_requests&amp;search=term&quot;},&quot;milestones&quot;:{&quot;label&quot;:&quot;Milestones&quot;,&quot;scope&quot;:&quot;milestones&quot;,&quot;data&quot;:null,&quot;link&quot;:&quot;/search?scope=milestones&amp;search=term&quot;,&quot;active&quot;:false,&quot;count_link&quot;:&quot;/search/count?scope=milestones&amp;search=term&quot;},&quot;users&quot;:{&quot;label&quot;:&quot;Users&quot;,&quot;scope&quot;:&quot;users&quot;,&quot;data&quot;:null,&quot;link&quot;:&quot;/search?scope=users&amp;search=term&quot;,&quot;active&quot;:false,&quot;count_link&quot;:&quot;/search/count?scope=users&amp;search=term&quot;}}" id="js-search-sidebar"></div>
+<div class="gl-w-full gl-flex-grow-1 gl-overflow-x-hidden">
+<div class="search_box gl-my-8 gl-text-center">
+<div class="search_glyph"></div>
+<h4>
+<svg class="s24 gl-vertical-align-text-bottom" data-testid="search-icon"><use href="/assets/icons-7dd47f0bd545c11d13acc0f4abc2ce8fac4abbbef0965fde375eb13c62e7f9fd.svg#search"></use></svg>
+We couldn&#39;t find any issues matching <code>term</code>
+</h4>
+</div>
+
+
+</div>
+</div>
+
+
+</main>
+</div>
+
+
+</div>
+</div>
+<div class="top-nav-responsive layout-page content-wrapper-margin">
+<div class="cloak-startup">
+<div data-view-model="{&quot;primary&quot;:[{&quot;type&quot;:&quot;header&quot;,&quot;title&quot;:&quot;Switch to&quot;},{&quot;id&quot;:&quot;project&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Projects&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;project&quot;,&quot;href&quot;:&quot;&quot;,&quot;view&quot;:&quot;projects&quot;,&quot;css_class&quot;:&quot;qa-projects-dropdown&quot;,&quot;data&quot;:{&quot;track_label&quot;:&quot;projects_dropdown&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;groups&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Groups&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;group&quot;,&quot;href&quot;:&quot;&quot;,&quot;view&quot;:&quot;groups&quot;,&quot;css_class&quot;:&quot;qa-groups-dropdown&quot;,&quot;data&quot;:{&quot;track_label&quot;:&quot;groups_dropdown&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;},&quot;emoji&quot;:null},{&quot;type&quot;:&quot;header&quot;,&quot;title&quot;:&quot;Explore&quot;},{&quot;id&quot;:&quot;milestones&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Milestones&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;clock&quot;,&quot;href&quot;:&quot;/dashboard/milestones&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;milestones_link&quot;,&quot;track_label&quot;:&quot;menu_milestones&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;snippets&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Snippets&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;snippet&quot;,&quot;href&quot;:&quot;/dashboard/snippets&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;snippets_link&quot;,&quot;track_label&quot;:&quot;menu_snippets&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;activity&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Activity&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;history&quot;,&quot;href&quot;:&quot;/dashboard/activity&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;activity_link&quot;,&quot;track_label&quot;:&quot;menu_activity&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null}],&quot;secondary&quot;:[],&quot;views&quot;:{&quot;projects&quot;:{&quot;namespace&quot;:&quot;projects&quot;,&quot;currentUserName&quot;:&quot;user1&quot;,&quot;currentItem&quot;:{},&quot;linksPrimary&quot;:[{&quot;id&quot;:&quot;your&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;View all projects&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/projects&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;View all projects&quot;,&quot;track_label&quot;:&quot;menu_view_all_projects&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null}],&quot;linksSecondary&quot;:[]},&quot;groups&quot;:{&quot;namespace&quot;:&quot;groups&quot;,&quot;currentUserName&quot;:&quot;user1&quot;,&quot;currentItem&quot;:{},&quot;linksPrimary&quot;:[{&quot;id&quot;:&quot;your&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;View all groups&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/groups&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;View all groups&quot;,&quot;track_label&quot;:&quot;menu_view_all_groups&quot;,&quot;track_action&quot;:&quot;click_dropdown&quot;,&quot;track_property&quot;:&quot;navigation&quot;},&quot;emoji&quot;:null}],&quot;linksSecondary&quot;:[]},&quot;new&quot;:{&quot;title&quot;:&quot;Create new...&quot;,&quot;menu_sections&quot;:[{&quot;title&quot;:&quot;GitLab&quot;,&quot;menu_items&quot;:[{&quot;id&quot;:&quot;general_new_project&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;New project/repository&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/projects/new&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;track_action&quot;:&quot;click_link_new_project&quot;,&quot;track_label&quot;:&quot;plus_menu_dropdown&quot;,&quot;qa_selector&quot;:&quot;global_new_project_link&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;general_new_group&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;New group&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/groups/new&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;track_action&quot;:&quot;click_link_new_group&quot;,&quot;track_label&quot;:&quot;plus_menu_dropdown&quot;,&quot;qa_selector&quot;:&quot;global_new_group_link&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;general_new_snippet&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;New snippet&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/-/snippets/new&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;track_action&quot;:&quot;click_link_new_snippet_parent&quot;,&quot;track_label&quot;:&quot;plus_menu_dropdown&quot;,&quot;qa_selector&quot;:&quot;global_new_snippet_link&quot;},&quot;emoji&quot;:null}]}]},&quot;search&quot;:{&quot;id&quot;:&quot;search&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Search&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;search&quot;,&quot;href&quot;:&quot;/search&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:null,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Search&quot;},&quot;emoji&quot;:null}},&quot;shortcuts&quot;:[{&quot;id&quot;:&quot;project-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Projects&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/projects&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-projects&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Projects&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;groups-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Groups&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/groups&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-groups&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Groups&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;milestones-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Milestones&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/milestones&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-milestones&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Milestones&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;snippets-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Snippets&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/snippets&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-snippets&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Snippets&quot;},&quot;emoji&quot;:null},{&quot;id&quot;:&quot;activity-shortcut&quot;,&quot;type&quot;:&quot;item&quot;,&quot;title&quot;:&quot;Activity&quot;,&quot;active&quot;:false,&quot;icon&quot;:&quot;&quot;,&quot;href&quot;:&quot;/dashboard/activity&quot;,&quot;view&quot;:&quot;&quot;,&quot;css_class&quot;:&quot;dashboard-shortcuts-activity&quot;,&quot;data&quot;:{&quot;qa_selector&quot;:&quot;menu_item_link&quot;,&quot;qa_title&quot;:&quot;Activity&quot;},&quot;emoji&quot;:null}],&quot;menuTooltip&quot;:&quot;Main menu&quot;}" id="js-top-nav-responsive"></div>
+</div>
+</div>
+
+
+
+<script nonce="HoEnOW5qBBDwC4FvyjlrqA==">
+//<![CDATA[
+if ('loading' in HTMLImageElement.prototype) {
+ document.querySelectorAll('img.lazy').forEach(img => {
+ img.loading = 'lazy';
+ let imgUrl = img.dataset.src;
+ // Only adding width + height for avatars for now
+ if (imgUrl.indexOf('/avatar/') > -1 && imgUrl.indexOf('?') === -1) {
+ const targetWidth = img.getAttribute('width') || img.width;
+ imgUrl += `?width=${targetWidth}`;
+ }
+ img.src = imgUrl;
+ img.removeAttribute('data-src');
+ img.classList.remove('lazy');
+ img.classList.add('js-lazy-loaded', 'qa-js-lazy-loaded');
+ });
+}
+
+//]]>
+</script>
+<script nonce="HoEnOW5qBBDwC4FvyjlrqA==">
+//<![CDATA[
+gl = window.gl || {};
+gl.experiments = {};
+
+
+//]]>
+</script>
+
+</body>
+</html>
+
diff --git a/yarn.lock b/yarn.lock
index b6dd915d203..2f7bc645901 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1113,10 +1113,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.5.0.tgz#226240b7aa93db986f4c6f7738ca2a1846b5234d"
integrity sha512-/djPsJzUY7i/FaydRVt3ZyXiFf5HGNo1rg2mfLn1EpXvT4zc2ag5ECwnYcPb97KgqFCJX6Tk+Ndu8Wh3GoOW1g==
-"@gitlab/ui@49.0.2":
- version "49.0.2"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-49.0.2.tgz#4d545fdb4165cf961176fb8972fb9bbfe6b15f1f"
- integrity sha512-hleFBhGbNDItQVe/EavY8K1sAaeTsYZeOb2Z9+W1xafqOlruxOnM1fW3/Rjn9d2dEA8kNCwav1yTwH72wUE4rg==
+"@gitlab/ui@49.2.0":
+ version "49.2.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-49.2.0.tgz#45eedbe943bccbb6d986d66bf7c6294c82e89366"
+ integrity sha512-S7jfYtmh2Z36bum48aqb+NFLl/WAqow5gOXfWjdl1lGXjpKZ27neJPTWfpYi2PRyhmPs8ptVg7zKaxXJMZ7cgA==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.20.1"