summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 06:07:11 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-17 06:07:11 +0000
commit75621c94b5dbe233edd72c3d8cc602fed25e84d2 (patch)
treea38d832241e66a2e296e276493bff0260bfc9712
parent9bf8cb8d34039f3cef9e1b2f812ce634f2bebe69 (diff)
downloadgitlab-ce-75621c94b5dbe233edd72c3d8cc602fed25e84d2.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/merge_request_templates/Default.md4
-rw-r--r--.rubocop.yml5
-rw-r--r--.rubocop_todo/graphql/resource_not_available_error.yml42
-rw-r--r--app/assets/javascripts/layout_nav.js15
-rw-r--r--app/assets/stylesheets/framework/mixins.scss10
-rw-r--r--app/controllers/sent_notifications_controller.rb4
-rw-r--r--app/models/issue.rb6
-rw-r--r--app/services/ci/job_artifacts/create_service.rb3
-rw-r--r--app/uploaders/object_storage.rb28
-rw-r--r--app/views/admin/jobs/index.html.haml6
-rw-r--r--app/views/admin/projects/index.html.haml6
-rw-r--r--app/views/admin/users/_users.html.haml4
-rw-r--r--app/views/dashboard/_projects_head.html.haml6
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml12
-rw-r--r--app/views/shared/_event_filter.html.haml6
-rw-r--r--app/views/shared/milestones/_tabs.html.haml6
-rw-r--r--app/views/users/show.html.haml6
-rw-r--r--db/post_migrate/20230516032545_add_unique_notes_id_convert_to_bigint_for_gitlab_com.rb34
-rw-r--r--db/post_migrate/20230516033729_add_referencing_bigint_fks_for_notes_on_gitlab_com.rb74
-rw-r--r--db/schema_migrations/202305160325451
-rw-r--r--db/schema_migrations/202305160337291
-rw-r--r--db/structure.sql50
-rw-r--r--doc/ci/cloud_services/google_cloud/index.md16
-rw-r--r--doc/user/application_security/coverage_fuzzing/index.md4
-rw-r--r--locale/gitlab.pot68
-rw-r--r--rubocop/cop/graphql/resource_not_available_error.rb47
-rw-r--r--rubocop/node_pattern_helper.rb14
-rw-r--r--spec/controllers/sent_notifications_controller_spec.rb56
-rw-r--r--spec/frontend/layout_nav_spec.js39
-rw-r--r--spec/models/issue_spec.rb24
-rw-r--r--spec/rubocop/cop/graphql/resource_not_available_error_spec.rb37
-rw-r--r--spec/rubocop/node_pattern_helper_spec.rb20
-rw-r--r--spec/services/ci/job_artifacts/create_service_spec.rb6
-rw-r--r--spec/uploaders/object_storage_spec.rb104
34 files changed, 652 insertions, 112 deletions
diff --git a/.gitlab/merge_request_templates/Default.md b/.gitlab/merge_request_templates/Default.md
index 404a18ad074..254e90d5fb0 100644
--- a/.gitlab/merge_request_templates/Default.md
+++ b/.gitlab/merge_request_templates/Default.md
@@ -20,6 +20,10 @@ reviewers and future readers. If you need help visually verifying the change,
please leave a comment and ping a GitLab reviewer, maintainer, or MR coach.
-->
+| Before | After |
+| ------ | ------ |
+| | |
+
## How to set up and validate locally
_Numbered steps to set up and validate the change are strongly suggested._
diff --git a/.rubocop.yml b/.rubocop.yml
index 81ad4cd31f3..9ceb8466270 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1007,3 +1007,8 @@ SidekiqLoadBalancing/WorkerDataConsistency:
# This cop is disabled for Ruby 3.0+ anyway.
Lint/NonDeterministicRequireOrder:
Enabled: false
+
+Graphql/ResourceNotAvailableError:
+ Exclude:
+ # Definition of `raise_resource_not_available_error!`
+ - 'lib/gitlab/graphql/authorize/authorize_resource.rb'
diff --git a/.rubocop_todo/graphql/resource_not_available_error.yml b/.rubocop_todo/graphql/resource_not_available_error.yml
new file mode 100644
index 00000000000..68e68c51277
--- /dev/null
+++ b/.rubocop_todo/graphql/resource_not_available_error.yml
@@ -0,0 +1,42 @@
+---
+# Cop supports --autocorrect.
+Graphql/ResourceNotAvailableError:
+ Details: grace period
+ Exclude:
+ - 'app/graphql/mutations/achievements/create.rb'
+ - 'app/graphql/mutations/admin/sidekiq_queues/delete_jobs.rb'
+ - 'app/graphql/mutations/ci/ci_cd_settings_update.rb'
+ - 'app/graphql/mutations/ci/job_artifact/bulk_destroy.rb'
+ - 'app/graphql/mutations/ci/runner/create.rb'
+ - 'app/graphql/mutations/custom_emoji/create.rb'
+ - 'app/graphql/mutations/custom_emoji/destroy.rb'
+ - 'app/graphql/mutations/design_management/move.rb'
+ - 'app/graphql/mutations/issues/bulk_update.rb'
+ - 'app/graphql/mutations/issues/set_crm_contacts.rb'
+ - 'app/graphql/mutations/issues/set_escalation_status.rb'
+ - 'app/graphql/mutations/notes/create/base.rb'
+ - 'app/graphql/mutations/notes/create/note.rb'
+ - 'app/graphql/mutations/notes/reposition_image_diff_note.rb'
+ - 'app/graphql/mutations/notes/update/image_diff_note.rb'
+ - 'app/graphql/mutations/saved_replies/create.rb'
+ - 'app/graphql/mutations/saved_replies/destroy.rb'
+ - 'app/graphql/mutations/saved_replies/update.rb'
+ - 'app/graphql/mutations/todos/mark_all_done.rb'
+ - 'app/graphql/mutations/work_items/export.rb'
+ - 'app/graphql/resolvers/ci/runner_setup_resolver.rb'
+ - 'app/graphql/resolvers/concerns/search_arguments.rb'
+ - 'app/graphql/resolvers/container_repository_tags_resolver.rb'
+ - 'app/graphql/resolvers/design_management/versions_resolver.rb'
+ - 'app/graphql/resolvers/kas/agent_configurations_resolver.rb'
+ - 'app/graphql/resolvers/kas/agent_connections_resolver.rb'
+ - 'app/graphql/resolvers/projects/snippets_resolver.rb'
+ - 'app/graphql/types/container_repository_details_type.rb'
+ - 'app/graphql/types/container_repository_type.rb'
+ - 'ee/app/graphql/ee/types/query_type.rb'
+ - 'ee/app/graphql/mutations/ai/action.rb'
+ - 'ee/app/graphql/mutations/audit_events/instance_external_audit_event_destinations/base.rb'
+ - 'ee/app/graphql/mutations/issues/set_escalation_policy.rb'
+ - 'ee/app/graphql/mutations/projects/set_locked.rb'
+ - 'ee/app/graphql/resolvers/incident_management/oncall_shifts_resolver.rb'
+ - 'ee/app/graphql/resolvers/product_analytics/visualization_resolver.rb'
+ - 'ee/app/graphql/resolvers/remote_development/workspaces_resolver.rb'
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index f5078962b8f..39eb1d934ce 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -16,6 +16,21 @@ export function initScrollingTabs() {
const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
$scrollingTabs.addClass('is-initialized');
+ const el = $scrollingTabs.get(0);
+ const parentElement = el?.parentNode;
+ if (el && parentElement) {
+ parentElement
+ .querySelector('button.fade-left')
+ .addEventListener('click', function scrollLeft() {
+ el.scrollBy({ left: -200, behavior: 'smooth' });
+ });
+ parentElement
+ .querySelector('button.fade-right')
+ .addEventListener('click', function scrollRight() {
+ el.scrollBy({ left: 200, behavior: 'smooth' });
+ });
+ }
+
$(window)
.on('resize.nav', () => {
hideEndFade($scrollingTabs);
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 15a31fbb3d9..dd1989aa5e1 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -156,6 +156,12 @@
background: linear-gradient(to $gradient-direction,
$gradient-color 45%,
rgba($gradient-color, 0.4));
+ border: 0;
+ padding: 0;
+
+ &:hover {
+ @include gl-focus;
+ }
&.scrolling {
visibility: visible;
@@ -164,8 +170,8 @@
}
svg {
- position: relative;
- top: 5px;
+ position: absolute;
+ top: 12px;
font-size: 18px;
}
}
diff --git a/app/controllers/sent_notifications_controller.rb b/app/controllers/sent_notifications_controller.rb
index 6069924b39a..6c5e709a98a 100644
--- a/app/controllers/sent_notifications_controller.rb
+++ b/app/controllers/sent_notifications_controller.rb
@@ -29,6 +29,10 @@ class SentNotificationsController < ApplicationController
def unsubscribe_and_redirect
noteable.unsubscribe(@sent_notification.recipient, @sent_notification.project)
+ if noteable.is_a?(Issue) && @sent_notification.recipient_id == User.support_bot.id
+ noteable.unsubscribe_email_participant(noteable.external_author)
+ end
+
flash[:notice] = _("You have been unsubscribed from this thread.")
if current_user
diff --git a/app/models/issue.rb b/app/models/issue.rb
index b7125617034..51deb8dea68 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -758,6 +758,12 @@ class Issue < ApplicationRecord
end
end
+ def unsubscribe_email_participant(email)
+ return if email.blank?
+
+ issue_email_participants.find_by_email(email)&.destroy
+ end
+
private
def check_issue_type_in_sync!
diff --git a/app/services/ci/job_artifacts/create_service.rb b/app/services/ci/job_artifacts/create_service.rb
index f7e04c59463..1647e921092 100644
--- a/app/services/ci/job_artifacts/create_service.rb
+++ b/app/services/ci/job_artifacts/create_service.rb
@@ -26,7 +26,8 @@ module Ci
headers = JobArtifactUploader.workhorse_authorize(
has_length: false,
maximum_size: max_size(artifact_type),
- use_final_store_path: Feature.enabled?(:ci_artifacts_upload_to_final_location, project)
+ use_final_store_path: Feature.enabled?(:ci_artifacts_upload_to_final_location, project),
+ final_store_path_root_id: project.id
)
if lsif?(artifact_type)
diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb
index 4188e0caa8e..e1e103e20f8 100644
--- a/app/uploaders/object_storage.rb
+++ b/app/uploaders/object_storage.rb
@@ -11,6 +11,7 @@ module ObjectStorage
RemoteStoreError = Class.new(StandardError)
UnknownStoreError = Class.new(StandardError)
ObjectStorageUnavailable = Class.new(StandardError)
+ MissingFinalStorePathRootId = Class.new(StandardError)
class ExclusiveLeaseTaken < StandardError
def initialize(lease_key)
@@ -153,21 +154,30 @@ module ObjectStorage
[CarrierWave.generate_cache_id, SecureRandom.hex].join('-')
end
- def generate_final_store_path
+ def generate_final_store_path(root_id:)
hash = Digest::SHA2.hexdigest(SecureRandom.uuid)
# We prefix '@final' to prevent clashes and make the files easily recognizable
# as having been created by this code.
- File.join('@final', hash[0..1], hash[2..3], hash[4..])
+ sub_path = File.join('@final', hash[0..1], hash[2..3], hash[4..])
+
+ # We generate a hashed path of the root ID (e.g. Project ID) to distribute directories instead of
+ # filling up one root directory with a bunch of files.
+ Gitlab::HashedPath.new(sub_path, root_hash: root_id).to_s
end
- def workhorse_authorize(has_length:, maximum_size: nil, use_final_store_path: false)
+ def workhorse_authorize(
+ has_length:,
+ maximum_size: nil,
+ use_final_store_path: false,
+ final_store_path_root_id: nil)
{}.tap do |hash|
if self.direct_upload_to_object_store?
hash[:RemoteObject] = workhorse_remote_upload_options(
has_length: has_length,
maximum_size: maximum_size,
- use_final_store_path: use_final_store_path
+ use_final_store_path: use_final_store_path,
+ final_store_path_root_id: final_store_path_root_id
)
else
hash[:TempPath] = workhorse_local_upload_path
@@ -190,11 +200,17 @@ module ObjectStorage
ObjectStorage::Config.new(object_store_options)
end
- def workhorse_remote_upload_options(has_length:, maximum_size: nil, use_final_store_path: false)
+ def workhorse_remote_upload_options(
+ has_length:,
+ maximum_size: nil,
+ use_final_store_path: false,
+ final_store_path_root_id: nil)
return unless direct_upload_to_object_store?
if use_final_store_path
- id = generate_final_store_path
+ raise MissingFinalStorePathRootId unless final_store_path_root_id.present?
+
+ id = generate_final_store_path(root_id: final_store_path_root_id)
upload_path = with_bucket_prefix(id)
prepare_pending_direct_upload(id)
else
diff --git a/app/views/admin/jobs/index.html.haml b/app/views/admin/jobs/index.html.haml
index 7b00019cc21..c5632e0d70b 100644
--- a/app/views/admin/jobs/index.html.haml
+++ b/app/views/admin/jobs/index.html.haml
@@ -10,8 +10,10 @@
- else
.top-area
.scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-min-w-0.gl-w-full
- .fade-left= sprite_icon('chevron-lg-left', size: 12)
- .fade-right= sprite_icon('chevron-lg-right', size: 12)
+ %button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
+ = sprite_icon('chevron-lg-left', size: 12)
+ %button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
+ = sprite_icon('chevron-lg-right', size: 12)
- build_path_proc = ->(scope) { admin_jobs_path(scope: scope) }
= render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index e942a513166..31ec4935f64 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -4,8 +4,10 @@
.top-area.gl-flex-direction-column-reverse
.scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-min-w-0.gl-w-full
- .fade-left= sprite_icon('chevron-lg-left', size: 12)
- .fade-right= sprite_icon('chevron-lg-right', size: 12)
+ %button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
+ = sprite_icon('chevron-lg-left', size: 12)
+ %button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
+ = sprite_icon('chevron-lg-right', size: 12)
= gl_tabs_nav({ class: 'scrolling-tabs nav-links gl-display-flex gl-flex-grow-1 gl-w-full nav gl-tabs-nav nav gl-tabs-nav' }) do
= gl_tab_link_to _('All'), admin_projects_path(visibility_level: nil), { item_active: params[:visibility_level].empty? }
= gl_tab_link_to _('Private'), admin_projects_path(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
diff --git a/app/views/admin/users/_users.html.haml b/app/views/admin/users/_users.html.haml
index c9264535a13..213d5847986 100644
--- a/app/views/admin/users/_users.html.haml
+++ b/app/views/admin/users/_users.html.haml
@@ -9,9 +9,9 @@
.top-area
.scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-min-w-0.gl-w-full
- .fade-left
+ %button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
= sprite_icon('chevron-lg-left', size: 12)
- .fade-right
+ %button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
= sprite_icon('chevron-lg-right', size: 12)
= gl_tabs_nav({ class: 'scrolling-tabs nav-links gl-display-flex gl-flex-grow-1 gl-w-full' }) do
= gl_tab_link_to admin_users_path, { item_active: active_when(params[:filter].nil?), class: 'gl-border-0!' } do
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index e600d84f492..b72b252a852 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -12,8 +12,10 @@
.top-area
.scrolling-tabs-container.inner-page-scroll-tabs.gl-flex-grow-1.gl-flex-basis-0.gl-min-w-0
- .fade-left= sprite_icon('chevron-lg-left', size: 12)
- .fade-right= sprite_icon('chevron-lg-right', size: 12)
+ %button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
+ = sprite_icon('chevron-lg-left', size: 12)
+ %button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
+ = sprite_icon('chevron-lg-right', size: 12)
= render 'dashboard/projects_nav'
.nav-controls
= render 'shared/projects/search_form'
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index 35e8b30e6e9..bec7cb3fd34 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -15,8 +15,10 @@
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
.merge-request-tabs-container.gl-display-flex.gl-justify-content-space-between
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= sprite_icon('chevron-lg-left', size: 12)
- .fade-right= sprite_icon('chevron-lg-right', size: 12)
+ %button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
+ = sprite_icon('chevron-lg-left', size: 12)
+ %button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
+ = sprite_icon('chevron-lg-right', size: 12)
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0.js-tabs-affix
%li.commits-tab.new-tab
= link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do
@@ -32,8 +34,10 @@
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
.merge-request-tabs-container.gl-display-flex.gl-justify-content-space-between
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= sprite_icon('chevron-lg-left', size: 12)
- .fade-right= sprite_icon('chevron-lg-right', size: 12)
+ %button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
+ = sprite_icon('chevron-lg-left', size: 12)
+ %button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
+ = sprite_icon('chevron-lg-right', size: 12)
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0.js-tabs-affix
%li.commits-tab.new-tab
= link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do
diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml
index 03534bf78d1..82b4a314b59 100644
--- a/app/views/shared/_event_filter.html.haml
+++ b/app/views/shared/_event_filter.html.haml
@@ -1,8 +1,10 @@
- show_group_events = local_assigns.fetch(:show_group_events, false)
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.flex-fill
- .fade-left= sprite_icon('chevron-lg-left', size: 12)
- .fade-right= sprite_icon('chevron-lg-right', size: 12)
+ %button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
+ = sprite_icon('chevron-lg-left', size: 12)
+ %button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
+ = sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs
= event_filter_link EventFilter::ALL, _('All'), s_('EventFilterBy|Filter by all')
- if event_filter_visible(:repository)
diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml
index 8c49977fe82..cd1667cb3b3 100644
--- a/app/views/shared/milestones/_tabs.html.haml
+++ b/app/views/shared/milestones/_tabs.html.haml
@@ -1,8 +1,10 @@
- show_project_name = local_assigns.fetch(:show_project_name, false)
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
- .fade-left= sprite_icon('chevron-lg-left', size: 12)
- .fade-right= sprite_icon('chevron-lg-right', size: 12)
+ %button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
+ = sprite_icon('chevron-lg-left', size: 12)
+ %button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
+ = sprite_icon('chevron-lg-right', size: 12)
= gl_tabs_nav({ class: %w[scrolling-tabs js-milestone-tabs] }) do
= gl_tab_link_to '#tab-issues', item_active: true, data: { endpoint: milestone_tab_path(milestone, 'issues', show_project_name: show_project_name) } do
= _('Issues')
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 1ebf02ffd39..4113a276416 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -131,8 +131,10 @@
- if !profile_tabs.empty? && !Feature.enabled?(:profile_tabs_vue, current_user)
.scrolling-tabs-container{ class: [('gl-display-none' if show_super_sidebar?)] }
- .fade-left= sprite_icon('chevron-lg-left', size: 12)
- .fade-right= sprite_icon('chevron-lg-right', size: 12)
+ %button.fade-left{ type: 'button', title: _('Scroll left'), 'aria-label': _('Scroll left') }
+ = sprite_icon('chevron-lg-left', size: 12)
+ %button.fade-right{ type: 'button', title: _('Scroll right'), 'aria-label': _('Scroll right') }
+ = sprite_icon('chevron-lg-right', size: 12)
%ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs.gl-border-b-0
- if profile_tab?(:overview)
%li.js-overview-tab
diff --git a/db/post_migrate/20230516032545_add_unique_notes_id_convert_to_bigint_for_gitlab_com.rb b/db/post_migrate/20230516032545_add_unique_notes_id_convert_to_bigint_for_gitlab_com.rb
new file mode 100644
index 00000000000..330bf23434d
--- /dev/null
+++ b/db/post_migrate/20230516032545_add_unique_notes_id_convert_to_bigint_for_gitlab_com.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class AddUniqueNotesIdConvertToBigintForGitlabCom < Gitlab::Database::Migration[2.1]
+ include Gitlab::Database::MigrationHelpers::ConvertToBigint
+
+ disable_ddl_transaction!
+
+ TABLE_NAME = :notes
+ INDEX_NAME = :index_notes_on_id_convert_to_bigint
+
+ def up
+ return unless should_run?
+
+ # This was created async for GitLab.com with
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119913
+ # and will replace the existing PK index when we swap the integer and bigint columns in
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119705
+ add_concurrent_index TABLE_NAME, :id_convert_to_bigint,
+ unique: true,
+ name: INDEX_NAME
+ end
+
+ def down
+ return unless should_run?
+
+ remove_concurrent_index_by_name(TABLE_NAME, INDEX_NAME)
+ end
+
+ private
+
+ def should_run?
+ com_or_dev_or_test_but_not_jh?
+ end
+end
diff --git a/db/post_migrate/20230516033729_add_referencing_bigint_fks_for_notes_on_gitlab_com.rb b/db/post_migrate/20230516033729_add_referencing_bigint_fks_for_notes_on_gitlab_com.rb
new file mode 100644
index 00000000000..0cc77d43625
--- /dev/null
+++ b/db/post_migrate/20230516033729_add_referencing_bigint_fks_for_notes_on_gitlab_com.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+class AddReferencingBigintFksForNotesOnGitlabCom < Gitlab::Database::Migration[2.1]
+ include Gitlab::Database::MigrationHelpers::ConvertToBigint
+
+ disable_ddl_transaction!
+
+ REFERENCING_FOREIGN_KEYS = [
+ [:todos, :fk_91d1f47b13, :note_id, :cascade],
+ [:incident_management_timeline_events, :fk_d606a2a890, :promoted_from_note_id, :nullify],
+ [:system_note_metadata, :fk_d83a918cb1, :note_id, :cascade],
+ [:diff_note_positions, :fk_rails_13c7212859, :note_id, :cascade],
+ [:epic_user_mentions, :fk_rails_1c65976a49, :note_id, :cascade],
+ [:suggestions, :fk_rails_33b03a535c, :note_id, :cascade],
+ [:issue_user_mentions, :fk_rails_3861d9fefa, :note_id, :cascade],
+ [:note_diff_files, :fk_rails_3d66047aeb, :diff_note_id, :cascade],
+ [:snippet_user_mentions, :fk_rails_4d3f96b2cb, :note_id, :cascade],
+ [:design_user_mentions, :fk_rails_8de8c6d632, :note_id, :cascade],
+ [:vulnerability_user_mentions, :fk_rails_a18600f210, :note_id, :cascade],
+ [:commit_user_mentions, :fk_rails_a6760813e0, :note_id, :cascade],
+ [:merge_request_user_mentions, :fk_rails_c440b9ea31, :note_id, :cascade],
+ [:note_metadata, :fk_rails_d853224d37, :note_id, :cascade],
+ [:alert_management_alert_user_mentions, :fk_rails_eb2de0cdef, :note_id, :cascade],
+ [:timelogs, :fk_timelogs_note_id, :note_id, :nullify]
+ ]
+
+ def up
+ return unless should_run?
+
+ REFERENCING_FOREIGN_KEYS.each do |(from_table, name, column, on_delete)|
+ temporary_name = "#{name}_tmp"
+
+ # This will replace the existing FKs when
+ # we swap the integer and bigint columns in
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/119705
+ add_concurrent_foreign_key(
+ from_table,
+ :notes,
+ column: column,
+ target_column: :id_convert_to_bigint,
+ name: temporary_name,
+ on_delete: on_delete,
+ reverse_lock_order: true,
+ validate: false)
+
+ prepare_async_foreign_key_validation from_table, column, name: temporary_name
+ end
+ end
+
+ def down
+ return unless should_run?
+
+ REFERENCING_FOREIGN_KEYS.each do |(from_table, name, column, _)|
+ temporary_name = "#{name}_tmp"
+
+ unprepare_async_foreign_key_validation from_table, column, name: temporary_name
+
+ with_lock_retries do
+ remove_foreign_key_if_exists(
+ from_table,
+ :notes,
+ name: temporary_name,
+ reverse_lock_order: true
+ )
+ end
+ end
+ end
+
+ private
+
+ def should_run?
+ com_or_dev_or_test_but_not_jh?
+ end
+end
diff --git a/db/schema_migrations/20230516032545 b/db/schema_migrations/20230516032545
new file mode 100644
index 00000000000..d9c5caaa3fd
--- /dev/null
+++ b/db/schema_migrations/20230516032545
@@ -0,0 +1 @@
+5f7e7d5b4af1a2e022e64ba2098c9e6be15853b2242334a41b4d53c5454201fe \ No newline at end of file
diff --git a/db/schema_migrations/20230516033729 b/db/schema_migrations/20230516033729
new file mode 100644
index 00000000000..128f8154d29
--- /dev/null
+++ b/db/schema_migrations/20230516033729
@@ -0,0 +1 @@
+ab3a2fa247fa1170aa38ec0471f136b479d715137138929a4d690f7b6022d022 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index ed00829cb6e..013b0e67c9a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -31559,6 +31559,8 @@ CREATE INDEX index_notes_on_created_at ON notes USING btree (created_at);
CREATE INDEX index_notes_on_discussion_id ON notes USING btree (discussion_id);
+CREATE UNIQUE INDEX index_notes_on_id_convert_to_bigint ON notes USING btree (id_convert_to_bigint);
+
CREATE INDEX index_notes_on_id_where_confidential ON notes USING btree (id) WHERE (confidential = true);
CREATE INDEX index_notes_on_id_where_internal ON notes USING btree (id) WHERE (internal = true);
@@ -35156,6 +35158,9 @@ ALTER TABLE ONLY protected_tags
ALTER TABLE ONLY todos
ADD CONSTRAINT fk_91d1f47b13 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY todos
+ ADD CONSTRAINT fk_91d1f47b13_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY dast_site_profiles_builds
ADD CONSTRAINT fk_94e80df60e FOREIGN KEY (dast_site_profile_id) REFERENCES dast_site_profiles(id) ON DELETE CASCADE;
@@ -35447,6 +35452,9 @@ ALTER TABLE ONLY ci_sources_pipelines
ALTER TABLE ONLY incident_management_timeline_events
ADD CONSTRAINT fk_d606a2a890 FOREIGN KEY (promoted_from_note_id) REFERENCES notes(id) ON DELETE SET NULL;
+ALTER TABLE ONLY incident_management_timeline_events
+ ADD CONSTRAINT fk_d606a2a890_tmp FOREIGN KEY (promoted_from_note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE SET NULL NOT VALID;
+
ALTER TABLE ONLY lists
ADD CONSTRAINT fk_d6cf4279f7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
@@ -35468,6 +35476,9 @@ ALTER TABLE ONLY ci_pipelines
ALTER TABLE ONLY system_note_metadata
ADD CONSTRAINT fk_d83a918cb1 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY system_note_metadata
+ ADD CONSTRAINT fk_d83a918cb1_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY sbom_occurrences
ADD CONSTRAINT fk_d857c6edc1 FOREIGN KEY (component_id) REFERENCES sbom_components(id) ON DELETE CASCADE;
@@ -35783,6 +35794,9 @@ ALTER TABLE ONLY bulk_imports
ALTER TABLE ONLY diff_note_positions
ADD CONSTRAINT fk_rails_13c7212859 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY diff_note_positions
+ ADD CONSTRAINT fk_rails_13c7212859_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY analytics_cycle_analytics_aggregations
ADD CONSTRAINT fk_rails_13c8374c7a FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -35846,6 +35860,9 @@ ALTER TABLE ONLY board_assignees
ALTER TABLE ONLY epic_user_mentions
ADD CONSTRAINT fk_rails_1c65976a49 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY epic_user_mentions
+ ADD CONSTRAINT fk_rails_1c65976a49_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY approver_groups
ADD CONSTRAINT fk_rails_1cdcbd7723 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -36005,6 +36022,9 @@ ALTER TABLE ONLY alert_management_alert_metric_images
ALTER TABLE ONLY suggestions
ADD CONSTRAINT fk_rails_33b03a535c FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY suggestions
+ ADD CONSTRAINT fk_rails_33b03a535c_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY requirements
ADD CONSTRAINT fk_rails_33fed8aa4e FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
@@ -36035,6 +36055,9 @@ ALTER TABLE ONLY packages_debian_project_distribution_keys
ALTER TABLE ONLY issue_user_mentions
ADD CONSTRAINT fk_rails_3861d9fefa FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY issue_user_mentions
+ ADD CONSTRAINT fk_rails_3861d9fefa_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY namespace_settings
ADD CONSTRAINT fk_rails_3896d4fae5 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -36062,6 +36085,9 @@ ALTER TABLE ONLY cluster_groups
ALTER TABLE ONLY note_diff_files
ADD CONSTRAINT fk_rails_3d66047aeb FOREIGN KEY (diff_note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY note_diff_files
+ ADD CONSTRAINT fk_rails_3d66047aeb_tmp FOREIGN KEY (diff_note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY snippet_user_mentions
ADD CONSTRAINT fk_rails_3e00189191 FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE;
@@ -36167,6 +36193,9 @@ ALTER TABLE ONLY scim_identities
ALTER TABLE ONLY snippet_user_mentions
ADD CONSTRAINT fk_rails_4d3f96b2cb FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY snippet_user_mentions
+ ADD CONSTRAINT fk_rails_4d3f96b2cb_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY protected_environment_approval_rules
ADD CONSTRAINT fk_rails_4e554f96f5 FOREIGN KEY (protected_environment_id) REFERENCES protected_environments(id) ON DELETE CASCADE;
@@ -36596,6 +36625,9 @@ ALTER TABLE ONLY approval_merge_request_rules_approved_approvers
ALTER TABLE ONLY design_user_mentions
ADD CONSTRAINT fk_rails_8de8c6d632 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY design_user_mentions
+ ADD CONSTRAINT fk_rails_8de8c6d632_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY clusters_kubernetes_namespaces
ADD CONSTRAINT fk_rails_8df789f3ab FOREIGN KEY (environment_id) REFERENCES environments(id) ON DELETE SET NULL;
@@ -36725,6 +36757,9 @@ ALTER TABLE ONLY project_aliases
ALTER TABLE ONLY vulnerability_user_mentions
ADD CONSTRAINT fk_rails_a18600f210 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY vulnerability_user_mentions
+ ADD CONSTRAINT fk_rails_a18600f210_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY todos
ADD CONSTRAINT fk_rails_a27c483435 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
@@ -36752,6 +36787,9 @@ ALTER TABLE ONLY cluster_projects
ALTER TABLE ONLY commit_user_mentions
ADD CONSTRAINT fk_rails_a6760813e0 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY commit_user_mentions
+ ADD CONSTRAINT fk_rails_a6760813e0_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY vulnerability_identifiers
ADD CONSTRAINT fk_rails_a67a16c885 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -36962,6 +37000,9 @@ ALTER TABLE ONLY project_wiki_repositories
ALTER TABLE ONLY merge_request_user_mentions
ADD CONSTRAINT fk_rails_c440b9ea31 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY merge_request_user_mentions
+ ADD CONSTRAINT fk_rails_c440b9ea31_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY user_achievements
ADD CONSTRAINT fk_rails_c44f5b3b25 FOREIGN KEY (achievement_id) REFERENCES achievements(id) ON DELETE CASCADE;
@@ -37079,6 +37120,9 @@ ALTER TABLE ONLY packages_rpm_metadata
ALTER TABLE ONLY note_metadata
ADD CONSTRAINT fk_rails_d853224d37 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY note_metadata
+ ADD CONSTRAINT fk_rails_d853224d37_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY merge_request_reviewers
ADD CONSTRAINT fk_rails_d9fec24b9d FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE;
@@ -37220,6 +37264,9 @@ ALTER TABLE ONLY protected_branch_unprotect_access_levels
ALTER TABLE ONLY alert_management_alert_user_mentions
ADD CONSTRAINT fk_rails_eb2de0cdef FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
+ALTER TABLE ONLY alert_management_alert_user_mentions
+ ADD CONSTRAINT fk_rails_eb2de0cdef_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE CASCADE NOT VALID;
+
ALTER TABLE ONLY snippet_statistics
ADD CONSTRAINT fk_rails_ebc283ccf1 FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE;
@@ -37370,6 +37417,9 @@ ALTER TABLE ONLY timelogs
ALTER TABLE ONLY timelogs
ADD CONSTRAINT fk_timelogs_note_id FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE SET NULL;
+ALTER TABLE ONLY timelogs
+ ADD CONSTRAINT fk_timelogs_note_id_tmp FOREIGN KEY (note_id) REFERENCES notes(id_convert_to_bigint) ON DELETE SET NULL NOT VALID;
+
ALTER TABLE ONLY u2f_registrations
ADD CONSTRAINT fk_u2f_registrations_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
diff --git a/doc/ci/cloud_services/google_cloud/index.md b/doc/ci/cloud_services/google_cloud/index.md
index 5ed22883518..d99b50b5013 100644
--- a/doc/ci/cloud_services/google_cloud/index.md
+++ b/doc/ci/cloud_services/google_cloud/index.md
@@ -114,6 +114,17 @@ the assertion in the previous section.
After you configure the OIDC and role, the GitLab CI/CD job can retrieve a temporary credential from the
[Google Cloud Security Token Service (STS)](https://cloud.google.com/iam/docs/reference/sts/rest).
+Add `id_tokens` to your CI/CD job:
+
+```yaml
+job:
+ id_tokens:
+ GITLAB_OIDC_TOKEN:
+ aud: https://gitlab.example.com
+```
+
+Get temporary credentials using the ID token:
+
```shell
PAYLOAD="$(cat <<EOF
{
@@ -122,7 +133,7 @@ PAYLOAD="$(cat <<EOF
"requestedTokenType": "urn:ietf:params:oauth:token-type:access_token",
"scope": "https://www.googleapis.com/auth/cloud-platform",
"subjectTokenType": "urn:ietf:params:oauth:token-type:jwt",
- "subjectToken": "${CI_JOB_JWT_V2}"
+ "subjectToken": "${GITLAB_OIDC_TOKEN}"
}
EOF
)"
@@ -142,8 +153,7 @@ Where:
- `PROJECT_NUMBER` is your Google Cloud project number (not name).
- `POOL_ID` is the ID of the Workload Identity Pool created in the first section.
- `PROVIDER_ID` is the ID of the Workload Identity Provider created in the second section.
-- `CI_JOB_JWT_V2` is injected into the CI/CD job by GitLab. For more information about
- this variable, read [Connect to cloud services](../index.md).
+- `GITLAB_OIDC_TOKEN` is an OIDC [ID token](../../yaml/index.md#id_tokens).
You can then use the resulting federated token to impersonate the service account created
in the previous section:
diff --git a/doc/user/application_security/coverage_fuzzing/index.md b/doc/user/application_security/coverage_fuzzing/index.md
index 09370a9a7f5..281ee43dc8e 100644
--- a/doc/user/application_security/coverage_fuzzing/index.md
+++ b/doc/user/application_security/coverage_fuzzing/index.md
@@ -46,12 +46,14 @@ You can use the following fuzzing engines to test the specified languages.
| Go | [go-fuzz (libFuzzer support)](https://github.com/dvyukov/go-fuzz) | [go-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example) |
| Swift | [libFuzzer](https://github.com/apple/swift/blob/master/docs/libFuzzerIntegration.md) | [swift-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/swift-fuzzing-example) |
| Rust | [cargo-fuzz (libFuzzer support)](https://github.com/rust-fuzz/cargo-fuzz) | [rust-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/rust-fuzzing-example) |
-| Java | [Javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz) (recommended) | [javafuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/javafuzz-fuzzing-example) |
+| Java (Maven only)<sup>1</sup> | [Javafuzz](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/javafuzz) (recommended) | [javafuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/javafuzz-fuzzing-example) |
| Java | [JQF](https://github.com/rohanpadhye/JQF) (not preferred) | [jqf-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/java-fuzzing-example) |
| JavaScript | [`jsfuzz`](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/jsfuzz) | [jsfuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/jsfuzz-fuzzing-example) |
| Python | [`pythonfuzz`](https://gitlab.com/gitlab-org/security-products/analyzers/fuzzers/pythonfuzz) | [pythonfuzz-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/pythonfuzz-fuzzing-example) |
| AFL (any language that works on top of AFL) | [AFL](https://lcamtuf.coredump.cx/afl/) | [afl-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/afl-fuzzing-example) |
+1. Support for Gradle is planned in [issue 409764](https://gitlab.com/gitlab-org/gitlab/-/issues/409764).
+
## Confirm status of coverage-guided fuzz testing
To confirm the status of coverage-guided fuzz testing:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 27dc7af7e5c..61bb794f7d9 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -11098,9 +11098,6 @@ msgstr ""
msgid "Committed-before"
msgstr ""
-msgid "CommonJS module"
-msgstr ""
-
msgid "Community forum"
msgstr ""
@@ -16185,9 +16182,6 @@ msgstr ""
msgid "E-mail:"
msgstr ""
-msgid "ESM module"
-msgstr ""
-
msgid "Each project can also have an issue tracker and a wiki."
msgstr ""
@@ -21779,9 +21773,6 @@ msgstr ""
msgid "HAR file path or URL"
msgstr ""
-msgid "HTML script tag"
-msgstr ""
-
msgid "HTTP Archive (HAR)"
msgstr ""
@@ -34090,15 +34081,27 @@ msgstr ""
msgid "Product analytics"
msgstr ""
+msgid "ProductAnalytics|1. Add the NPM package to your package.json using your preferred package manager"
+msgstr ""
+
+msgid "ProductAnalytics|2. Import the new package into your JS code"
+msgstr ""
+
+msgid "ProductAnalytics|3. Initiate the tracking"
+msgstr ""
+
msgid "ProductAnalytics|Add another dimension"
msgstr ""
-msgid "ProductAnalytics|Add the NPM package to your package.json using your preferred package manager:"
+msgid "ProductAnalytics|Add the script to the page and assign the client SDK to window"
msgstr ""
msgid "ProductAnalytics|Add the script to the page and assign the client SDK to window:"
msgstr ""
+msgid "ProductAnalytics|After your application has been instrumented and data is being collected, you can visualize and monitor behaviors in your %{linkStart}analytics dashboards%{linkEnd}."
+msgstr ""
+
msgid "ProductAnalytics|All Clicks Compared"
msgstr ""
@@ -34198,12 +34201,6 @@ msgstr ""
msgid "ProductAnalytics|How often sessions are repeated"
msgstr ""
-msgid "ProductAnalytics|Identifies the sender of tracking events"
-msgstr ""
-
-msgid "ProductAnalytics|Import the new package into your JS code:"
-msgstr ""
-
msgid "ProductAnalytics|Instrument your application"
msgstr ""
@@ -34234,10 +34231,13 @@ msgstr ""
msgid "ProductAnalytics|Repeat Visit Percentage"
msgstr ""
-msgid "ProductAnalytics|SDK App ID"
+msgid "ProductAnalytics|SDK application ID"
+msgstr ""
+
+msgid "ProductAnalytics|SDK clients"
msgstr ""
-msgid "ProductAnalytics|SDK Host"
+msgid "ProductAnalytics|SDK host"
msgstr ""
msgid "ProductAnalytics|Sessions"
@@ -34252,16 +34252,10 @@ msgstr ""
msgid "ProductAnalytics|Set up to track how your product is performing and optimize your product and development processes."
msgstr ""
-msgid "ProductAnalytics|Steps to add product analytics as a CommonJS module"
-msgstr ""
-
-msgid "ProductAnalytics|Steps to add product analytics as a HTML script tag"
-msgstr ""
-
-msgid "ProductAnalytics|Steps to add product analytics as an ESM module"
+msgid "ProductAnalytics|The host to send all tracking events to"
msgstr ""
-msgid "ProductAnalytics|The host to send all tracking events to"
+msgid "ProductAnalytics|The sender of tracking events"
msgstr ""
msgid "ProductAnalytics|There is no data for this type of chart currently. Please see the Setup tab if you have not configured the product analytics tool already."
@@ -34288,12 +34282,18 @@ msgstr ""
msgid "ProductAnalytics|Users"
msgstr ""
+msgid "ProductAnalytics|Using JS module"
+msgstr ""
+
msgid "ProductAnalytics|Waiting for events"
msgstr ""
msgid "ProductAnalytics|What do you want to measure?"
msgstr ""
+msgid "ProductAnalytics|You can instrument your application using a JS module or an HTML script. Follow the instructions below for the option you prefer."
+msgstr ""
+
msgid "Productivity"
msgstr ""
@@ -35314,10 +35314,10 @@ msgstr ""
msgid "ProjectSettings|Configure analytics features for this project"
msgstr ""
-msgid "ProjectSettings|Configure product analytics to track events within your project applications."
+msgid "ProjectSettings|Configure your infrastructure."
msgstr ""
-msgid "ProjectSettings|Configure your infrastructure."
+msgid "ProjectSettings|Connect to your instance"
msgstr ""
msgid "ProjectSettings|Contact an admin to change this setting."
@@ -35419,6 +35419,9 @@ msgstr ""
msgid "ProjectSettings|Infrastructure"
msgstr ""
+msgid "ProjectSettings|Instrument your application to track events and behaviors."
+msgstr ""
+
msgid "ProjectSettings|Internal"
msgstr ""
@@ -35503,6 +35506,9 @@ msgstr ""
msgid "ProjectSettings|Only signed commits can be pushed to this repository."
msgstr ""
+msgid "ProjectSettings|Override instance analytics configuration for this project"
+msgstr ""
+
msgid "ProjectSettings|Override user notification preferences for all project members."
msgstr ""
@@ -35521,6 +35527,9 @@ msgstr ""
msgid "ProjectSettings|Private"
msgstr ""
+msgid "ProjectSettings|Product analytics needs to be set up before your application can be instrumented. Follow the %{link_start}set up process%{link_end}."
+msgstr ""
+
msgid "ProjectSettings|Project visibility"
msgstr ""
@@ -49054,6 +49063,9 @@ msgstr ""
msgid "Using %{code_start}::%{code_end} denotes a %{link_start}scoped label set%{link_end}"
msgstr ""
+msgid "Using HTML script"
+msgstr ""
+
msgid "Using required encryption strategy when encrypted field is missing!"
msgstr ""
diff --git a/rubocop/cop/graphql/resource_not_available_error.rb b/rubocop/cop/graphql/resource_not_available_error.rb
new file mode 100644
index 00000000000..d759e145008
--- /dev/null
+++ b/rubocop/cop/graphql/resource_not_available_error.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require_relative '../../node_pattern_helper'
+
+module RuboCop
+ module Cop
+ module Graphql
+ # Encourages the use of `raise_resource_not_available_error!` method
+ # instead of `raise Gitlab::Graphql::Errors::ResourceNotAvailable`.
+ #
+ # @example
+ #
+ # # bad
+ # raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'message'
+ #
+ # # good
+ # raise_resource_not_available_error! 'message'
+ class ResourceNotAvailableError < Base
+ extend NodePatternHelper
+ extend AutoCorrector
+
+ MSG = 'Prefer using `raise_resource_not_available_error!` instead.'
+
+ EXCEPTION = 'Gitlab::Graphql::Errors::ResourceNotAvailable'
+
+ RESTRICT_ON_SEND = %i[raise].freeze
+
+ def_node_matcher :error, const_pattern(EXCEPTION)
+
+ def_node_matcher :raise_error, <<~PATTERN
+ (send nil? :raise #error $...)
+ PATTERN
+
+ def on_send(node)
+ raise_error(node) do |args|
+ add_offense(node) do |corrector|
+ replacement = +'raise_resource_not_available_error!'
+ replacement << " #{args.map(&:source).join(', ')}" if args.any?
+
+ corrector.replace(node, replacement)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/node_pattern_helper.rb b/rubocop/node_pattern_helper.rb
new file mode 100644
index 00000000000..ecd4560763c
--- /dev/null
+++ b/rubocop/node_pattern_helper.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module NodePatternHelper
+ # Returns a nested `(const ...)` node pattern for a full qualified +name+.
+ #
+ # @examples
+ # const_pattern 'Foo::Bar' # => (const (const {nil? cbase} :Foo) :Bar)
+ # const_pattern 'Foo::Bar', parent: ':Baz' # => (const (const :Baz :Foo) :Bar)
+ def const_pattern(name, parent: '{nil? cbase}')
+ name.split('::').inject(parent) { |memo, name_part| "(const #{memo} :#{name_part})" }
+ end
+ end
+end
diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb
index ec74a902258..e60cf37aad6 100644
--- a/spec/controllers/sent_notifications_controller_spec.rb
+++ b/spec/controllers/sent_notifications_controller_spec.rb
@@ -7,10 +7,12 @@ RSpec.describe SentNotificationsController do
let(:project) { create(:project, :public) }
let(:private_project) { create(:project, :private) }
let(:sent_notification) { create(:sent_notification, project: target_project, noteable: noteable, recipient: user) }
+ let(:email) { 'email@example.com' }
let(:issue) do
- create(:issue, project: target_project) do |issue|
+ create(:issue, project: target_project, external_author: email) do |issue|
issue.subscriptions.create!(user: user, project: target_project, subscribed: true)
+ issue.issue_email_participants.create!(email: email)
end
end
@@ -29,6 +31,14 @@ RSpec.describe SentNotificationsController do
let(:noteable) { issue }
let(:target_project) { project }
+ def force_unsubscribe
+ get(:unsubscribe, params: { id: sent_notification.reply_key, force: true })
+ end
+
+ def unsubscribe
+ get(:unsubscribe, params: { id: sent_notification.reply_key })
+ end
+
describe 'GET unsubscribe' do
shared_examples 'returns 404' do
it 'does not set the flash message' do
@@ -43,13 +53,17 @@ RSpec.describe SentNotificationsController do
context 'when the user is not logged in' do
context 'when the force param is passed' do
before do
- get(:unsubscribe, params: { id: sent_notification.reply_key, force: true })
+ force_unsubscribe
end
it 'unsubscribes the user' do
expect(issue.subscribed?(user, project)).to be_falsey
end
+ it 'does not delete the issue email participant for non-service-desk issue' do
+ expect { force_unsubscribe }.not_to change { issue.issue_email_participants.count }
+ end
+
it 'sets the flash message' do
expect(controller).to set_flash[:notice].to(/unsubscribed/)
end
@@ -63,7 +77,7 @@ RSpec.describe SentNotificationsController do
render_views
before do
- get(:unsubscribe, params: { id: sent_notification.reply_key })
+ unsubscribe
end
shared_examples 'unsubscribing as anonymous' do |project_visibility|
@@ -101,6 +115,10 @@ RSpec.describe SentNotificationsController do
expect(response.body).to include(issue.title)
end
+ it 'does not delete the issue email participant' do
+ expect { unsubscribe }.not_to change { issue.issue_email_participants.count }
+ end
+
it_behaves_like 'unsubscribing as anonymous', :public
end
@@ -171,7 +189,7 @@ RSpec.describe SentNotificationsController do
before do
sent_notification.noteable.destroy!
- get(:unsubscribe, params: { id: sent_notification.reply_key })
+ unsubscribe
end
it_behaves_like 'returns 404'
@@ -193,7 +211,7 @@ RSpec.describe SentNotificationsController do
context 'when the force param is passed' do
before do
- get(:unsubscribe, params: { id: sent_notification.reply_key, force: true })
+ force_unsubscribe
end
it 'unsubscribes the user' do
@@ -220,7 +238,7 @@ RSpec.describe SentNotificationsController do
let(:sent_notification) { create(:sent_notification, project: project, noteable: merge_request, recipient: user) }
before do
- get(:unsubscribe, params: { id: sent_notification.reply_key })
+ unsubscribe
end
it 'unsubscribes the user' do
@@ -243,7 +261,7 @@ RSpec.describe SentNotificationsController do
let(:target_project) { private_project }
before do
- get(:unsubscribe, params: { id: sent_notification.reply_key })
+ unsubscribe
end
it 'unsubscribes user and redirects to root path' do
@@ -257,12 +275,16 @@ RSpec.describe SentNotificationsController do
before do
private_project.add_developer(user)
- get(:unsubscribe, params: { id: sent_notification.reply_key })
+ unsubscribe
end
it 'unsubscribes user and redirects to issue path' do
expect(response).to redirect_to(project_issue_path(private_project, issue))
end
+
+ it 'does not delete the issue email participant for non-service-desk issue' do
+ expect { unsubscribe }.not_to change { issue.issue_email_participants.count }
+ end
end
end
@@ -270,11 +292,27 @@ RSpec.describe SentNotificationsController do
before do
sent_notification.noteable.destroy!
- get(:unsubscribe, params: { id: sent_notification.reply_key })
+ unsubscribe
end
it_behaves_like 'returns 404'
end
+
+ context 'when support bot is the notification recipient' do
+ let(:sent_notification) { create(:sent_notification, project: target_project, noteable: noteable, recipient: User.support_bot) }
+
+ it 'deletes the external author on the issue' do
+ expect { unsubscribe }.to change { issue.issue_email_participants.count }.by(-1)
+ end
+
+ context 'when noteable is not an issue' do
+ let(:noteable) { merge_request }
+
+ it 'does not delete the external author on the issue' do
+ expect { unsubscribe }.not_to change { issue.issue_email_participants.count }
+ end
+ end
+ end
end
end
end
diff --git a/spec/frontend/layout_nav_spec.js b/spec/frontend/layout_nav_spec.js
new file mode 100644
index 00000000000..30f4f7fcac1
--- /dev/null
+++ b/spec/frontend/layout_nav_spec.js
@@ -0,0 +1,39 @@
+import { initScrollingTabs } from '~/layout_nav';
+import { setHTMLFixture } from './__helpers__/fixtures';
+
+describe('initScrollingTabs', () => {
+ const htmlFixture = `
+ <button type='button' class='fade-left'></button>
+ <button type='button' class='fade-right'></button>
+ <div class='scrolling-tabs'></div>
+ `;
+ const findTabs = () => document.querySelector('.scrolling-tabs');
+ const findScrollLeftButton = () => document.querySelector('button.fade-left');
+ const findScrollRightButton = () => document.querySelector('button.fade-right');
+
+ beforeEach(() => {
+ setHTMLFixture(htmlFixture);
+ });
+
+ it('scrolls left when clicking on the left button', () => {
+ initScrollingTabs();
+ const tabs = findTabs();
+ tabs.scrollBy = jest.fn();
+ const fadeLeft = findScrollLeftButton();
+
+ fadeLeft.click();
+
+ expect(tabs.scrollBy).toHaveBeenCalledWith({ left: -200, behavior: 'smooth' });
+ });
+
+ it('scrolls right when clicking on the right button', () => {
+ initScrollingTabs();
+ const tabs = findTabs();
+ tabs.scrollBy = jest.fn();
+ const fadeRight = findScrollRightButton();
+
+ fadeRight.click();
+
+ expect(tabs.scrollBy).toHaveBeenCalledWith({ left: 200, behavior: 'smooth' });
+ });
+});
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 6ae33fe2642..a3d015fd768 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -2002,6 +2002,30 @@ RSpec.describe Issue, feature_category: :team_planning do
it { is_expected.to eq(WorkItems::Type.default_by_type(::Issue::DEFAULT_ISSUE_TYPE)) }
end
+ describe '#unsubscribe_email_participant' do
+ let_it_be(:email) { 'email@example.com' }
+
+ let_it_be(:issue1) do
+ create(:issue, project: reusable_project, external_author: email) do |issue|
+ issue.issue_email_participants.create!(email: email)
+ end
+ end
+
+ let_it_be(:issue2) do
+ create(:issue, project: reusable_project, external_author: email) do |issue|
+ issue.issue_email_participants.create!(email: email)
+ end
+ end
+
+ it 'deletes email for issue1' do
+ expect { issue1.unsubscribe_email_participant(email) }.to change { issue1.issue_email_participants.count }.by(-1)
+ end
+
+ it 'does not delete email for issue2 when issue1 is used' do
+ expect { issue1.unsubscribe_email_participant(email) }.not_to change { issue2.issue_email_participants.count }
+ end
+ end
+
describe 'issue_type enum generated methods' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/rubocop/cop/graphql/resource_not_available_error_spec.rb b/spec/rubocop/cop/graphql/resource_not_available_error_spec.rb
new file mode 100644
index 00000000000..6003b9f3954
--- /dev/null
+++ b/spec/rubocop/cop/graphql/resource_not_available_error_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'rubocop_spec_helper'
+
+require_relative '../../../../rubocop/cop/graphql/resource_not_available_error'
+
+RSpec.describe RuboCop::Cop::Graphql::ResourceNotAvailableError, feature_category: :shared do
+ shared_examples 'flagging and auto-correction' do |exception|
+ it "flags and auto-corrects `raise #{exception}`" do
+ expect_offense(<<~'RUBY', exception: exception)
+ raise %{exception}
+ ^^^^^^^{exception} Prefer using `raise_resource_not_available_error!` instead.
+
+ raise %{exception}, 'message ' \
+ ^^^^^^^{exception}^^^^^^^^^^^^^^ Prefer using `raise_resource_not_available_error!` instead.
+ 'with new lines'
+ RUBY
+
+ expect_correction(<<~'RUBY')
+ raise_resource_not_available_error!
+
+ raise_resource_not_available_error! 'message ' \
+ 'with new lines'
+ RUBY
+ end
+ end
+
+ it_behaves_like 'flagging and auto-correction', 'Gitlab::Graphql::Errors::ResourceNotAvailable'
+ it_behaves_like 'flagging and auto-correction', '::Gitlab::Graphql::Errors::ResourceNotAvailable'
+
+ it 'does not flag unrelated exceptions' do
+ expect_no_offenses(<<~RUBY)
+ raise Gitlab::Graphql::Errors::ResourceVeryAvailable
+ raise ::Gitlab::Graphql::Errors::ResourceVeryAvailable
+ RUBY
+ end
+end
diff --git a/spec/rubocop/node_pattern_helper_spec.rb b/spec/rubocop/node_pattern_helper_spec.rb
new file mode 100644
index 00000000000..a141e81b618
--- /dev/null
+++ b/spec/rubocop/node_pattern_helper_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rubocop_spec_helper'
+
+require_relative '../../rubocop/node_pattern_helper'
+
+RSpec.describe RuboCop::NodePatternHelper, feature_category: :tooling do
+ include described_class
+
+ describe '#const_pattern' do
+ it 'returns nested const node patterns' do
+ expect(const_pattern('Foo')).to eq('(const {nil? cbase} :Foo)')
+ expect(const_pattern('Foo::Bar')).to eq('(const (const {nil? cbase} :Foo) :Bar)')
+ end
+
+ it 'returns nested const node patterns with custom parent' do
+ expect(const_pattern('Foo::Bar', parent: 'nil?')).to eq('(const (const nil? :Foo) :Bar)')
+ end
+ end
+end
diff --git a/spec/services/ci/job_artifacts/create_service_spec.rb b/spec/services/ci/job_artifacts/create_service_spec.rb
index f71d7feb04a..73cb791f145 100644
--- a/spec/services/ci/job_artifacts/create_service_spec.rb
+++ b/spec/services/ci/job_artifacts/create_service_spec.rb
@@ -82,7 +82,11 @@ RSpec.describe Ci::JobArtifacts::CreateService, :clean_gitlab_redis_shared_state
before do
stub_artifacts_object_storage(JobArtifactUploader, direct_upload: true)
- allow(JobArtifactUploader).to receive(:generate_final_store_path).and_return(final_store_path)
+
+ allow(JobArtifactUploader)
+ .to receive(:generate_final_store_path)
+ .with(root_id: project.id)
+ .and_return(final_store_path)
end
it 'includes the authorize headers' do
diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb
index 1566021934a..a748c544bfd 100644
--- a/spec/uploaders/object_storage_spec.rb
+++ b/spec/uploaders/object_storage_spec.rb
@@ -525,12 +525,14 @@ RSpec.describe ObjectStorage, :clean_gitlab_redis_shared_state, feature_category
let(:has_length) { true }
let(:maximum_size) { nil }
let(:use_final_store_path) { false }
+ let(:final_store_path_root_id) { nil }
subject do
uploader_class.workhorse_authorize(
has_length: has_length,
maximum_size: maximum_size,
- use_final_store_path: use_final_store_path
+ use_final_store_path: use_final_store_path,
+ final_store_path_root_id: final_store_path_root_id
)
end
@@ -615,51 +617,30 @@ RSpec.describe ObjectStorage, :clean_gitlab_redis_shared_state, feature_category
shared_examples 'handling object storage final upload path' do |multipart|
context 'when use_final_store_path is true' do
let(:use_final_store_path) { true }
- let(:final_store_path) { File.join('@final', 'abc', '123', 'somefilename') }
+ let(:final_store_path_root_id) { 12345 }
+ let(:final_store_path) { File.join('@final', 'myprefix', 'abc', '123', 'somefilename') }
let(:escaped_path) { escape_path(final_store_path) }
- before do
- stub_object_storage_multipart_init_with_final_store_path("#{storage_url}#{final_store_path}") if multipart
-
- allow(uploader_class).to receive(:generate_final_store_path).and_return(final_store_path)
- end
-
- it 'uses the full path instead of the temporary one' do
- expect(subject[:RemoteObject][:ID]).to eq(final_store_path)
+ context 'and final_store_path_root_id was not given' do
+ let(:final_store_path_root_id) { nil }
- expect(subject[:RemoteObject][:GetURL]).to include(escaped_path)
- expect(subject[:RemoteObject][:StoreURL]).to include(escaped_path)
-
- if multipart
- expect(subject[:RemoteObject][:MultipartUpload][:PartURLs]).to all(include(escaped_path))
- expect(subject[:RemoteObject][:MultipartUpload][:CompleteURL]).to include(escaped_path)
- expect(subject[:RemoteObject][:MultipartUpload][:AbortURL]).to include(escaped_path)
+ it 'raises an error' do
+ expect { subject }.to raise_error(ObjectStorage::MissingFinalStorePathRootId)
end
-
- expect(subject[:RemoteObject][:SkipDelete]).to eq(true)
-
- expect(
- ObjectStorage::PendingDirectUpload.exists?(uploader_class.storage_location_identifier, final_store_path)
- ).to eq(true)
end
- context 'and bucket prefix is configured' do
- let(:prefixed_final_store_path) { "my/prefix/#{final_store_path}" }
- let(:escaped_path) { escape_path(prefixed_final_store_path) }
-
+ context 'and final_store_path_root_id was given' do
before do
- allow(uploader_class.object_store_options).to receive(:bucket_prefix).and_return('my/prefix')
+ stub_object_storage_multipart_init_with_final_store_path("#{storage_url}#{final_store_path}") if multipart
- if multipart
- stub_object_storage_multipart_init_with_final_store_path("#{storage_url}#{prefixed_final_store_path}")
- end
+ allow(uploader_class).to receive(:generate_final_store_path)
+ .with(root_id: final_store_path_root_id)
+ .and_return(final_store_path)
end
- it 'sets the remote object ID to the final path without prefix' do
+ it 'uses the full path instead of the temporary one' do
expect(subject[:RemoteObject][:ID]).to eq(final_store_path)
- end
- it 'returns the final path with prefix' do
expect(subject[:RemoteObject][:GetURL]).to include(escaped_path)
expect(subject[:RemoteObject][:StoreURL]).to include(escaped_path)
@@ -668,15 +649,49 @@ RSpec.describe ObjectStorage, :clean_gitlab_redis_shared_state, feature_category
expect(subject[:RemoteObject][:MultipartUpload][:CompleteURL]).to include(escaped_path)
expect(subject[:RemoteObject][:MultipartUpload][:AbortURL]).to include(escaped_path)
end
- end
- it 'creates the pending upload entry without the prefix' do
- is_expected.to have_key(:RemoteObject)
+ expect(subject[:RemoteObject][:SkipDelete]).to eq(true)
expect(
ObjectStorage::PendingDirectUpload.exists?(uploader_class.storage_location_identifier, final_store_path)
).to eq(true)
end
+
+ context 'and bucket prefix is configured' do
+ let(:prefixed_final_store_path) { "my/prefix/#{final_store_path}" }
+ let(:escaped_path) { escape_path(prefixed_final_store_path) }
+
+ before do
+ allow(uploader_class.object_store_options).to receive(:bucket_prefix).and_return('my/prefix')
+
+ if multipart
+ stub_object_storage_multipart_init_with_final_store_path("#{storage_url}#{prefixed_final_store_path}")
+ end
+ end
+
+ it 'sets the remote object ID to the final path without prefix' do
+ expect(subject[:RemoteObject][:ID]).to eq(final_store_path)
+ end
+
+ it 'returns the final path with prefix' do
+ expect(subject[:RemoteObject][:GetURL]).to include(escaped_path)
+ expect(subject[:RemoteObject][:StoreURL]).to include(escaped_path)
+
+ if multipart
+ expect(subject[:RemoteObject][:MultipartUpload][:PartURLs]).to all(include(escaped_path))
+ expect(subject[:RemoteObject][:MultipartUpload][:CompleteURL]).to include(escaped_path)
+ expect(subject[:RemoteObject][:MultipartUpload][:AbortURL]).to include(escaped_path)
+ end
+ end
+
+ it 'creates the pending upload entry without the bucket prefix' do
+ is_expected.to have_key(:RemoteObject)
+
+ expect(
+ ObjectStorage::PendingDirectUpload.exists?(uploader_class.storage_location_identifier, final_store_path)
+ ).to eq(true)
+ end
+ end
end
end
@@ -716,7 +731,7 @@ RSpec.describe ObjectStorage, :clean_gitlab_redis_shared_state, feature_category
end
before do
- expect_next_instance_of(ObjectStorage::Config) do |instance|
+ allow_next_instance_of(ObjectStorage::Config) do |instance|
allow(instance).to receive(:credentials).and_return(credentials)
end
end
@@ -767,7 +782,7 @@ RSpec.describe ObjectStorage, :clean_gitlab_redis_shared_state, feature_category
end
before do
- expect_next_instance_of(ObjectStorage::Config) do |instance|
+ allow_next_instance_of(ObjectStorage::Config) do |instance|
allow(instance).to receive(:credentials).and_return(credentials)
end
end
@@ -812,7 +827,7 @@ RSpec.describe ObjectStorage, :clean_gitlab_redis_shared_state, feature_category
end
before do
- expect_next_instance_of(ObjectStorage::Config) do |instance|
+ allow_next_instance_of(ObjectStorage::Config) do |instance|
allow(instance).to receive(:credentials).and_return(credentials)
end
end
@@ -1184,14 +1199,17 @@ RSpec.describe ObjectStorage, :clean_gitlab_redis_shared_state, feature_category
end
describe '.generate_final_store_path' do
- subject(:final_path) { uploader_class.generate_final_store_path }
+ let(:root_id) { 12345 }
+ let(:expected_root_hashed_path) { Gitlab::HashedPath.new(root_hash: root_id) }
+
+ subject(:final_path) { uploader_class.generate_final_store_path(root_id: root_id) }
before do
allow(Digest::SHA2).to receive(:hexdigest).and_return('somehash1234')
end
- it 'returns the generated hashed path' do
- expect(final_path).to eq('@final/so/me/hash1234')
+ it 'returns the generated hashed path nested under the hashed path of the root ID' do
+ expect(final_path).to eq(File.join(expected_root_hashed_path, '@final/so/me/hash1234'))
end
end