Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-06-25 12:07:29 +00:00
parent 1d107471ad
commit 2b2854cf44
79 changed files with 1597 additions and 719 deletions

View File

@ -1,6 +0,0 @@
---
# Cop supports --autocorrect.
InternalAffairs/NodePatternGroups:
Details: grace period
Exclude:
- 'rubocop/cop/user_admin.rb'

View File

@ -170,6 +170,7 @@ export default {
}"
>
<collapsible-section
:id="list.id"
:count="count"
:has-merge-requests="mergeRequests.length > 0"
:title="list.title"

View File

@ -1,5 +1,6 @@
<script>
import { GlBadge, GlButton, GlTooltipDirective } from '@gitlab/ui';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import { __, sprintf } from '~/locale';
@ -7,12 +8,17 @@ export default {
components: {
GlBadge,
GlButton,
LocalStorageSync,
CrudComponent,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
id: {
type: String,
required: true,
},
title: {
type: String,
required: true,
@ -56,6 +62,7 @@ export default {
data() {
return {
open: true,
savedOpenState: null,
};
},
computed: {
@ -78,6 +85,14 @@ export default {
helpPopoverAriaLabel() {
return sprintf(__('%{list} list help popover'), { list: this.title });
},
storageKey() {
return `mr_list_${this.id}`;
},
isSectionOpen() {
if (this.savedOpenState === null) return this.open;
return this.savedOpenState;
},
},
watch: {
loading(newVal) {
@ -87,60 +102,67 @@ export default {
methods: {
onCollapsedSection() {
this.open = false;
this.savedOpenState = false;
this.$emit('clear-new');
},
onExpandSection() {
this.open = true;
this.savedOpenState = true;
},
},
};
</script>
2
<template>
<crud-component
is-collapsible
:collapsed="!open"
:toggle-aria-label="toggleButtonLabel"
body-class="!gl-mx-0 gl-mb-0"
@collapsed="onCollapsedSection"
@expanded="onExpandSection"
<local-storage-sync
:storage-key="storageKey"
:value="savedOpenState"
@input="(val) => (savedOpenState = val)"
>
<template #title>
{{ title }}
<gl-badge v-if="count !== null" size="sm">{{ count }}</gl-badge>
</template>
<crud-component
is-collapsible
:collapsed="!isSectionOpen"
:toggle-aria-label="toggleButtonLabel"
body-class="!gl-mx-0 gl-mb-0"
@collapsed="onCollapsedSection"
@expanded="onExpandSection"
>
<template #title>
{{ title }}
<gl-badge v-if="count !== null" size="sm">{{ count }}</gl-badge>
</template>
<template #actions>
<gl-badge
v-if="!open && newMergeRequests.length"
:variant="activeList ? 'success' : 'muted'"
class="gl-font-bold"
>
{{ newMergeRequestsBadgeText }}
</gl-badge>
<gl-button
v-gl-tooltip
:title="helpContent"
:aria-label="helpPopoverAriaLabel"
icon="information-o"
variant="link"
class="gl-mr-2 gl-self-center"
/>
</template>
<template #actions>
<gl-badge
v-if="!open && newMergeRequests.length"
:variant="activeList ? 'success' : 'muted'"
class="gl-font-bold"
>
{{ newMergeRequestsBadgeText }}
</gl-badge>
<gl-button
v-gl-tooltip
:title="helpContent"
:aria-label="helpPopoverAriaLabel"
icon="information-o"
variant="link"
class="gl-mr-2 gl-self-center"
/>
</template>
<template v-if="!hasMergeRequests && !loading" #empty>
<p class="gl-pt-1 gl-text-center gl-text-subtle">
{{ __('No merge requests match this list.') }}
</p>
</template>
<template v-if="!hasMergeRequests && !loading" #empty>
<p class="gl-pt-1 gl-text-center gl-text-subtle">
{{ __('No merge requests match this list.') }}
</p>
</template>
<template #default>
<slot></slot>
</template>
<template #default>
<slot></slot>
</template>
<template v-if="open" #pagination>
<slot name="pagination"></slot>
</template>
</crud-component>
<template v-if="open" #pagination>
<slot name="pagination"></slot>
</template>
</crud-component>
</local-storage-sync>
</template>

View File

@ -1,14 +1,14 @@
- if !@lazy
- helpers.add_page_startup_api_call @diffs_stats_endpoint
- helpers.add_page_startup_api_call @diff_files_endpoint
- if @stream_url
- if !lazy?
- helpers.add_page_startup_api_call diffs_stats_endpoint
- helpers.add_page_startup_api_call diff_files_endpoint
- if diffs_stream_url
- helpers.content_for :startup_js do
- javascript_tag nonce: content_security_policy_nonce do
:plain
var controller = new AbortController();
window.gl.rapidDiffsPreload = {
controller: controller,
streamRequest: fetch('#{Gitlab::UrlSanitizer.sanitize(@stream_url)}', { signal: controller.signal })
streamRequest: fetch('#{Gitlab::UrlSanitizer.sanitize(diffs_stream_url)}', { signal: controller.signal })
}
%article.rd-app{ aria: { label: root_label }, data: { rapid_diffs: true, app_data: app_data.to_json } }
@ -24,14 +24,14 @@
-# using label produces better results in VoiceOver than labelledby + hidden h2
%section.rd-app-content{ aria: { label: content_label } }
.rd-app-content-header{ data: { hidden_files_warning: true } }
- if empty_diff? && !@lazy
- if empty_diff? && !lazy?
= render RapidDiffs::EmptyStateComponent.new
.rd-app-code-theme.code{ class: helpers.user_color_scheme }
.rd-app-diffs-list
-# performance optimization: using a sibling element to cover diffs list is faster than changing opacity on the parent
.rd-app-diffs-list-loading-overlay{ data: { diffs_overlay: true } }
%div{ data: { diffs_list: true } }
- if @lazy
- if lazy?
.rd-app-diffs-loading{ data: { testid: 'rd-diffs-list-loading' } }
= helpers.gl_loading_icon(size: 'lg')
- else
@ -41,8 +41,8 @@
- if diffs_list?
= diffs_list
- elsif !empty_diff?
= render RapidDiffs::DiffFileComponent.with_collection(@diffs_slice, parallel_view: parallel_view?)
- if @stream_url
= render RapidDiffs::DiffFileComponent.with_collection(diffs_slice, parallel_view: parallel_view?)
- if diffs_stream_url
%div{ data: { stream_remaining_diffs: true } }
- else
= javascript_tag nonce: content_security_policy_nonce do

View File

@ -4,53 +4,46 @@ module RapidDiffs
class AppComponent < ViewComponent::Base
renders_one :diffs_list
def initialize(
diffs_slice:,
reload_stream_url:,
stream_url:,
show_whitespace:,
diff_view:,
update_user_endpoint:,
diffs_stats_endpoint:,
diff_files_endpoint:,
diff_file_endpoint:,
should_sort_metadata_files: false,
lazy: false
)
@diffs_slice = diffs_slice
@reload_stream_url = reload_stream_url
@stream_url = stream_url
@show_whitespace = show_whitespace
@diff_view = diff_view
@update_user_endpoint = update_user_endpoint
@diffs_stats_endpoint = diffs_stats_endpoint
@diff_files_endpoint = diff_files_endpoint
@should_sort_metadata_files = should_sort_metadata_files
@diff_file_endpoint = diff_file_endpoint
@lazy = lazy
attr_reader :presenter
delegate :diffs_stream_url, :reload_stream_url, :diffs_stats_endpoint, :diff_files_endpoint, :diff_file_endpoint,
:should_sort_metadata_files?, :diffs_slice, :lazy?, to: :presenter
delegate :diff_view, :current_user, to: :helpers
def initialize(presenter)
@presenter = presenter
end
def app_data
{
diffs_stream_url: @stream_url,
reload_stream_url: @reload_stream_url,
diffs_stats_endpoint: @diffs_stats_endpoint,
diff_files_endpoint: @diff_files_endpoint,
should_sort_metadata_files: @should_sort_metadata_files,
show_whitespace: @show_whitespace,
diff_view_type: @diff_view,
diff_file_endpoint: @diff_file_endpoint,
update_user_endpoint: @update_user_endpoint,
lazy: @lazy
diffs_stream_url: diffs_stream_url,
reload_stream_url: reload_stream_url,
diffs_stats_endpoint: diffs_stats_endpoint,
diff_files_endpoint: diff_files_endpoint,
should_sort_metadata_files: should_sort_metadata_files?,
show_whitespace: show_whitespace?,
diff_view_type: diff_view,
diff_file_endpoint: diff_file_endpoint,
update_user_endpoint: update_user_endpoint,
lazy: lazy?
}
end
def update_user_endpoint
helpers.expose_path(helpers.api_v4_user_preferences_path)
end
def show_whitespace?
!helpers.hide_whitespace?
end
def parallel_view?
@diff_view == :parallel
diff_view == :parallel
end
def empty_diff?
@diffs_slice.nil? || @diffs_slice.empty?
diffs_slice.nil? || diffs_slice.empty?
end
def browser_visible?

View File

@ -4,12 +4,6 @@ module RapidDiffs
module Resource
extend ActiveSupport::Concern
def diffs_stream_url(resource, offset = nil, diff_view = nil)
return if offset && offset > resource.diffs_for_streaming.diff_files.count
diffs_stream_resource_url(resource, offset, diff_view)
end
def diff_files_metadata
return render_404 unless rapid_diffs_enabled?
return render_404 unless diffs_resource.present?
@ -77,10 +71,6 @@ module RapidDiffs
end
end
def diffs_stream_resource_url(resource, offset, diff_view)
raise NotImplementedError
end
# When overridden this mthod should return a path to view diffs in an email-friendly format.
def email_format_path
nil

View File

@ -6,13 +6,9 @@ module EventForward
SELF_MANAGED_SUFFIX = 'sm'
def forward
if ::Feature.enabled?(:collect_product_usage_events, :instance)
process_events
process_events
head :ok
else
head :not_found
end
head :ok
end
private

View File

@ -150,14 +150,11 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 unless ::Feature.enabled?(:rapid_diffs, current_user, type: :wip) &&
::Feature.enabled?(:rapid_diffs_on_commit_show, current_user, type: :wip)
streaming_offset = 5
@reload_stream_url = diffs_stream_url(@commit)
@stream_url = diffs_stream_url(@commit, streaming_offset, diff_view)
@diffs_slice = @commit.first_diffs_slice(streaming_offset, commit_diff_options)
@diff_files_endpoint = diff_files_metadata_namespace_project_commit_path
@diff_file_endpoint = diff_file_namespace_project_commit_path
@diffs_stats_endpoint = diffs_stats_namespace_project_commit_path
@update_current_user_path = expose_path(api_v4_user_preferences_path)
@rapid_diffs_presenter = RapidDiffs::CommitPresenter.new(
@commit,
diff_view,
commit_diff_options
)
show
end
@ -277,16 +274,6 @@ class Projects::CommitController < Projects::ApplicationController
payload[:metadata]['meta.diffs_files_count'] = @diffs.size
end
def diffs_stream_resource_url(commit, offset, diff_view)
diffs_stream_namespace_project_commit_path(
namespace_id: commit.project.namespace.to_param,
project_id: commit.project.to_param,
id: commit.id,
offset: offset,
view: diff_view
)
end
def rate_limit_for_expanded_diff_files
return unless diffs_expanded?

View File

@ -81,12 +81,12 @@ class Projects::CompareController < Projects::ApplicationController
return render_404 unless ::Feature.enabled?(:rapid_diffs, current_user, type: :wip) &&
::Feature.enabled?(:rapid_diffs_on_compare_show, current_user, type: :wip)
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
@reload_stream_url = diffs_stream_namespace_project_compare_index_path(**compare_params)
@diff_files_endpoint = diff_files_metadata_namespace_project_compare_index_path(**compare_params)
@diff_file_endpoint = diff_file_namespace_project_compare_index_path(**compare_params)
@diffs_stats_endpoint = diffs_stats_namespace_project_compare_index_path(**compare_params)
@update_current_user_path = expose_path(api_v4_user_preferences_path)
@rapid_diffs_presenter = ::RapidDiffs::ComparePresenter.new(
compare,
diff_view,
diff_options,
compare_params
)
show
end

View File

@ -33,7 +33,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
# load 'rapid_diffs' javascript entrypoint instead of 'new'
@js_action_name = 'rapid_diffs'
define_rapid_diffs_vars
@rapid_diffs_presenter = ::RapidDiffs::MergeRequestCreationPresenter.new(
@merge_request,
project,
diff_view,
diff_options,
{ merge_request: merge_request_params }
)
render action: :rapid_diffs
end
@ -159,16 +165,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
::Feature.enabled?(:rapid_diffs_debug, current_user, type: :ops) && params[:rapid_diffs_disabled] == 'true'
end
def define_rapid_diffs_vars
merge_request = { source_branch: @merge_request.source_branch, target_branch: @merge_request.target_branch }
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
@stream_url = project_new_merge_request_diffs_stream_path(@project, merge_request: merge_request)
@reload_stream_url = project_new_merge_request_diffs_stream_path(@project, merge_request: merge_request)
@diff_files_endpoint = project_new_merge_request_diff_files_metadata_path(@project, merge_request: merge_request)
@diff_file_endpoint = project_new_merge_request_diff_file_path(@project, merge_request: merge_request)
@diffs_stats_endpoint = project_new_merge_request_diffs_stats_path(@project, merge_request: merge_request)
end
# rubocop: disable CodeReuse/ActiveRecord
def selected_target_project
return @project unless @project.forked?

View File

@ -104,13 +104,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def rapid_diffs
return render_404 unless rapid_diffs_page_enabled?
streaming_offset = 5
@reload_stream_url = diffs_stream_url(@merge_request)
@stream_url = diffs_stream_url(@merge_request, streaming_offset, diff_view)
@diffs_slice = @merge_request.first_diffs_slice(streaming_offset, diff_options)
@diff_files_endpoint = diff_files_metadata_namespace_project_merge_request_path
@diff_file_endpoint = diff_file_namespace_project_merge_request_path
@diffs_stats_endpoint = diffs_stats_namespace_project_merge_request_path
@rapid_diffs_presenter = ::RapidDiffs::MergeRequestPresenter.new(
@merge_request,
diff_view,
diff_options
)
show_merge_request
end
@ -678,16 +676,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
payload[:metadata]['meta.diffs_files_count'] = @merge_request.merge_request_diff.files_count
end
def diffs_stream_resource_url(merge_request, offset, diff_view)
diffs_stream_namespace_project_merge_request_path(
id: merge_request.iid,
project_id: merge_request.project.to_param,
namespace_id: merge_request.project.namespace.to_param,
offset: offset,
view: diff_view
)
end
def display_limit_warnings
if @merge_request.reached_versions_limit?
flash[:alert] = format(

View File

@ -295,6 +295,12 @@ module DiffHelper
"#{diff_file.file_hash[0..8]}-heading"
end
def hide_whitespace?
return params[:w] == '1' if params.key?(:w)
current_user.nil? || !current_user.show_whitespace_in_diffs
end
private
def cached_conflicts_with_types
@ -340,12 +346,6 @@ module DiffHelper
toggle_whitespace_link(url, options)
end
def hide_whitespace?
return params[:w] == '1' if params.key?(:w)
current_user.nil? || !current_user.show_whitespace_in_diffs
end
def toggle_whitespace_link(url, options)
toggle_text = hide_whitespace? ? s_('Diffs|Show whitespace changes') : s_('Diffs|Hide whitespace changes')
link_button_to toggle_text, url, class: options[:class]

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
module RapidDiffs
class BasePresenter < Gitlab::View::Presenter::Delegated
def initialize(subject, diff_view, diff_options, request_params = nil)
super(subject)
@diff_view = diff_view
@diff_options = diff_options
@request_params = request_params
end
def diffs_stream_url
return if offset.nil? || offset >= diffs_count
reload_stream_url(offset: offset, diff_view: @diff_view)
end
def reload_stream_url(offset: nil, diff_view: nil)
raise NotImplementedError
end
def diffs_slice
return if offset.nil?
@diffs_slice ||= resource.first_diffs_slice(offset, @diff_options)
end
def diffs_stats_endpoint
raise NotImplementedError
end
def diff_files_endpoint
raise NotImplementedError
end
def diff_file_endpoint
raise NotImplementedError
end
def should_sort_metadata_files?
false
end
def lazy?
offset.nil?
end
protected
attr_reader :request_params
def offset
5
end
private
def diffs_count
@diffs_count ||= resource.diffs_for_streaming.diff_files.count
end
end
end

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module RapidDiffs
class CommitPresenter < BasePresenter
extend ::Gitlab::Utils::Override
presents ::Commit, as: :resource
def diffs_stats_endpoint
diffs_stats_project_commit_path(resource.project, resource.id)
end
def diff_files_endpoint
diff_files_metadata_project_commit_path(resource.project, resource.id)
end
def diff_file_endpoint
diff_file_project_commit_path(resource.project, resource.id)
end
override(:reload_stream_url)
def reload_stream_url(offset: nil, diff_view: nil)
diffs_stream_project_commit_path(
resource.project,
resource.id,
offset: offset,
view: diff_view
)
end
end
end

View File

@ -0,0 +1,38 @@
# frozen_string_literal: true
module RapidDiffs
class ComparePresenter < BasePresenter
extend ::Gitlab::Utils::Override
presents ::Compare, as: :resource
def diffs_stats_endpoint
diffs_stats_project_compare_index_path(resource.project, request_params)
end
def diff_files_endpoint
diff_files_metadata_project_compare_index_path(resource.project, request_params)
end
def diff_file_endpoint
diff_file_project_compare_index_path(resource.project, request_params)
end
override(:reload_stream_url)
def reload_stream_url(offset: nil, diff_view: nil)
diffs_stream_project_compare_index_path(
resource.project,
**request_params,
offset: offset,
view: diff_view
)
end
protected
override(:offset)
def offset
nil
end
end
end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
module RapidDiffs
class MergeRequestCreationPresenter < BasePresenter
extend ::Gitlab::Utils::Override
presents ::MergeRequest, as: :resource
def initialize(subject, project, diff_view, diff_options, request_params = nil)
super(subject, diff_view, diff_options, request_params)
@project = project
end
def diffs_stats_endpoint
project_new_merge_request_diffs_stats_path(@project, request_params)
end
def diff_files_endpoint
project_new_merge_request_diff_files_metadata_path(@project, request_params)
end
def diff_file_endpoint
project_new_merge_request_diff_file_path(@project, request_params)
end
override(:reload_stream_url)
def reload_stream_url(offset: nil, diff_view: nil)
project_new_merge_request_diffs_stream_path(
@project,
**request_params,
offset: offset,
view: diff_view
)
end
protected
override(:offset)
def offset
nil
end
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
module RapidDiffs
class MergeRequestPresenter < BasePresenter
extend ::Gitlab::Utils::Override
presents ::MergeRequest, as: :resource
def diffs_stats_endpoint
diffs_stats_project_merge_request_path(resource.project, resource)
end
def diff_files_endpoint
diff_files_metadata_project_merge_request_path(resource.project, resource)
end
def diff_file_endpoint
diff_file_project_merge_request_path(resource.project, resource)
end
override(:reload_stream_url)
def reload_stream_url(offset: nil, diff_view: nil)
diffs_stream_project_merge_request_path(
resource.project,
resource,
offset: offset,
view: diff_view
)
end
def should_sort_metadata_files?
true
end
end
end

View File

@ -11,7 +11,7 @@
= preload_link_tag(universal_path_to_stylesheet('application_utilities'), as: 'style', crossorigin: css_crossorigin)
= preload_link_tag(universal_path_to_stylesheet('application'), as: 'style', crossorigin: css_crossorigin)
= preload_link_tag(universal_path_to_stylesheet("highlight/themes/#{user_color_scheme}"), as: 'style', crossorigin: css_crossorigin)
- if Gitlab::Tracking.enabled? && Gitlab::Tracking.collector_hostname
- if Gitlab::Tracking.frontend_connect_directly_to_snowplow_collector?
- unless Rails.env.development?
%link{ rel: 'preconnect', href: "https://#{Gitlab::Tracking.collector_hostname}", crossorigin: '' }
-# Do not use preload_link_tag for fonts, to work around Firefox double-fetch bug.

View File

@ -13,5 +13,4 @@
.container-fluid{ class: [container_class] }
= render "commit_box"
= render "ci_menu"
- args = { diffs_slice: @diffs_slice, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: @diff_view, update_user_endpoint: @update_current_user_path, diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint, diff_file_endpoint: @diff_file_endpoint }
= render ::RapidDiffs::AppComponent.new(**args)
= render ::RapidDiffs::AppComponent.new(@rapid_diffs_presenter)

View File

@ -17,8 +17,7 @@
.container-fluid{ class: [container_class] }
= render "projects/commits/commit_list" unless hide_commit_list
.container-fluid
- args = { diffs_slice: nil, reload_stream_url: @reload_stream_url, stream_url: nil, show_whitespace: @show_whitespace_default, diff_view: diff_view, diffs_stats_endpoint: @diffs_stats_endpoint, update_user_endpoint: @update_current_user_path, diff_files_endpoint: @diff_files_endpoint, diff_file_endpoint: @diff_file_endpoint, lazy: true }
= render ::RapidDiffs::AppComponent.new(**args)
= render ::RapidDiffs::AppComponent.new(@rapid_diffs_presenter)
- else
.container-fluid
= render Pajamas::CardComponent.new(card_options: { class: "gl-bg-subtle" }) do |c|

View File

@ -1,5 +1,3 @@
- args = { diffs_slice: @diffs_slice, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: @diff_view, update_user_endpoint: @update_current_user_path, diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint, should_sort_metadata_files: true, diff_file_endpoint: @diff_file_endpoint }
= render ::RapidDiffs::AppComponent.new(**args) do |c|
= render ::RapidDiffs::AppComponent.new(@rapid_diffs_presenter) do |c|
- c.with_diffs_list do
= render RapidDiffs::MergeRequestDiffFileComponent.with_collection(@diffs_slice, merge_request: @merge_request, parallel_view: @diff_view == :parallel)
= render RapidDiffs::MergeRequestDiffFileComponent.with_collection(c.diffs_slice, merge_request: @merge_request, parallel_view: c.diff_view == :parallel)

View File

@ -3,5 +3,4 @@
- add_page_specific_style 'page_bundles/merge_request_creation_rapid_diffs'
= render "page" do
- args = { diffs_slice: nil, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: diff_view, update_user_endpoint: expose_path(api_v4_user_preferences_path), diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint, diff_file_endpoint: @diff_file_endpoint, lazy: true }
= render ::RapidDiffs::AppComponent.new(**args)
= render ::RapidDiffs::AppComponent.new(@rapid_diffs_presenter)

View File

@ -1,9 +0,0 @@
---
name: collect_product_usage_events
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/510317
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182916
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/540574
milestone: '17.10'
group: group::analytics instrumentation
type: beta
default_enabled: true

View File

@ -5,4 +5,4 @@ feature_category: source_code_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180440
milestone: '17.9'
queued_migration_version: 20250205200242
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250623175338'

View File

@ -10,23 +10,6 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111708
milestone: '15.10'
table_size: small
gitlab_schema: gitlab_main_cell
desired_sharding_key:
project_id:
references: projects
backfill_via:
parent:
foreign_key: export_id
table: bulk_import_exports
sharding_key: project_id
belongs_to: export
group_id:
references: namespaces
backfill_via:
parent:
foreign_key: export_id
table: bulk_import_exports
sharding_key: group_id
belongs_to: export
desired_sharding_key_migration_job_name:
- BackfillBulkImportExportBatchesProjectId
- BackfillBulkImportExportBatchesGroupId
sharding_key:
project_id: projects
group_id: namespaces

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
class AddLinkedResourcesWidgetToWorkItemTypesV2 < Gitlab::Database::Migration[2.3]
include Gitlab::Database::MigrationHelpers::WorkItems::Widgets
restrict_gitlab_migration gitlab_schema: :gitlab_main
disable_ddl_transaction!
milestone '18.2'
WORK_ITEM_TYPE_ENUM_VALUES = [0, 1, 4] # issue, incident, task
WIDGETS = [
{
name: 'LinkedResources',
widget_type: 27
}
]
def up
add_widget_definitions(type_enum_values: WORK_ITEM_TYPE_ENUM_VALUES, widgets: WIDGETS)
end
def down
remove_widget_definitions(type_enum_values: WORK_ITEM_TYPE_ENUM_VALUES, widgets: WIDGETS)
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddMultiColumnNotNullConstraintToBulkImportExportBatches < Gitlab::Database::Migration[2.3]
milestone '18.2'
disable_ddl_transaction!
def up
add_multi_column_not_null_constraint(:bulk_import_export_batches, :project_id, :group_id)
end
def down
remove_multi_column_not_null_constraint(:bulk_import_export_batches, :project_id, :group_id)
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class FinalizeBackfillRequiredCodeOwnersSectionsProtectedBranchProjectId < Gitlab::Database::Migration[2.3]
milestone '18.2'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillRequiredCodeOwnersSectionsProtectedBranchProjectId',
table_name: :required_code_owners_sections,
column_name: :id,
job_arguments: [:protected_branch_project_id, :protected_branches, :project_id, :protected_branch_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
11ab2abf5afce30a90368f7eeee7e2f7de7e612feb419a1be4018f325ae8f492

View File

@ -0,0 +1 @@
263565d50e6bad7adf42abbf072c958a53bd8bcd1b6ebcd651b13429dde5d6a8

View File

@ -0,0 +1 @@
d15115f89fb926b7fb789547cd12cd8eeed3d907d17a9e539c53b9849d3099d3

View File

@ -10855,7 +10855,8 @@ CREATE TABLE bulk_import_export_batches (
error text,
project_id bigint,
group_id bigint,
CONSTRAINT check_046dc60dfe CHECK ((char_length(error) <= 255))
CONSTRAINT check_046dc60dfe CHECK ((char_length(error) <= 255)),
CONSTRAINT check_31f6b54459 CHECK ((num_nonnulls(group_id, project_id) = 1))
);
CREATE SEQUENCE bulk_import_export_batches_id_seq

View File

@ -199,6 +199,7 @@ The following API resources are available outside of project and group contexts
| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
| [Namespaces](namespaces.md) | `/namespaces` |
| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
| [Policy settings](policy_settings.md) | `/admin/security/policy_settings` |
| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
| [Personal access tokens](personal_access_tokens.md) | `/personal_access_tokens` |
| [Plan limits](plan_limits.md) | `/application/plan_limits` |

View File

@ -681,7 +681,7 @@ These endpoints are intended for internal use by GitLab, and generally not meant
These endpoints do not adhere to the [REST API authentication methods](rest/authentication.md).
For more information on which headers and token types are supported,
see [Maven package registry](../user/packages/maven_repository/_index.md). Undocumented authentication methods might be removed in the future.
see [Maven virtual registry](../user/packages/virtual_registry/maven/_index.md). Undocumented authentication methods might be removed in the future.
{{< /alert >}}

View File

@ -580,5 +580,7 @@ registries, as they:
- Do not allow users to authenticate to:
- The GitLab [container registry](../user/packages/container_registry/authenticate_with_container_registry.md).
- Packages listed in the GitLab [Package registry](../user/packages/package_registry/_index.md).
- [Virtual registries](../user/packages/virtual_registry/_index.md).
- Allow users to get, list, and delete registries through
the [container registry API](container_registry.md).
- Allow users to get, list, and delete registry objects through the [Maven virtual registry API](maven_virtual_registries.md).

View File

@ -0,0 +1,90 @@
---
stage: Security Risk Management
group: Security Policies
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Policy settings API
---
{{< details >}}
- Tier: Premium, Ultimate
- Offering: GitLab Self-Managed, GitLab Dedicated
{{< /details >}}
{{< history >}}
- [Introduced](https://issue-link) in GitLab 18.2 [with a flag](../administration/feature_flags/_index.md) named `security_policies_csp`. Disabled by default.
{{< /history >}}
{{< alert type="flag" >}}
The availability of this feature is controlled by a feature flag. For more information, see the history.
{{< /alert >}}
Use this API to interact with the security policy settings for your GitLab instance.
Prerequisites:
- You must have administrator access to the instance.
- Your instance must have the Ultimate tier to use security policies.
## Get security policy settings
Gets the current security policy settings for this GitLab instance.
```plaintext
GET /admin/security/policy_settings
```
```shell
curl --request GET \
--header "PRIVATE-TOKEN: <your_access_token>" \
--url "https://gitlab.example.com/api/v4/admin/security/policy_settings"
```
Example response:
```json
{
"csp_namespace_id": 42
}
```
When no CSP namespace is configured:
```json
{
"csp_namespace_id": null
}
```
## Update security policy settings
Updates the security policy settings for this GitLab instance.
```plaintext
PUT /admin/security/policy_settings
```
| Attribute | Type | Required | Description |
|:------------------|:--------|:---------|:------------|
| `csp_namespace_id` | integer | yes | ID of the group designated to centrally manage security policies. Must be a top-level group. Set to `null` to clear the setting. |
```shell
curl --request PUT \
--header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-Type: application/json" \
--data '{"csp_namespace_id": 42}' \
--url "https://gitlab.example.com/api/v4/admin/security/policy_settings"
```
Example response:
```json
{
"csp_namespace_id": 42
}
```

View File

@ -21,7 +21,6 @@ namespaces running jobs on either type of instance runners.
For GitLab-hosted runners:
- You can view your estimated usage in the [GitLab-hosted runner usage dashboard](#view-compute-usage).
- Usage billing is based on build duration logs collected from GitLab-hosted runners.
- Quota enforcement and notifications are not available.
For self-managed instance runners registered to your GitLab Dedicated instance, see [view instance runner usage](instance_runner_compute_minutes.md#view-usage).
@ -44,14 +43,6 @@ You can see compute usage:
- By month, which you can filter by year and runner.
- By namespace, which you can filter by month and runner.
{{< alert type="note" >}}
Compute usage data provides only an estimate of total usage.
Bills are generated directly from the raw runner logs,
which may show discrepancies compared to the usage data in GitLab.
{{< /alert >}}
To view GitLab-hosted runner compute usage for all namespaces across your entire GitLab instance:
1. On the left sidebar, at the bottom, select **Admin**.

View File

@ -2,14 +2,17 @@
stage: Fulfillment
group: Subscription Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
description: Payment and company details.
description: Customers Portal is a comprehensive self-service hub for purchasing and managing GitLab subscriptions and billing.
title: The Customers Portal
---
For some management tasks for your subscription and account, such as purchasing additional seats or storage and viewing invoices, you use the Customers Portal. See the following pages for specific instructions on managing your subscription:
The Customers Portal is your comprehensive self-service hub for managing GitLab subscriptions and billing. You can purchase GitLab products, manage your subscriptions throughout the entire subscription lifecycle, view and pay invoices, and access your billing details and contact information.
See the following pages for specific instructions on managing your subscription:
- [GitLab SaaS subscription](gitlab_com/_index.md)
- [GitLab Self-Managed subscription](self_managed/_index.md)
- [Manage subscription](manage_subscription.md)
If you made your purchase through an authorized reseller, you must contact them directly to make changes to your subscription.
For more information, see [Customers that purchased through a reseller](#customers-that-purchased-through-a-reseller).

View File

@ -451,6 +451,8 @@ You can include additional instructions to be considered. For example:
- Focus on performance, for example `/refactor improving performance`.
- Focus on potential vulnerabilities, for example `/refactor avoiding memory leaks and exploits`.
`/refactor` uses [Repository X-Ray](../project/repository/code_suggestions/repository_xray.md) to deliver more accurate, context-aware suggestions.
For more information, see:
- <i class="fa-youtube-play" aria-hidden="true"></i> [Application modernization with GitLab Duo (C++ to Java)](https://youtu.be/FjoAmt5eeXA?si=SLv9Mv8eSUAVwW5Z).
@ -492,6 +494,8 @@ You can include additional instructions to be considered. For example:
- Focus on code performance problems, for example, `/fix performance problems`.
- Focus on fixing the build when the code does not compile, for example, `/fix the build`.
`/fix` uses [Repository X-Ray](../project/repository/code_suggestions/repository_xray.md) to deliver more accurate, context-aware suggestions.
## Write tests in the IDE
{{< details >}}
@ -527,6 +531,8 @@ You can include additional instructions to be considered. For example:
- Focus on performance, for example `/tests focus on performance`.
- Focus on regressions and potential exploits, for example `/tests focus on regressions and potential exploits`.
`/tests` uses [Repository X-Ray](../project/repository/code_suggestions/repository_xray.md) to deliver more accurate, context-aware suggestions.
For more information, see [Use GitLab Duo Chat in VS Code](_index.md#use-gitlab-duo-chat-in-vs-code).
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch an overview](https://www.youtube.com/watch?v=zWhwuixUkYU)

View File

@ -38,6 +38,7 @@ to the following endpoints:
- GitLab package registry public API.
- [Git commands](https://git-scm.com/docs/gitcredentials#_description).
- [GitLab virtual registry package operations](../../../api/maven_virtual_registries.md#manage-package-operations).
You can create deploy tokens at either the project or group level:

View File

@ -17,6 +17,9 @@ module Gitlab
http_private_token_header
http_header
token_param
private_token_param
job_token_param
access_token_param
].freeze
attr_reader :location
@ -31,21 +34,27 @@ module Gitlab
def extract(request)
case @location
when :http_basic_auth
extract_from_http_basic_auth request
extract_from_http_basic_auth(request)
when :http_token
extract_from_http_token request
extract_from_http_token(request)
when :http_bearer_token
extract_from_http_bearer_token request
extract_from_http_bearer_token(request)
when :http_deploy_token_header
extract_from_http_deploy_token_header request
extract_from_http_deploy_token_header(request)
when :http_job_token_header
extract_from_http_job_token_header request
extract_from_http_job_token_header(request)
when :http_private_token_header
extract_from_http_private_token_header request
extract_from_http_private_token_header(request)
when :http_header
extract_from_http_header request
extract_from_http_header(request)
when :token_param
extract_from_token_param request
extract_from_query_param(request, 'token')
when :private_token_param
extract_from_query_param(request, 'private_token')
when :job_token_param
extract_from_query_param(request, 'job_token')
when :access_token_param
extract_from_query_param(request, 'access_token')
end
end
@ -103,15 +112,15 @@ module Gitlab
UsernameAndPassword.new(nil, password)
end
def extract_from_token_param(request)
password = request.query_parameters['token']
def extract_from_http_header(request)
password = request.headers[@token_identifier]
return unless password.present?
UsernameAndPassword.new(nil, password)
end
def extract_from_http_header(request)
password = request.headers[@token_identifier]
def extract_from_query_param(request, param_name)
password = request.query_parameters[param_name]
return unless password.present?
UsernameAndPassword.new(nil, password)

View File

@ -18,6 +18,7 @@ module Gitlab
personal_access_token_from_jwt
deploy_token_from_jwt
job_token_from_jwt
oauth_token
]
}
@ -45,31 +46,25 @@ module Gitlab
def resolve(raw)
case @token_type
when :personal_access_token
resolve_personal_access_token raw
resolve_personal_access_token(raw)
when :job_token
resolve_job_token raw
resolve_job_token(raw)
when :deploy_token
resolve_deploy_token raw
resolve_deploy_token(raw)
when :personal_access_token_with_username
resolve_personal_access_token_with_username raw
resolve_personal_access_token_with_username(raw)
when :job_token_with_username
resolve_job_token_with_username raw
resolve_job_token_with_username(raw)
when :deploy_token_with_username
resolve_deploy_token_with_username raw
resolve_deploy_token_with_username(raw)
when :personal_access_token_from_jwt
resolve_personal_access_token_from_jwt raw
resolve_personal_access_token_from_jwt(raw)
when :deploy_token_from_jwt
resolve_deploy_token_from_jwt raw
resolve_deploy_token_from_jwt(raw)
when :job_token_from_jwt
resolve_job_token_from_jwt raw
resolve_job_token_from_jwt(raw)
when :oauth_token
resolve_oauth_token(raw)
end
end
@ -157,6 +152,19 @@ module Gitlab
end
end
def resolve_oauth_token(raw)
oauth_token = OauthAccessToken.by_token(raw.password)
raise ::Gitlab::Auth::UnauthorizedError unless oauth_token
oauth_token.revoke_previous_refresh_token!
::Gitlab::Auth::Identity.link_from_oauth_token(oauth_token).tap do |identity|
raise ::Gitlab::Auth::UnauthorizedError if identity && !identity.valid?
end
oauth_token
end
def with_personal_access_token(raw, &block)
pat = ::PersonalAccessToken.find_by_token(raw.password)
return unless pat

View File

@ -23,6 +23,7 @@ module Gitlab
current_user_todos: 'Current user todos',
award_emoji: 'Award emoji',
linked_items: 'Linked items',
linked_resources: 'Linked Resources',
color: 'Color',
participants: 'Participants',
time_tracking: 'Time tracking',
@ -60,6 +61,7 @@ module Gitlab
:start_and_due_date,
:time_tracking,
:vulnerabilities,
:linked_resources,
[:weight, { editable: true, rollup: false }],
:status
],
@ -76,6 +78,7 @@ module Gitlab
:iteration,
:labels,
:linked_items,
:linked_resources,
:milestone,
:notes,
:notifications,
@ -119,6 +122,7 @@ module Gitlab
:iteration,
:labels,
:linked_items,
:linked_resources,
:milestone,
:notes,
:notifications,

View File

@ -12,6 +12,10 @@ module Gitlab
tracker.enabled?
end
def frontend_connect_directly_to_snowplow_collector?
Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank?
end
def micro_verification_enabled?
Gitlab::Utils.to_boolean(ENV['VERIFY_TRACKING'], default: false)
end

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
module Gitlab
module Tracking
module Destinations
class DestinationConfiguration
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT = 'https://events.gitlab.net'
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT_STG = 'https://events-stg.gitlab.net'
SNOWPLOW_MICRO_DEFAULT_URI = 'http://localhost:9090'
class << self
def snowplow_configuration
new(snowplow_uri)
end
def snowplow_micro_configuration
new(snowplow_micro_uri)
end
private
def snowplow_micro_uri
url = Gitlab.config.snowplow_micro.address
scheme = Gitlab.config.gitlab.https ? 'https' : 'http'
URI("#{scheme}://#{url}")
rescue GitlabSettings::MissingSetting
URI(SNOWPLOW_MICRO_DEFAULT_URI)
end
def snowplow_uri
if Gitlab::CurrentSettings.snowplow_enabled?
hostname = Gitlab::CurrentSettings.snowplow_collector_hostname
addressable_uri = Addressable::URI.heuristic_parse(convert_if_bare_hostname(hostname), scheme: 'https')
URI(addressable_uri.to_s)
elsif Feature.enabled?(:use_staging_endpoint_for_product_usage_events, :instance)
URI(PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT_STG)
else
URI(PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT)
end
end
def convert_if_bare_hostname(hostname)
return hostname if hostname.blank? || hostname.include?('/') || hostname.include?('.')
"//#{hostname}"
end
end
attr_reader :uri
def initialize(collector_uri)
@uri = collector_uri
end
def hostname
return uri.host unless uri.port
return uri.host if default_port?
"#{uri.host}:#{uri.port}"
end
def port
uri.port
end
def protocol
uri.scheme
end
private
def default_port?
(uri.scheme == 'https' && uri.port == 443) ||
(uri.scheme == 'http' && uri.port == 80)
end
end
end
end
end

View File

@ -7,12 +7,15 @@ module Gitlab
module Destinations
class Snowplow
SNOWPLOW_NAMESPACE = 'gl'
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT = 'events.gitlab.net'
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT_STG = 'events-stg.gitlab.net'
DEDICATED_APP_ID = 'gitlab_dedicated'
SELF_MANAGED_APP_ID = 'gitlab_sm'
def initialize
delegate :hostname, :uri, :protocol, to: :@destination_configuration
attr_reader :destination_configuration
def initialize(destination_configuration = DestinationConfiguration.snowplow_configuration)
@destination_configuration = destination_configuration
@event_eligibility_checker = Gitlab::Tracking::EventEligibilityChecker.new
return if batching_disabled?
@ -42,7 +45,7 @@ module Gitlab
end
def frontend_client_options(group)
if Gitlab::CurrentSettings.snowplow_enabled? || ::Feature.disabled?(:collect_product_usage_events, :instance)
if Gitlab::CurrentSettings.snowplow_enabled?
snowplow_options(group)
else
product_usage_events_options
@ -50,18 +53,7 @@ module Gitlab
end
def enabled?
Gitlab::CurrentSettings.snowplow_enabled? ||
::Feature.enabled?(:collect_product_usage_events, :instance)
end
def hostname
if Gitlab::CurrentSettings.snowplow_enabled?
Gitlab::CurrentSettings.snowplow_collector_hostname
elsif Feature.enabled?(:use_staging_endpoint_for_product_usage_events, :instance)
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT_STG
else
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT
end
true
end
def app_id
@ -111,10 +103,6 @@ module Gitlab
Gitlab::Utils.to_boolean(ENV['GITLAB_DISABLE_PRODUCT_USAGE_EVENT_LOGGING'], default: false)
end
def protocol
'https'
end
def cookie_domain
Gitlab::CurrentSettings.snowplow_cookie_domain
end

View File

@ -12,6 +12,10 @@ module Gitlab
COOKIE_DOMAIN = '.gitlab.com'
DEFAULT_URI = 'http://localhost:9090'
def initialize
super(DestinationConfiguration.snowplow_micro_configuration)
end
override :snowplow_options
def snowplow_options(group)
# Using camel case as these keys will be used only in JavaScript
@ -27,27 +31,6 @@ module Gitlab
def enabled?
true
end
override :hostname
def hostname
"#{uri.host}:#{uri.port}"
end
def uri
url = Gitlab.config.snowplow_micro.address
scheme = Gitlab.config.gitlab.https ? 'https' : 'http'
URI("#{scheme}://#{url}")
rescue GitlabSettings::MissingSetting
URI(DEFAULT_URI)
end
strong_memoize_attr :uri
private
override :protocol
def protocol
uri.scheme
end
end
end
end

View File

@ -15,11 +15,7 @@ module Gitlab
end
def eligible?(_event, _app_id = nil)
if ::Feature.enabled?(:collect_product_usage_events, :instance)
snowplow_enabled? || send_usage_data?
else
snowplow_enabled?
end
snowplow_enabled? || send_usage_data?
end
private

View File

@ -68,7 +68,7 @@
"@gitlab/ui": "114.8.1",
"@gitlab/vue-router-vue3": "npm:vue-router@4.5.1",
"@gitlab/vuex-vue3": "npm:vuex@4.1.0",
"@gitlab/web-ide": "^0.0.1-dev-20250618150607",
"@gitlab/web-ide": "^0.0.1-dev-20250625063230",
"@gleam-lang/highlight.js-gleam": "^1.5.0",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@rails/actioncable": "7.1.501",

View File

@ -10,7 +10,7 @@ module RuboCop
'with `User#can_admin_all_resources?` or `User#can_read_all_resources?`.'
def_node_matcher :admin_call?, <<~PATTERN
({send | csend} _ :admin? ...)
(call _ :admin? ...)
PATTERN
def on_send(node)

View File

@ -3,11 +3,11 @@
require "spec_helper"
RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :code_review_workflow do
let(:diffs_slice) { Array.new(2) { build(:diff_file) } }
let(:stream_url) { '/stream' }
let_it_be(:diffs_slice) { Array.new(2, build(:diff_file)) }
let(:diffs_stream_url) { '/stream' }
let(:reload_stream_url) { '/reload_stream' }
let(:show_whitespace) { true }
let(:diff_view) { 'inline' }
let(:diff_view) { :inline }
let(:update_user_endpoint) { '/update_user' }
let(:diffs_stats_endpoint) { '/diffs_stats' }
let(:diff_files_endpoint) { '/diff_files_metadata' }
@ -15,6 +15,34 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
let(:should_sort_metadata_files) { false }
let(:lazy) { false }
let(:diff_presenter) do
instance_double(
::RapidDiffs::BasePresenter,
diffs_slice: diffs_slice,
diffs_stream_url: diffs_stream_url,
reload_stream_url: reload_stream_url,
diffs_stats_endpoint: diffs_stats_endpoint,
diff_files_endpoint: diff_files_endpoint,
diff_file_endpoint: diff_file_endpoint,
should_sort_metadata_files?: should_sort_metadata_files,
lazy?: lazy
)
end
subject(:component) { described_class.new(diff_presenter) }
before do
allow(component).to receive(:helpers).and_wrap_original do |original_method, *args|
helpers = original_method.call(*args)
allow(helpers).to receive_messages(
hide_whitespace?: !show_whitespace,
diff_view: diff_view,
api_v4_user_preferences_path: update_user_endpoint
)
helpers
end
end
it "renders app" do
render_component
expect(page).to have_css('[data-rapid-diffs]')
@ -29,14 +57,14 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
render_component
app = page.find('[data-rapid-diffs]')
data = Gitlab::Json.parse(app['data-app-data'])
expect(data['diffs_stream_url']).to eq(diffs_stream_url)
expect(data['reload_stream_url']).to eq(reload_stream_url)
expect(data['diffs_stats_endpoint']).to eq(diffs_stats_endpoint)
expect(data['diff_files_endpoint']).to eq(diff_files_endpoint)
expect(data['show_whitespace']).to eq(show_whitespace)
expect(data['diff_view_type']).to eq(diff_view)
expect(data['update_user_endpoint']).to eq(update_user_endpoint)
expect(data['diffs_stream_url']).to eq(stream_url)
expect(data['diff_file_endpoint']).to eq(diff_file_endpoint)
expect(data['update_user_endpoint']).to eq(update_user_endpoint)
expect(data['show_whitespace']).to eq(show_whitespace)
expect(data['diff_view_type']).to eq(diff_view.to_s)
expect(data['lazy']).to eq(lazy)
end
@ -109,10 +137,9 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
end
it 'preloads' do
instance = create_instance
render_inline(instance)
expect(instance.helpers.page_startup_api_calls).to include(diffs_stats_endpoint)
expect(instance.helpers.page_startup_api_calls).to include(diff_files_endpoint)
render_component
expect(component.helpers.page_startup_api_calls).to include(diffs_stats_endpoint)
expect(component.helpers.page_startup_api_calls).to include(diff_files_endpoint)
expect(vc_test_controller.view_context.content_for?(:startup_js)).not_to be_nil
end
@ -143,23 +170,7 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
end
end
def create_instance
described_class.new(
diffs_slice:,
stream_url:,
reload_stream_url:,
show_whitespace:,
diff_view:,
update_user_endpoint:,
diffs_stats_endpoint:,
diff_files_endpoint:,
diff_file_endpoint:,
should_sort_metadata_files:,
lazy:
)
end
def render_component(&block)
render_inline(create_instance, &block)
render_inline(component, &block)
end
end

View File

@ -3,9 +3,9 @@
require "spec_helper"
RSpec.shared_context "with diff file component tests" do
let_it_be(:diff_file) { build(:diff_file) }
let(:web_component_selector) { 'diff-file' }
let(:web_component) { page.find(web_component_selector) }
let(:diff_file) { build(:diff_file) }
let(:repository) { diff_file.repository }
let(:project) { repository.container }
let(:namespace) { project.namespace }

View File

@ -7,14 +7,6 @@ RSpec.describe RapidDiffs::Resource, type: :controller, feature_category: :sourc
Class.new(ApplicationController) do
include RapidDiffs::Resource
def call_diffs_stream_resource_url(resource, offset, diff_view)
diffs_stream_resource_url(resource, offset, diff_view)
end
def call_diffs_stream_url(resource, offset, diff_view)
diffs_stream_url(resource, offset, diff_view)
end
def call_diffs_resource
diffs_resource
end
@ -47,24 +39,6 @@ RSpec.describe RapidDiffs::Resource, type: :controller, feature_category: :sourc
let_it_be(:project) { create(:project, :repository) }
let_it_be(:commit) { project.commit_by(oid: sha) }
describe '#diffs_stream_resource_url' do
it 'raises NotImplementedError' do
expect do
controller.new.call_diffs_stream_resource_url(commit, offset, diff_view)
end.to raise_error(NotImplementedError)
end
end
describe '#diffs_stream_url' do
context 'when offset is greater than the number of diffs' do
let_it_be(:offset) { 9999 }
it 'returns nil' do
expect(controller.new.call_diffs_stream_url(commit, offset, diff_view)).to be_nil
end
end
end
describe '#diffs_resource' do
it 'raises NotImplementedError' do
expect do

View File

@ -1,83 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Merge request dashboard collapsible section renders section 1`] = `
<section
class="crud gl-bg-subtle gl-border gl-border-section gl-mt-5 gl-rounded-lg"
id=""
<local-storage-sync-stub
persist="true"
storagekey="mr_list_list"
>
<header
class="crud-header gl-bg-section gl-border-b gl-border-section gl-flex gl-flex-wrap gl-gap-x-5 gl-gap-y-2 gl-justify-between gl-p-4 gl-pl-5 gl-pr-10 gl-relative gl-rounded-t-lg md:gl-flex-nowrap"
<section
class="crud gl-bg-subtle gl-border gl-border-section gl-mt-5 gl-rounded-lg"
id="reference-0"
>
<div
class="gl-flex gl-flex-col gl-grow gl-self-center"
<header
class="crud-header gl-bg-section gl-border-b gl-border-section gl-flex gl-flex-wrap gl-gap-x-5 gl-gap-y-2 gl-justify-between gl-p-4 gl-pl-5 gl-pr-10 gl-relative gl-rounded-t-lg md:gl-flex-nowrap"
>
<h2
class="gl-font-bold gl-gap-3 gl-inline-flex gl-items-center gl-leading-normal gl-m-0 gl-text-base"
data-testid="crud-title"
>
Approved
<gl-badge-stub
iconsize="md"
size="sm"
tag="span"
target="_self"
variant="muted"
>
3
</gl-badge-stub>
</h2>
</div>
<div
class="gl-flex gl-gap-3 gl-items-center"
data-testid="crud-actions"
>
<gl-button-stub
aria-label="Approved list help popover"
buttontextclasses=""
category="primary"
class="gl-mr-2 gl-self-center"
icon="information-o"
size="medium"
tag="button"
title=""
type="button"
variant="link"
/>
<div
class="gl-absolute gl-border-l gl-border-l-section gl-h-6 gl-pl-3 gl-right-5 gl-top-4"
class="gl-flex gl-flex-col gl-grow gl-self-center"
>
<h2
class="gl-font-bold gl-gap-3 gl-inline-flex gl-items-center gl-leading-normal gl-m-0 gl-text-base"
data-testid="crud-title"
>
Approved
<gl-badge-stub
iconsize="md"
size="sm"
tag="span"
target="_self"
variant="muted"
>
3
</gl-badge-stub>
</h2>
</div>
<div
class="gl-flex gl-gap-3 gl-items-center"
data-testid="crud-actions"
>
<gl-button-stub
aria-controls=""
aria-expanded="true"
aria-label="Collapse"
aria-label="Approved list help popover"
buttontextclasses=""
category="tertiary"
class="-gl-mr-2 btn-icon gl-self-start"
data-testid="crud-collapse-toggle"
icon=""
size="small"
category="primary"
class="gl-mr-2 gl-self-center"
icon="information-o"
size="medium"
tag="button"
title="Collapse"
title=""
type="button"
variant="default"
variant="link"
/>
<div
class="gl-absolute gl-border-l gl-border-l-section gl-h-6 gl-pl-3 gl-right-5 gl-top-4"
>
<gl-animated-chevron-lg-down-up-icon-stub
is-on="true"
<gl-button-stub
aria-controls=""
aria-expanded="true"
aria-label="Collapse"
buttontextclasses=""
category="tertiary"
class="-gl-mr-2 btn-icon gl-self-start"
data-testid="crud-collapse-toggle"
icon=""
size="small"
tag="button"
title="Collapse"
type="button"
variant="default"
/>
</gl-button-stub>
>
<gl-animated-chevron-lg-down-up-icon-stub
is-on="true"
variant="default"
/>
</gl-button-stub>
</div>
</div>
</div>
</header>
<div
class="!gl-mx-0 crud-body gl-mb-0 gl-mx-5 gl-my-4 gl-rounded-b-lg"
data-testid="crud-body"
>
content
</header>
<div
class="crud-pagination gl-border-t gl-border-t-section gl-flex gl-justify-center gl-p-5"
data-testid="crud-pagination"
/>
</div>
</section>
class="!gl-mx-0 crud-body gl-mb-0 gl-mx-5 gl-my-4 gl-rounded-b-lg"
data-testid="crud-body"
>
content
<div
class="crud-pagination gl-border-t gl-border-t-section gl-flex gl-justify-center gl-p-5"
data-testid="crud-pagination"
/>
</div>
</section>
</local-storage-sync-stub>
`;

View File

@ -1,5 +1,6 @@
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import CrudComponent from '~/vue_shared/components/crud_component.vue';
import CollapsibleSection from '~/merge_request_dashboard/components/collapsible_section.vue';
@ -9,6 +10,7 @@ describe('Merge request dashboard collapsible section', () => {
const collapseToggle = () => wrapper.findByTestId('crud-collapse-toggle');
const sectionContent = () => wrapper.findByTestId('crud-body');
const emptyState = () => wrapper.findByTestId('crud-empty');
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
function createComponent({ count = 3, hasMergeRequests = count > 0, loading = false } = {}) {
wrapper = shallowMountExtended(CollapsibleSection, {
@ -16,6 +18,7 @@ describe('Merge request dashboard collapsible section', () => {
default: 'content',
},
propsData: {
id: 'list',
title: 'Approved',
count,
hasMergeRequests,
@ -74,4 +77,16 @@ describe('Merge request dashboard collapsible section', () => {
expect(sectionContent().exists()).toBe(true);
});
describe('collapsed state sync', () => {
it('collapses content when local storage value is set to false', async () => {
createComponent({ count: 1 });
findLocalStorageSync().vm.$emit('input', false);
await nextTick();
expect(sectionContent().exists()).toBe(false);
});
});
});

View File

@ -864,4 +864,40 @@ RSpec.describe DiffHelper, feature_category: :code_review_workflow do
it { is_expected.to eq("#{diff_file.file_hash[0..8]}-heading") }
end
describe "#hide_whitespace?" do
subject { helper.hide_whitespace? }
context 'when request has w param set' do
before do
allow(controller).to receive(:params) { { w: '1' } }
end
it { is_expected.to be(true) }
end
context 'when user is guest' do
before do
allow(helper).to receive(:current_user).and_return(nil)
end
it { is_expected.to be(true) }
end
context 'when user has preference' do
before do
allow(helper).to receive_message_chain(:current_user, :show_whitespace_in_diffs).and_return(true)
end
it { is_expected.to be(false) }
context 'when request has w param set' do
before do
allow(controller).to receive(:params) { { w: '1' } }
end
it { is_expected.to be(true) }
end
end
end
end

View File

@ -178,23 +178,30 @@ RSpec.describe Gitlab::APIAuthentication::TokenLocator, feature_category: :syste
end
end
context 'with :token_param' do
let(:type) { :token_param }
%i[
token
private_token
job_token
access_token
].each do |token_type|
context "with :#{token_type}_param" do
let(:type) { :"#{token_type}_param" }
context 'without credentials' do
let(:request) { double(query_parameters: {}) }
context 'without credentials' do
let(:request) { double(query_parameters: {}) }
it 'returns nil' do
expect(subject).to be_nil
it 'returns nil' do
expect(subject).to be_nil
end
end
end
context 'with credentials' do
let(:password) { 'bar' }
let(:request) { double(query_parameters: { 'token' => password }) }
context 'with credentials' do
let(:password) { 'bar' }
let(:request) { double(query_parameters: { token_type.to_s => password }) }
it 'returns the credentials' do
expect(subject.password).to eq(password)
it 'returns the credentials' do
expect(subject.password).to eq(password)
end
end
end
end

View File

@ -9,23 +9,9 @@ RSpec.describe Gitlab::APIAuthentication::TokenResolver, feature_category: :syst
let_it_be(:ci_job) { create(:ci_build, project: project, user: user, status: :running) }
let_it_be(:ci_job_done) { create(:ci_build, project: project, user: user, status: :success) }
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
shared_examples 'an authorized request' do
it 'returns the correct token' do
expect(subject).to eq(token)
end
end
shared_examples 'an unauthorized request' do
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
shared_examples 'an anoymous request' do
it 'returns nil' do
expect(subject).to eq(nil)
end
let_it_be(:oauth_application) { create(:oauth_application, owner: user) }
let_it_be(:oauth_token) do
create(:oauth_access_token, application_id: oauth_application.id, resource_owner_id: user.id, scopes: [:api])
end
describe '.new' do
@ -45,7 +31,25 @@ RSpec.describe Gitlab::APIAuthentication::TokenResolver, feature_category: :syst
describe '#resolve' do
let(:resolver) { described_class.new(type) }
subject { resolver.resolve(raw) }
subject(:resolve) { resolver.resolve(raw) }
shared_examples 'an authorized request' do
it 'returns the correct token' do
expect(resolve).to eq(token)
end
end
shared_examples 'an unauthorized request' do
it 'raises an error' do
expect { resolve }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
shared_examples 'an anoymous request' do
it 'returns nil' do
expect(resolve).to eq(nil)
end
end
context 'with :personal_access_token_with_username' do
let(:type) { :personal_access_token_with_username }
@ -217,6 +221,32 @@ RSpec.describe Gitlab::APIAuthentication::TokenResolver, feature_category: :syst
it_behaves_like 'an unauthorized request'
end
end
context 'with :oauth_token' do
let(:type) { :oauth_token }
let(:token) { oauth_token }
context 'with valid credentials' do
let(:raw) { username_and_password(nil, oauth_token.plaintext_token) }
it_behaves_like 'an authorized request'
it 'refreshes token expiry and links the composite identity' do
expect_next_found_instance_of(OauthAccessToken) do |token|
expect(token).to receive(:revoke_previous_refresh_token!)
expect(::Gitlab::Auth::Identity).to receive(:link_from_oauth_token).with(token)
end
resolve
end
end
context 'with invalid credentials' do
let(:raw) { username_and_password(nil, 'invalid') }
it_behaves_like 'an unauthorized request'
end
end
end
def username_and_password(username, password)

View File

@ -0,0 +1,289 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Tracking::Destinations::DestinationConfiguration, feature_category: :application_instrumentation do
include StubENV
describe '.snowplow_configuration' do
subject(:configuration) { described_class.snowplow_configuration }
context 'when snowplow is enabled' do
before do
stub_application_setting(snowplow_enabled?: true)
stub_application_setting(snowplow_collector_hostname: 'gitfoo.com')
end
it 'returns configuration with snowplow collector hostname' do
expect(configuration.hostname).to eq('gitfoo.com')
expect(configuration.protocol).to eq('https')
expect(configuration.uri).to be_a(URI::Generic)
end
end
context 'when snowplow is disabled' do
before do
stub_application_setting(snowplow_enabled?: false)
end
context 'with use_staging_endpoint_for_product_usage_events FF disabled' do
before do
stub_feature_flags(use_staging_endpoint_for_product_usage_events: false)
end
it 'returns configuration with production endpoint' do
expect(configuration.hostname).to eq('events.gitlab.net')
expect(configuration.protocol).to eq('https')
expect(configuration.uri.to_s).to eq(described_class::PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT)
end
end
context 'with use_staging_endpoint_for_product_usage_events FF enabled' do
before do
stub_feature_flags(use_staging_endpoint_for_product_usage_events: true)
end
it 'returns configuration with staging endpoint' do
expect(configuration.hostname).to eq('events-stg.gitlab.net')
expect(configuration.protocol).to eq('https')
expect(configuration.uri.to_s).to eq(described_class::PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT_STG)
end
end
end
end
describe '.snowplow_micro_configuration' do
subject(:configuration) { described_class.snowplow_micro_configuration }
context 'when snowplow_micro config is set' do
let(:snowplow_micro_settings) do
{
enabled: true,
address: '127.0.0.1:9091'
}
end
before do
stub_config(snowplow_micro: snowplow_micro_settings)
end
it 'returns configuration with snowplow micro URI' do
expect(configuration.hostname).to eq('127.0.0.1:9091')
expect(configuration.port).to eq(9091)
expect(configuration.protocol).to eq('http')
end
context 'when gitlab config has https scheme' do
before do
stub_config_setting(https: true)
end
it 'returns configuration with https scheme' do
expect(configuration.hostname).to eq('127.0.0.1:9091')
expect(configuration.protocol).to eq('https')
end
end
end
context 'when snowplow_micro config is not set' do
before do
allow(Gitlab.config).to receive(:snowplow_micro).and_raise(GitlabSettings::MissingSetting)
end
it 'returns configuration with default localhost URI' do
expect(configuration.hostname).to eq('localhost:9090')
expect(configuration.port).to eq(9090)
expect(configuration.protocol).to eq('http')
expect(configuration.uri.to_s).to eq(described_class::SNOWPLOW_MICRO_DEFAULT_URI)
end
end
end
describe '#initialize' do
let(:uri) { URI('https://example.com:8080') }
subject(:configuration) { described_class.new(uri) }
it 'sets the URI' do
expect(configuration.uri).to eq(uri)
end
end
describe '#hostname' do
context 'when URI has no explicit port' do
context 'with HTTPS scheme' do
let(:uri) { URI('https://example.com') }
subject(:configuration) { described_class.new(uri) }
it 'returns just the host (implicit port 443)' do
expect(configuration.hostname).to eq('example.com')
expect(uri.port).to eq(443) # Verify URI sets default port
end
end
context 'with HTTP scheme' do
let(:uri) { URI('http://example.com') }
subject(:configuration) { described_class.new(uri) }
it 'returns just the host (implicit port 80)' do
expect(configuration.hostname).to eq('example.com')
expect(uri.port).to eq(80) # Verify URI sets default port
end
end
end
context 'when URI has explicit default ports' do
context 'with HTTPS and explicit port 443' do
let(:uri) { URI('https://example.com:443') }
subject(:configuration) { described_class.new(uri) }
it 'returns just the host without port' do
expect(configuration.hostname).to eq('example.com')
end
end
context 'with HTTP and explicit port 80' do
let(:uri) { URI('http://example.com:80') }
subject(:configuration) { described_class.new(uri) }
it 'returns just the host without port' do
expect(configuration.hostname).to eq('example.com')
end
end
end
context 'when URI has non-default ports' do
context 'with HTTPS and custom port' do
let(:uri) { URI('https://example.com:8080') }
subject(:configuration) { described_class.new(uri) }
it 'returns host with port' do
expect(configuration.hostname).to eq('example.com:8080')
end
end
context 'with HTTP and custom port' do
let(:uri) { URI('http://example.com:9090') }
subject(:configuration) { described_class.new(uri) }
it 'returns host with port' do
expect(configuration.hostname).to eq('example.com:9090')
end
end
context 'with HTTPS using HTTP default port' do
let(:uri) { URI('https://example.com:80') }
subject(:configuration) { described_class.new(uri) }
it 'returns host with port (80 is not default for HTTPS)' do
expect(configuration.hostname).to eq('example.com:80')
end
end
context 'with HTTP using HTTPS default port' do
let(:uri) { URI('http://example.com:443') }
subject(:configuration) { described_class.new(uri) }
it 'returns host with port (443 is not default for HTTP)' do
expect(configuration.hostname).to eq('example.com:443')
end
end
end
end
describe '#port' do
context 'when URI has explicit port' do
let(:uri) { URI('https://example.com:8080') }
subject(:configuration) { described_class.new(uri) }
it 'returns the explicit port from URI' do
expect(configuration.port).to eq(8080)
end
end
context 'when URI has implicit default ports' do
context 'with HTTPS scheme' do
let(:uri) { URI('https://example.com') }
subject(:configuration) { described_class.new(uri) }
it 'returns the default HTTPS port (443)' do
expect(configuration.port).to eq(443)
end
end
context 'with HTTP scheme' do
let(:uri) { URI('http://example.com') }
subject(:configuration) { described_class.new(uri) }
it 'returns the default HTTP port (80)' do
expect(configuration.port).to eq(80)
end
end
end
context 'when URI has explicit default ports' do
context 'with HTTPS and explicit port 443' do
let(:uri) { URI('https://example.com:443') }
subject(:configuration) { described_class.new(uri) }
it 'returns the explicit default port' do
expect(configuration.port).to eq(443)
end
end
context 'with HTTP and explicit port 80' do
let(:uri) { URI('http://example.com:80') }
subject(:configuration) { described_class.new(uri) }
it 'returns the explicit default port' do
expect(configuration.port).to eq(80)
end
end
end
context 'with cross-scheme port scenarios' do
context 'with HTTPS using HTTP default port' do
let(:uri) { URI('https://example.com:80') }
subject(:configuration) { described_class.new(uri) }
it 'returns port 80 (non-default for HTTPS)' do
expect(configuration.port).to eq(80)
end
end
context 'with HTTP using HTTPS default port' do
let(:uri) { URI('http://example.com:443') }
subject(:configuration) { described_class.new(uri) }
it 'returns port 443 (non-default for HTTP)' do
expect(configuration.port).to eq(443)
end
end
end
end
describe '#protocol' do
let(:uri) { URI('https://example.com') }
subject(:configuration) { described_class.new(uri) }
it 'returns the scheme from URI' do
expect(configuration.protocol).to eq('https')
end
end
end

View File

@ -20,48 +20,11 @@ RSpec.describe Gitlab::Tracking::Destinations::SnowplowMicro, feature_category:
it { is_expected.to delegate_method(:flush).to(:tracker) }
describe '#hostname' do
context 'when snowplow_micro config is set' do
let(:address) { '127.0.0.1:9091' }
before do
stub_config(snowplow_micro: snowplow_micro_settings)
end
it 'returns proper URI' do
expect(subject.hostname).to eq('127.0.0.1:9091')
expect(subject.uri.scheme).to eq('http')
end
context 'when gitlab config has https scheme' do
before do
stub_config_setting(https: true)
end
it 'returns proper URI' do
expect(subject.hostname).to eq('127.0.0.1:9091')
expect(subject.uri.scheme).to eq('https')
end
end
end
context 'when snowplow_micro config is not set' do
before do
allow(Gitlab.config).to receive(:snowplow_micro).and_raise(GitlabSettings::MissingSetting)
end
it 'returns localhost hostname' do
expect(subject.hostname).to eq('localhost:9090')
end
end
end
describe '#snowplow_options' do
let_it_be(:group) { create :group }
before do
stub_config(snowplow_micro: snowplow_micro_settings)
stub_application_setting(snowplow_enabled?: true)
end
it 'adds Snowplow micro specific options to the parent Snowplow options' do
@ -88,51 +51,4 @@ RSpec.describe Gitlab::Tracking::Destinations::SnowplowMicro, feature_category:
expect(options).to include(base_options)
end
end
describe '#frontend_client_options' do
let_it_be(:group) { create :group }
before do
stub_config(snowplow_micro: snowplow_micro_settings)
end
context 'when snowplow is enabled' do
before do
stub_application_setting(snowplow_enabled?: true)
stub_feature_flags(additional_snowplow_tracking: true)
end
it 'includes snowplow_options with Snowplow micro-specific overrides' do
expect(subject).to receive(:snowplow_options).with(group).and_call_original
options = subject.frontend_client_options(group)
expect(options).to include(
protocol: 'http',
port: 9091,
forceSecureTracker: false
)
end
end
context 'when snowplow is disabled' do
before do
stub_application_setting(snowplow_enabled?: false)
allow(Gitlab).to receive(:host_with_port).and_return('gitlab.example.com')
allow(Gitlab.config.gitlab).to receive(:https).and_return(true)
end
it 'returns product_usage_events options' do
expect(subject).not_to receive(:snowplow_options)
options = subject.frontend_client_options(group)
expect(options).to include(
hostname: 'gitlab.example.com',
postPath: '/-/collect_events',
forceSecureTracker: true
)
end
end
end
end

View File

@ -43,6 +43,10 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_b
describe '#event' do
context 'when event is eligible' do
before do
allow_next_instance_of(Gitlab::Tracking::Destinations::DestinationConfiguration) do |config|
allow(config).to receive_messages(hostname: 'gitfoo.com', protocol: 'https')
end
expect(SnowplowTracker::AsyncEmitter)
.to receive(:new)
.with(endpoint: 'gitfoo.com',
@ -115,6 +119,10 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_b
allow(SnowplowTracker::Tracker).to receive(:new).and_return(tracker)
allow(tracker).to receive(:track_struct_event).and_call_original
allow_next_instance_of(Gitlab::Tracking::Destinations::DestinationConfiguration) do |config|
allow(config).to receive_messages(hostname: 'gitfoo.com', protocol: 'https')
end
expect(SnowplowTracker::AsyncEmitter)
.to receive(:new)
.with(endpoint: 'gitfoo.com',
@ -249,86 +257,39 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_b
stub_application_setting(snowplow_enabled?: false)
end
context 'and collect_product_usage_events is enabled' do
it 'returns true' do
expect(subject.enabled?).to be_truthy
end
end
context 'and collect_product_usage_events is disabled' do
before do
stub_feature_flags(collect_product_usage_events: false)
end
it 'returns false' do
expect(subject.enabled?).to be_falsey
end
it 'returns true' do
expect(subject.enabled?).to be_truthy
end
end
end
describe '#hostname' do
describe '#app_id' do
subject { described_class.new.app_id }
context 'when snowplow is enabled' do
before do
stub_application_setting(snowplow_enabled?: true)
end
it 'returns snowplow_collector_hostname' do
expect(subject.hostname).to eq('gitfoo.com')
end
it { is_expected.to eq('_abc123_') }
end
context 'when snowplow is disabled' do
before do
stub_application_setting(snowplow_enabled?: false)
stub_feature_flags(use_staging_endpoint_for_product_usage_events: enable_stg_events)
stub_application_setting(gitlab_dedicated_instance?: dedicated_instance)
end
context "with use_staging_endpoint_for_product_usage_events FF disabled" do
let(:enable_stg_events) { false }
context 'when dedicated instance' do
let(:dedicated_instance) { true }
it 'returns product usage event collection hostname' do
expect(subject.hostname).to eq('events.gitlab.net')
end
it { is_expected.to eq('gitlab_dedicated') }
end
context "with use_staging_endpoint_for_product_usage_events FF enabled" do
let(:enable_stg_events) { true }
context 'when self-hosted instance' do
let(:dedicated_instance) { false }
it 'returns product usage event collection hostname' do
expect(subject.hostname).to eq('events-stg.gitlab.net')
end
end
end
describe '#app_id' do
subject { described_class.new.app_id }
context 'when snowplow is enabled' do
before do
stub_application_setting(snowplow_enabled?: true)
end
it { is_expected.to eq('_abc123_') }
end
context 'when snowplow is disabled' do
before do
stub_application_setting(snowplow_enabled?: false)
stub_application_setting(gitlab_dedicated_instance?: dedicated_instance)
end
context 'when dedicated instance' do
let(:dedicated_instance) { true }
it { is_expected.to eq('gitlab_dedicated') }
end
context 'when self-hosted instance' do
let(:dedicated_instance) { false }
it { is_expected.to eq('gitlab_sm') }
end
it { is_expected.to eq('gitlab_sm') }
end
end
end

View File

@ -12,42 +12,21 @@ RSpec.describe Gitlab::Tracking::EventEligibilityChecker, feature_category: :ser
subject { checker.eligible?(event_name) }
context 'when collect_product_usage_events feature flag is enabled' do
where(:product_usage_data_enabled, :snowplow_enabled, :result) do
true | false | true
false | true | true
false | false | false
end
before do
stub_application_setting(
snowplow_enabled: snowplow_enabled,
gitlab_product_usage_data_enabled?: product_usage_data_enabled
)
end
with_them do
it { is_expected.to eq(result) }
end
where(:product_usage_data_enabled, :snowplow_enabled, :result) do
true | false | true
false | true | true
false | false | false
end
context 'when collect_product_usage_events feature flag is disabled' do
where(:product_usage_data_enabled, :snowplow_enabled, :result) do
true | false | false
false | true | true
false | false | false
end
before do
stub_application_setting(
snowplow_enabled: snowplow_enabled,
gitlab_product_usage_data_enabled?: product_usage_data_enabled
)
end
before do
stub_feature_flags(collect_product_usage_events: false)
stub_application_setting(
snowplow_enabled?: snowplow_enabled, gitlab_product_usage_data_enabled?: product_usage_data_enabled
)
end
with_them do
it { is_expected.to eq(result) }
end
with_them do
it { is_expected.to eq(result) }
end
end
@ -62,7 +41,6 @@ RSpec.describe Gitlab::Tracking::EventEligibilityChecker, feature_category: :ser
end
before do
stub_feature_flags(collect_product_usage_events: false)
stub_application_setting(
snowplow_enabled?: snowplow_enabled, gitlab_product_usage_data_enabled?: product_usage_data_enabled
)

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddLinkedResourcesWidgetToWorkItemTypesV2, :migration, feature_category: :team_planning do
it_behaves_like 'migration that adds widgets to a work item type'
end

View File

@ -244,6 +244,7 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
instance_of(WorkItems::Widgets::Hierarchy),
instance_of(WorkItems::Widgets::Labels),
instance_of(WorkItems::Widgets::LinkedItems),
instance_of(WorkItems::Widgets::LinkedResources),
instance_of(WorkItems::Widgets::Milestone),
instance_of(WorkItems::Widgets::Notes),
instance_of(WorkItems::Widgets::Notifications),
@ -293,6 +294,7 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
instance_of(WorkItems::Widgets::Hierarchy),
instance_of(WorkItems::Widgets::Labels),
instance_of(WorkItems::Widgets::LinkedItems),
instance_of(WorkItems::Widgets::LinkedResources),
instance_of(WorkItems::Widgets::Notes),
instance_of(WorkItems::Widgets::Notifications),
instance_of(WorkItems::Widgets::Participants),

View File

@ -16,6 +16,7 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do
::WorkItems::Widgets::CurrentUserTodos,
::WorkItems::Widgets::AwardEmoji,
::WorkItems::Widgets::LinkedItems,
::WorkItems::Widgets::LinkedResources,
::WorkItems::Widgets::Participants,
::WorkItems::Widgets::TimeTracking,
::WorkItems::Widgets::Designs,
@ -129,9 +130,7 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do
end
describe '.widget_classes' do
# Excluding LinkedResources as the linked_resources widget is still not added to any work item type
# Follow-up will add the widget to the types as part of https://gitlab.com/gitlab-org/gitlab/-/issues/372482
subject { described_class.widget_classes - [::WorkItems::Widgets::LinkedResources] }
subject { described_class.widget_classes }
it 'returns all widget classes no matter if disabled or not' do
is_expected.to match_array(all_widget_classes)

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe RapidDiffs::BasePresenter, feature_category: :source_code_management do
subject(:presenter) { described_class.new(Class.new, :inline, {}) }
describe 'abstract methods' do
it 'raises a NotImplementedError for #diffs_stats_endpoint' do
expect { presenter.diffs_stats_endpoint }.to raise_error(NotImplementedError)
end
it 'raises a NotImplementedError for #diff_files_endpoint' do
expect { presenter.diff_files_endpoint }.to raise_error(NotImplementedError)
end
it 'raises a NotImplementedError for #diff_file_endpoint' do
expect { presenter.diff_file_endpoint }.to raise_error(NotImplementedError)
end
it 'raises a NotImplementedError for #reload_stream_url' do
expect { presenter.send(:reload_stream_url) }.to raise_error(NotImplementedError)
end
end
end

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::RapidDiffs::CommitPresenter, feature_category: :source_code_management do
let_it_be(:commit) { build_stubbed(:commit) }
let_it_be(:project) { commit.project }
let_it_be(:namespace) { project.namespace }
let(:diff_view) { :inline }
let(:diff_options) { { ignore_whitespace_changes: true } }
let(:diffs_count) { 20 }
let(:base_path) { "/#{namespace.to_param}/#{project.to_param}/-/commit/#{commit.sha}" }
subject(:presenter) { described_class.new(commit, diff_view, diff_options) }
before do
allow(commit).to receive_message_chain(:diffs_for_streaming, :diff_files, :count).and_return(diffs_count)
end
describe '#diffs_slice' do
let(:offset) { presenter.send(:offset) }
it 'calls first_diffs_slice on the commit with the correct arguments' do
expect(commit).to receive(:first_diffs_slice).with(offset, diff_options)
presenter.diffs_slice
end
end
describe '#diffs_stats_endpoint' do
subject(:url) { presenter.diffs_stats_endpoint }
it { is_expected.to eq("#{base_path}/diffs_stats") }
end
describe '#diff_files_endpoint' do
subject(:url) { presenter.diff_files_endpoint }
it { is_expected.to eq("#{base_path}/diff_files_metadata") }
end
describe '#diff_file_endpoint' do
subject(:url) { presenter.diff_file_endpoint }
it { is_expected.to eq("#{base_path}/diff_file") }
end
describe 'stream urls' do
describe '#diffs_stream_url' do
subject(:url) { presenter.diffs_stream_url }
it { is_expected.to eq("#{base_path}/diffs_stream?offset=5&view=inline") }
context 'when diffs count is the same as streaming offset' do
let(:diffs_count) { 5 }
it { is_expected.to be_nil }
end
end
describe '#reload_stream_url' do
subject(:url) { presenter.reload_stream_url }
it { is_expected.to eq("#{base_path}/diffs_stream") }
end
end
describe '#lazy?' do
subject(:method) { presenter.lazy? }
it { is_expected.to be(false) }
end
describe '#should_sort_metadata_files?' do
subject(:method) { presenter.should_sort_metadata_files? }
it { is_expected.to be(false) }
end
end

View File

@ -0,0 +1,73 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::RapidDiffs::ComparePresenter, feature_category: :source_code_management do
let_it_be(:project) { build_stubbed(:project) }
let(:compare) { instance_double(Compare, project: project) }
let(:namespace) { project.namespace }
let(:diff_view) { :inline }
let(:diff_options) { { ignore_whitespace_changes: true } }
let(:request_params) { { from: 'a', to: 'b' } }
let(:base_path) { "/#{namespace.to_param}/#{project.to_param}/-/compare" }
let(:url_params) { '?from=a&to=b' }
subject(:presenter) { described_class.new(compare, diff_view, diff_options, request_params) }
describe '#diffs_slice' do
subject(:diffs_slice) { presenter.diffs_slice }
it { is_expected.to be_nil }
end
describe '#diffs_stats_endpoint' do
subject(:url) { presenter.diffs_stats_endpoint }
it { is_expected.to eq("#{base_path}/diffs_stats#{url_params}") }
end
describe '#diff_files_endpoint' do
subject(:url) { presenter.diff_files_endpoint }
it { is_expected.to eq("#{base_path}/diff_files_metadata#{url_params}") }
end
describe '#diff_file_endpoint' do
subject(:url) { presenter.diff_file_endpoint }
it { is_expected.to eq("#{base_path}/diff_file#{url_params}") }
end
describe 'stream urls' do
describe '#diffs_stream_url' do
subject(:url) { presenter.diffs_stream_url }
it { is_expected.to be_nil }
end
describe '#reload_stream_url' do
subject(:url) { presenter.reload_stream_url }
it { is_expected.to eq("#{base_path}/diffs_stream#{url_params}") }
end
end
describe '#lazy?' do
subject(:method) { presenter.lazy? }
it { is_expected.to be(true) }
end
describe '#should_sort_metadata_files?' do
subject(:method) { presenter.should_sort_metadata_files? }
it { is_expected.to be(false) }
end
# this method is tested only because code coverage can not detect its usage because of overrides
describe '#offset' do
subject(:method) { presenter.send(:offset) }
it { is_expected.to be_nil }
end
end

View File

@ -0,0 +1,73 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::RapidDiffs::MergeRequestCreationPresenter, feature_category: :code_review_workflow do
let_it_be(:merge_request) { build_stubbed(:merge_request) }
let_it_be(:project) { merge_request.project }
let_it_be(:namespace) { project.namespace }
let(:diff_view) { :inline }
let(:diff_options) { { ignore_whitespace_changes: true } }
let(:request_params) { { source_branch: 'a', target_branch: 'b' } }
let(:base_path) { "/#{namespace.to_param}/#{project.to_param}/-/merge_requests/new" }
let(:url_params) { '?source_branch=a&target_branch=b' }
subject(:presenter) { described_class.new(merge_request, project, diff_view, diff_options, request_params) }
describe '#diffs_slice' do
subject(:diffs_slice) { presenter.diffs_slice }
it { is_expected.to be_nil }
end
describe '#diffs_stats_endpoint' do
subject(:url) { presenter.diffs_stats_endpoint }
it { is_expected.to eq("#{base_path}/diffs_stats#{url_params}") }
end
describe '#diff_files_endpoint' do
subject(:url) { presenter.diff_files_endpoint }
it { is_expected.to eq("#{base_path}/diff_files_metadata#{url_params}") }
end
describe '#diff_file_endpoint' do
subject(:url) { presenter.diff_file_endpoint }
it { is_expected.to eq("#{base_path}/diff_file#{url_params}") }
end
describe 'stream urls' do
describe '#diffs_stream_url' do
subject(:url) { presenter.diffs_stream_url }
it { is_expected.to be_nil }
end
describe '#reload_stream_url' do
subject(:url) { presenter.reload_stream_url }
it { is_expected.to eq("#{base_path}/diffs_stream#{url_params}") }
end
end
describe '#lazy?' do
subject(:method) { presenter.lazy? }
it { is_expected.to be(true) }
end
describe '#should_sort_metadata_files?' do
subject(:method) { presenter.should_sort_metadata_files? }
it { is_expected.to be(false) }
end
# this method is tested only because code coverage can not detect its usage because of overrides
describe '#offset' do
subject(:method) { presenter.send(:offset) }
it { is_expected.to be_nil }
end
end

View File

@ -0,0 +1,79 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::RapidDiffs::MergeRequestPresenter, feature_category: :code_review_workflow do
let_it_be(:merge_request) { build_stubbed(:merge_request) }
let_it_be(:project) { merge_request.project }
let_it_be(:namespace) { project.namespace }
let(:diff_view) { :inline }
let(:diff_options) { { ignore_whitespace_changes: true } }
let(:diffs_count) { 20 }
let(:base_path) { "/#{namespace.to_param}/#{project.to_param}/-/merge_requests/#{merge_request.to_param}" }
subject(:presenter) { described_class.new(merge_request, diff_view, diff_options) }
before do
allow(merge_request).to receive_message_chain(:diffs_for_streaming, :diff_files, :count).and_return(diffs_count)
end
describe '#diffs_slice' do
let(:offset) { presenter.send(:offset) }
it 'calls first_diffs_slice on the merge_request with the correct arguments' do
expect(merge_request).to receive(:first_diffs_slice).with(offset, diff_options)
presenter.diffs_slice
end
end
describe '#diffs_stats_endpoint' do
subject(:url) { presenter.diffs_stats_endpoint }
it { is_expected.to eq("#{base_path}/diffs_stats") }
end
describe '#diff_files_endpoint' do
subject(:url) { presenter.diff_files_endpoint }
it { is_expected.to eq("#{base_path}/diff_files_metadata") }
end
describe '#diff_file_endpoint' do
subject(:url) { presenter.diff_file_endpoint }
it { is_expected.to eq("#{base_path}/diff_file") }
end
describe 'stream urls' do
describe '#diffs_stream_url' do
subject(:url) { presenter.diffs_stream_url }
it { is_expected.to eq("#{base_path}/diffs_stream?offset=5&view=inline") }
context 'when diffs count is the same as streaming offset' do
let(:diffs_count) { 5 }
it { is_expected.to be_nil }
end
end
describe '#reload_stream_url' do
subject(:url) { presenter.reload_stream_url }
it { is_expected.to eq("#{base_path}/diffs_stream") }
end
end
describe '#lazy?' do
subject(:method) { presenter.lazy? }
it { is_expected.to be(false) }
end
describe '#should_sort_metadata_files?' do
subject(:method) { presenter.should_sort_metadata_files? }
it { is_expected.to be(true) }
end
end

View File

@ -877,7 +877,8 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
{ "type" => "NOTIFICATIONS" },
{ "type" => "PARTICIPANTS" },
{ "type" => "START_AND_DUE_DATE" },
{ "type" => "TIME_TRACKING" }
{ "type" => "TIME_TRACKING" },
{ "type" => "LINKED_RESOURCES" }
]
)
end

View File

@ -19,12 +19,11 @@ RSpec.describe EventForward::EventForwardController, feature_category: :product_
before do
allow(Gitlab::Tracking).to receive(:tracker).and_return(tracker)
allow(tracker).to receive(:emit_event_payload)
allow(tracker).to receive_messages(emit_event_payload: nil, enabled?: true, hostname: 'localhost')
allow(Gitlab::Tracking::EventEligibilityChecker).to receive(:new).and_return(event_eligibility_checker)
allow(event_eligibility_checker).to receive(:eligible?).and_return(true)
allow(EventForward::Logger).to receive(:build).and_return(logger)
allow(logger).to receive(:info)
stub_feature_flags(collect_product_usage_events: true)
end
describe 'POST #forward' do
@ -75,20 +74,6 @@ RSpec.describe EventForward::EventForwardController, feature_category: :product_
expect(response.body).to be_empty
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(collect_product_usage_events: false)
end
it 'returns 404 and do not call tracker' do
expect(tracker).not_to receive(:emit_event_payload)
request
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when filtering events by eligibility' do
before do
allow(event_eligibility_checker).to receive(:eligible?).with("event_1", "app_id_1").and_return(true)

View File

@ -70,28 +70,6 @@ RSpec.describe Projects::CommitController, feature_category: :source_code_manage
expect(assigns(:environment)).to be_nil
expect(response).to render_template(:rapid_diffs)
end
context 'for stream_url' do
it 'returns stream_url with offset' do
send_request
url = "/#{project.full_path}/-/commit/#{commit.id}/diffs_stream?offset=5&view=inline"
expect(assigns(:stream_url)).to eq(url)
end
context 'when view is set to parallel' do
let_it_be(:diff_view) { :parallel }
it 'returns stream_url with parallel view' do
send_request
url = "/#{project.full_path}/-/commit/#{commit.id}/diffs_stream?offset=5&view=parallel"
expect(assigns(:stream_url)).to eq(url)
end
end
end
end
describe 'GET #diff_files_metadata' do

View File

@ -65,12 +65,6 @@ RSpec.describe 'Merge Request Creation', feature_category: :code_review_workflow
expect(flash[:alert]).to be_present
end
it 'assigns show_whitespace_default' do
get_diffs
expect(assigns(:show_whitespace_default)).to be(true)
end
end
end
end

View File

@ -293,26 +293,6 @@ RSpec.describe Projects::MergeRequestsController, feature_category: :source_code
expect(response.body.scan('<diff-file ').size).to eq(5)
end
context 'for stream_url' do
it 'returns stream_url with offset' do
get diffs_project_merge_request_path(project, merge_request, rapid_diffs: 'true')
url = "/#{project.full_path}/-/merge_requests/#{merge_request.iid}/diffs_stream?offset=5&view=inline"
expect(assigns(:stream_url)).to eq(url)
end
context 'when view is set to parallel' do
it 'returns stream_url with parallel view' do
get diffs_project_merge_request_path(project, merge_request, rapid_diffs: 'true', view: 'parallel')
url = "/#{project.full_path}/-/merge_requests/#{merge_request.iid}/diffs_stream?offset=5&view=parallel"
expect(assigns(:stream_url)).to eq(url)
end
end
end
end
private

View File

@ -2,6 +2,9 @@
module StubSnowplow
def stub_snowplow
# Make sure that a new tracker with the stubed data is used
Gitlab::Tracking.remove_instance_variable(:@tracker) if Gitlab::Tracking.instance_variable_defined?(:@tracker)
# WebMock is set up to allow requests to `localhost`
host = 'localhost'

View File

@ -5,8 +5,8 @@ go 1.23.0
toolchain go1.23.2
require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1
github.com/BurntSushi/toml v1.4.0
github.com/alecthomas/chroma/v2 v2.18.0
github.com/alicebob/miniredis/v2 v2.34.0
@ -54,11 +54,11 @@ require (
cloud.google.com/go/storage v1.44.0 // indirect
cloud.google.com/go/trace v1.11.0 // indirect
contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
github.com/DataDog/sketches-go v1.0.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect

View File

@ -70,26 +70,26 @@ contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waT
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0 h1:LR0kAX9ykz8G4YgLCaRDVJ3+n43R8MneB5dTy2konZo=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0/go.mod h1:DWAciXemNf++PQJLeXUB4HHH5OpsAh12HZnu2wXE1jA=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 h1:lhZdRq7TIx0GJQvSyX2Si406vrYsov2FXGp/RnSEtcs=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
@ -460,8 +460,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=

View File

@ -1485,10 +1485,10 @@
dependencies:
"@vue/devtools-api" "^6.0.0-beta.11"
"@gitlab/web-ide@^0.0.1-dev-20250618150607":
version "0.0.1-dev-20250618150607"
resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20250618150607.tgz#fba03e3053990921fcdacba021e84303de20b605"
integrity sha512-vij4a2yu0iXAy0wAJhVXmJUsyGFwzblOfNtl8YP+SpEBXF1xs6AvRb9p5m3oucp8xDN2hkFSQ6xkHvF0N2G2/g==
"@gitlab/web-ide@^0.0.1-dev-20250625063230":
version "0.0.1-dev-20250625063230"
resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20250625063230.tgz#ec6c72716c0dd688898f5037f63ac253ad6924f5"
integrity sha512-pvKeWRuZb4LSXkbUYEmxvQ3Nony9s8CHNY9XywEgEQs49upUPzmfSLl6tftQVs3pKsq2KK8AokR8DJ6bD1Egvg==
"@gleam-lang/highlight.js-gleam@^1.5.0":
version "1.5.0"