Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
9bf8cb8d34
commit
75621c94b5
|
|
@ -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._
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
5f7e7d5b4af1a2e022e64ba2098c9e6be15853b2242334a41b4d53c5454201fe
|
||||
|
|
@ -0,0 +1 @@
|
|||
ab3a2fa247fa1170aa38ec0471f136b479d715137138929a4d690f7b6022d022
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 Host"
|
||||
msgid "ProductAnalytics|SDK clients"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|SDK host"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|Sessions"
|
||||
|
|
@ -34252,18 +34252,12 @@ 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"
|
||||
msgstr ""
|
||||
|
||||
msgid "ProductAnalytics|The host to send all tracking events to"
|
||||
msgstr ""
|
||||
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -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 ""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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' });
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
context 'and final_store_path_root_id was not given' do
|
||||
let(:final_store_path_root_id) { nil }
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue