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 <collapsible-section
:id="list.id"
:count="count" :count="count"
:has-merge-requests="mergeRequests.length > 0" :has-merge-requests="mergeRequests.length > 0"
:title="list.title" :title="list.title"

View File

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

View File

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

View File

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

View File

@ -4,12 +4,6 @@ module RapidDiffs
module Resource module Resource
extend ActiveSupport::Concern 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 def diff_files_metadata
return render_404 unless rapid_diffs_enabled? return render_404 unless rapid_diffs_enabled?
return render_404 unless diffs_resource.present? return render_404 unless diffs_resource.present?
@ -77,10 +71,6 @@ module RapidDiffs
end end
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. # When overridden this mthod should return a path to view diffs in an email-friendly format.
def email_format_path def email_format_path
nil nil

View File

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

View File

@ -150,14 +150,11 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 unless ::Feature.enabled?(:rapid_diffs, current_user, type: :wip) && return render_404 unless ::Feature.enabled?(:rapid_diffs, current_user, type: :wip) &&
::Feature.enabled?(:rapid_diffs_on_commit_show, current_user, type: :wip) ::Feature.enabled?(:rapid_diffs_on_commit_show, current_user, type: :wip)
streaming_offset = 5 @rapid_diffs_presenter = RapidDiffs::CommitPresenter.new(
@reload_stream_url = diffs_stream_url(@commit) @commit,
@stream_url = diffs_stream_url(@commit, streaming_offset, diff_view) diff_view,
@diffs_slice = @commit.first_diffs_slice(streaming_offset, commit_diff_options) 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)
show show
end end
@ -277,16 +274,6 @@ class Projects::CommitController < Projects::ApplicationController
payload[:metadata]['meta.diffs_files_count'] = @diffs.size payload[:metadata]['meta.diffs_files_count'] = @diffs.size
end 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 def rate_limit_for_expanded_diff_files
return unless diffs_expanded? 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) && return render_404 unless ::Feature.enabled?(:rapid_diffs, current_user, type: :wip) &&
::Feature.enabled?(:rapid_diffs_on_compare_show, 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 @rapid_diffs_presenter = ::RapidDiffs::ComparePresenter.new(
@reload_stream_url = diffs_stream_namespace_project_compare_index_path(**compare_params) compare,
@diff_files_endpoint = diff_files_metadata_namespace_project_compare_index_path(**compare_params) diff_view,
@diff_file_endpoint = diff_file_namespace_project_compare_index_path(**compare_params) diff_options,
@diffs_stats_endpoint = diffs_stats_namespace_project_compare_index_path(**compare_params) compare_params
@update_current_user_path = expose_path(api_v4_user_preferences_path) )
show show
end end

View File

@ -33,7 +33,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
# load 'rapid_diffs' javascript entrypoint instead of 'new' # load 'rapid_diffs' javascript entrypoint instead of 'new'
@js_action_name = 'rapid_diffs' @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 render action: :rapid_diffs
end 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' ::Feature.enabled?(:rapid_diffs_debug, current_user, type: :ops) && params[:rapid_diffs_disabled] == 'true'
end 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 # rubocop: disable CodeReuse/ActiveRecord
def selected_target_project def selected_target_project
return @project unless @project.forked? return @project unless @project.forked?

View File

@ -104,13 +104,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def rapid_diffs def rapid_diffs
return render_404 unless rapid_diffs_page_enabled? return render_404 unless rapid_diffs_page_enabled?
streaming_offset = 5 @rapid_diffs_presenter = ::RapidDiffs::MergeRequestPresenter.new(
@reload_stream_url = diffs_stream_url(@merge_request) @merge_request,
@stream_url = diffs_stream_url(@merge_request, streaming_offset, diff_view) diff_view,
@diffs_slice = @merge_request.first_diffs_slice(streaming_offset, diff_options) 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
show_merge_request show_merge_request
end end
@ -678,16 +676,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
payload[:metadata]['meta.diffs_files_count'] = @merge_request.merge_request_diff.files_count payload[:metadata]['meta.diffs_files_count'] = @merge_request.merge_request_diff.files_count
end 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 def display_limit_warnings
if @merge_request.reached_versions_limit? if @merge_request.reached_versions_limit?
flash[:alert] = format( flash[:alert] = format(

View File

@ -295,6 +295,12 @@ module DiffHelper
"#{diff_file.file_hash[0..8]}-heading" "#{diff_file.file_hash[0..8]}-heading"
end end
def hide_whitespace?
return params[:w] == '1' if params.key?(:w)
current_user.nil? || !current_user.show_whitespace_in_diffs
end
private private
def cached_conflicts_with_types def cached_conflicts_with_types
@ -340,12 +346,6 @@ module DiffHelper
toggle_whitespace_link(url, options) toggle_whitespace_link(url, options)
end 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) def toggle_whitespace_link(url, options)
toggle_text = hide_whitespace? ? s_('Diffs|Show whitespace changes') : s_('Diffs|Hide whitespace changes') toggle_text = hide_whitespace? ? s_('Diffs|Show whitespace changes') : s_('Diffs|Hide whitespace changes')
link_button_to toggle_text, url, class: options[:class] 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_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('application'), as: 'style', crossorigin: css_crossorigin)
= preload_link_tag(universal_path_to_stylesheet("highlight/themes/#{user_color_scheme}"), 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? - unless Rails.env.development?
%link{ rel: 'preconnect', href: "https://#{Gitlab::Tracking.collector_hostname}", crossorigin: '' } %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. -# 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] } .container-fluid{ class: [container_class] }
= render "commit_box" = render "commit_box"
= render "ci_menu" = 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(@rapid_diffs_presenter)
= render ::RapidDiffs::AppComponent.new(**args)

View File

@ -17,8 +17,7 @@
.container-fluid{ class: [container_class] } .container-fluid{ class: [container_class] }
= render "projects/commits/commit_list" unless hide_commit_list = render "projects/commits/commit_list" unless hide_commit_list
.container-fluid .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(@rapid_diffs_presenter)
= render ::RapidDiffs::AppComponent.new(**args)
- else - else
.container-fluid .container-fluid
= render Pajamas::CardComponent.new(card_options: { class: "gl-bg-subtle" }) do |c| = 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(@rapid_diffs_presenter) do |c|
= render ::RapidDiffs::AppComponent.new(**args) do |c|
- c.with_diffs_list do - 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' - add_page_specific_style 'page_bundles/merge_request_creation_rapid_diffs'
= render "page" do = 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(@rapid_diffs_presenter)
= render ::RapidDiffs::AppComponent.new(**args)

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 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180440
milestone: '17.9' milestone: '17.9'
queued_migration_version: 20250205200242 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' milestone: '15.10'
table_size: small table_size: small
gitlab_schema: gitlab_main_cell gitlab_schema: gitlab_main_cell
desired_sharding_key: sharding_key:
project_id: project_id: projects
references: projects group_id: namespaces
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

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, error text,
project_id bigint, project_id bigint,
group_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 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) | | [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
| [Namespaces](namespaces.md) | `/namespaces` | | [Namespaces](namespaces.md) | `/namespaces` |
| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) | | [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) | | [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
| [Personal access tokens](personal_access_tokens.md) | `/personal_access_tokens` | | [Personal access tokens](personal_access_tokens.md) | `/personal_access_tokens` |
| [Plan limits](plan_limits.md) | `/application/plan_limits` | | [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). 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, 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 >}} {{< /alert >}}

View File

@ -580,5 +580,7 @@ registries, as they:
- Do not allow users to authenticate to: - Do not allow users to authenticate to:
- The GitLab [container registry](../user/packages/container_registry/authenticate_with_container_registry.md). - 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). - 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 - Allow users to get, list, and delete registries through
the [container registry API](container_registry.md). 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: For GitLab-hosted runners:
- You can view your estimated usage in the [GitLab-hosted runner usage dashboard](#view-compute-usage). - 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. - 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). 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 month, which you can filter by year and runner.
- By namespace, which you can filter by month 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: 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**. 1. On the left sidebar, at the bottom, select **Admin**.

View File

@ -2,14 +2,17 @@
stage: Fulfillment stage: Fulfillment
group: Subscription Management 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 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 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 SaaS subscription](gitlab_com/_index.md)
- [GitLab Self-Managed subscription](self_managed/_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. 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). 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 performance, for example `/refactor improving performance`.
- Focus on potential vulnerabilities, for example `/refactor avoiding memory leaks and exploits`. - 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: 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). - <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 code performance problems, for example, `/fix performance problems`.
- Focus on fixing the build when the code does not compile, for example, `/fix the build`. - 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 ## Write tests in the IDE
{{< details >}} {{< 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 performance, for example `/tests focus on performance`.
- Focus on regressions and potential exploits, for example `/tests focus on regressions and potential exploits`. - 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). 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) <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. - GitLab package registry public API.
- [Git commands](https://git-scm.com/docs/gitcredentials#_description). - [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: 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_private_token_header
http_header http_header
token_param token_param
private_token_param
job_token_param
access_token_param
].freeze ].freeze
attr_reader :location attr_reader :location
@ -31,21 +34,27 @@ module Gitlab
def extract(request) def extract(request)
case @location case @location
when :http_basic_auth when :http_basic_auth
extract_from_http_basic_auth request extract_from_http_basic_auth(request)
when :http_token when :http_token
extract_from_http_token request extract_from_http_token(request)
when :http_bearer_token when :http_bearer_token
extract_from_http_bearer_token request extract_from_http_bearer_token(request)
when :http_deploy_token_header when :http_deploy_token_header
extract_from_http_deploy_token_header request extract_from_http_deploy_token_header(request)
when :http_job_token_header when :http_job_token_header
extract_from_http_job_token_header request extract_from_http_job_token_header(request)
when :http_private_token_header when :http_private_token_header
extract_from_http_private_token_header request extract_from_http_private_token_header(request)
when :http_header when :http_header
extract_from_http_header request extract_from_http_header(request)
when :token_param 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
end end
@ -103,15 +112,15 @@ module Gitlab
UsernameAndPassword.new(nil, password) UsernameAndPassword.new(nil, password)
end end
def extract_from_token_param(request) def extract_from_http_header(request)
password = request.query_parameters['token'] password = request.headers[@token_identifier]
return unless password.present? return unless password.present?
UsernameAndPassword.new(nil, password) UsernameAndPassword.new(nil, password)
end end
def extract_from_http_header(request) def extract_from_query_param(request, param_name)
password = request.headers[@token_identifier] password = request.query_parameters[param_name]
return unless password.present? return unless password.present?
UsernameAndPassword.new(nil, password) UsernameAndPassword.new(nil, password)

View File

@ -18,6 +18,7 @@ module Gitlab
personal_access_token_from_jwt personal_access_token_from_jwt
deploy_token_from_jwt deploy_token_from_jwt
job_token_from_jwt job_token_from_jwt
oauth_token
] ]
} }
@ -45,31 +46,25 @@ module Gitlab
def resolve(raw) def resolve(raw)
case @token_type case @token_type
when :personal_access_token when :personal_access_token
resolve_personal_access_token raw resolve_personal_access_token(raw)
when :job_token when :job_token
resolve_job_token raw resolve_job_token(raw)
when :deploy_token when :deploy_token
resolve_deploy_token raw resolve_deploy_token(raw)
when :personal_access_token_with_username 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 when :job_token_with_username
resolve_job_token_with_username raw resolve_job_token_with_username(raw)
when :deploy_token_with_username when :deploy_token_with_username
resolve_deploy_token_with_username raw resolve_deploy_token_with_username(raw)
when :personal_access_token_from_jwt 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 when :deploy_token_from_jwt
resolve_deploy_token_from_jwt raw resolve_deploy_token_from_jwt(raw)
when :job_token_from_jwt 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
end end
@ -157,6 +152,19 @@ module Gitlab
end end
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) def with_personal_access_token(raw, &block)
pat = ::PersonalAccessToken.find_by_token(raw.password) pat = ::PersonalAccessToken.find_by_token(raw.password)
return unless pat return unless pat

View File

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

View File

@ -12,6 +12,10 @@ module Gitlab
tracker.enabled? tracker.enabled?
end end
def frontend_connect_directly_to_snowplow_collector?
Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank?
end
def micro_verification_enabled? def micro_verification_enabled?
Gitlab::Utils.to_boolean(ENV['VERIFY_TRACKING'], default: false) Gitlab::Utils.to_boolean(ENV['VERIFY_TRACKING'], default: false)
end 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 module Destinations
class Snowplow class Snowplow
SNOWPLOW_NAMESPACE = 'gl' 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' DEDICATED_APP_ID = 'gitlab_dedicated'
SELF_MANAGED_APP_ID = 'gitlab_sm' 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 @event_eligibility_checker = Gitlab::Tracking::EventEligibilityChecker.new
return if batching_disabled? return if batching_disabled?
@ -42,7 +45,7 @@ module Gitlab
end end
def frontend_client_options(group) 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) snowplow_options(group)
else else
product_usage_events_options product_usage_events_options
@ -50,18 +53,7 @@ module Gitlab
end end
def enabled? def enabled?
Gitlab::CurrentSettings.snowplow_enabled? || true
::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
end end
def app_id def app_id
@ -111,10 +103,6 @@ module Gitlab
Gitlab::Utils.to_boolean(ENV['GITLAB_DISABLE_PRODUCT_USAGE_EVENT_LOGGING'], default: false) Gitlab::Utils.to_boolean(ENV['GITLAB_DISABLE_PRODUCT_USAGE_EVENT_LOGGING'], default: false)
end end
def protocol
'https'
end
def cookie_domain def cookie_domain
Gitlab::CurrentSettings.snowplow_cookie_domain Gitlab::CurrentSettings.snowplow_cookie_domain
end end

View File

@ -12,6 +12,10 @@ module Gitlab
COOKIE_DOMAIN = '.gitlab.com' COOKIE_DOMAIN = '.gitlab.com'
DEFAULT_URI = 'http://localhost:9090' DEFAULT_URI = 'http://localhost:9090'
def initialize
super(DestinationConfiguration.snowplow_micro_configuration)
end
override :snowplow_options override :snowplow_options
def snowplow_options(group) def snowplow_options(group)
# Using camel case as these keys will be used only in JavaScript # Using camel case as these keys will be used only in JavaScript
@ -27,27 +31,6 @@ module Gitlab
def enabled? def enabled?
true true
end 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 end
end end

View File

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

View File

@ -68,7 +68,7 @@
"@gitlab/ui": "114.8.1", "@gitlab/ui": "114.8.1",
"@gitlab/vue-router-vue3": "npm:vue-router@4.5.1", "@gitlab/vue-router-vue3": "npm:vue-router@4.5.1",
"@gitlab/vuex-vue3": "npm:vuex@4.1.0", "@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", "@gleam-lang/highlight.js-gleam": "^1.5.0",
"@mattiasbuelens/web-streams-adapter": "^0.1.0", "@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@rails/actioncable": "7.1.501", "@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?`.' 'with `User#can_admin_all_resources?` or `User#can_read_all_resources?`.'
def_node_matcher :admin_call?, <<~PATTERN def_node_matcher :admin_call?, <<~PATTERN
({send | csend} _ :admin? ...) (call _ :admin? ...)
PATTERN PATTERN
def on_send(node) def on_send(node)

View File

@ -3,11 +3,11 @@
require "spec_helper" require "spec_helper"
RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :code_review_workflow do RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :code_review_workflow do
let(:diffs_slice) { Array.new(2) { build(:diff_file) } } let_it_be(:diffs_slice) { Array.new(2, build(:diff_file)) }
let(:stream_url) { '/stream' } let(:diffs_stream_url) { '/stream' }
let(:reload_stream_url) { '/reload_stream' } let(:reload_stream_url) { '/reload_stream' }
let(:show_whitespace) { true } let(:show_whitespace) { true }
let(:diff_view) { 'inline' } let(:diff_view) { :inline }
let(:update_user_endpoint) { '/update_user' } let(:update_user_endpoint) { '/update_user' }
let(:diffs_stats_endpoint) { '/diffs_stats' } let(:diffs_stats_endpoint) { '/diffs_stats' }
let(:diff_files_endpoint) { '/diff_files_metadata' } 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(:should_sort_metadata_files) { false }
let(:lazy) { 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 it "renders app" do
render_component render_component
expect(page).to have_css('[data-rapid-diffs]') expect(page).to have_css('[data-rapid-diffs]')
@ -29,14 +57,14 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
render_component render_component
app = page.find('[data-rapid-diffs]') app = page.find('[data-rapid-diffs]')
data = Gitlab::Json.parse(app['data-app-data']) 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['reload_stream_url']).to eq(reload_stream_url)
expect(data['diffs_stats_endpoint']).to eq(diffs_stats_endpoint) expect(data['diffs_stats_endpoint']).to eq(diffs_stats_endpoint)
expect(data['diff_files_endpoint']).to eq(diff_files_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['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) expect(data['lazy']).to eq(lazy)
end end
@ -109,10 +137,9 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
end end
it 'preloads' do it 'preloads' do
instance = create_instance render_component
render_inline(instance) expect(component.helpers.page_startup_api_calls).to include(diffs_stats_endpoint)
expect(instance.helpers.page_startup_api_calls).to include(diffs_stats_endpoint) expect(component.helpers.page_startup_api_calls).to include(diff_files_endpoint)
expect(instance.helpers.page_startup_api_calls).to include(diff_files_endpoint)
expect(vc_test_controller.view_context.content_for?(:startup_js)).not_to be_nil expect(vc_test_controller.view_context.content_for?(:startup_js)).not_to be_nil
end end
@ -143,23 +170,7 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
end end
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) def render_component(&block)
render_inline(create_instance, &block) render_inline(component, &block)
end end
end end

View File

@ -3,9 +3,9 @@
require "spec_helper" require "spec_helper"
RSpec.shared_context "with diff file component tests" do 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_selector) { 'diff-file' }
let(:web_component) { page.find(web_component_selector) } let(:web_component) { page.find(web_component_selector) }
let(:diff_file) { build(:diff_file) }
let(:repository) { diff_file.repository } let(:repository) { diff_file.repository }
let(:project) { repository.container } let(:project) { repository.container }
let(:namespace) { project.namespace } let(:namespace) { project.namespace }

View File

@ -7,14 +7,6 @@ RSpec.describe RapidDiffs::Resource, type: :controller, feature_category: :sourc
Class.new(ApplicationController) do Class.new(ApplicationController) do
include RapidDiffs::Resource 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 def call_diffs_resource
diffs_resource diffs_resource
end end
@ -47,24 +39,6 @@ RSpec.describe RapidDiffs::Resource, type: :controller, feature_category: :sourc
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:commit) { project.commit_by(oid: sha) } 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 describe '#diffs_resource' do
it 'raises NotImplementedError' do it 'raises NotImplementedError' do
expect do expect do

View File

@ -1,83 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Merge request dashboard collapsible section renders section 1`] = ` exports[`Merge request dashboard collapsible section renders section 1`] = `
<section <local-storage-sync-stub
class="crud gl-bg-subtle gl-border gl-border-section gl-mt-5 gl-rounded-lg" persist="true"
id="" storagekey="mr_list_list"
> >
<header <section
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" class="crud gl-bg-subtle gl-border gl-border-section gl-mt-5 gl-rounded-lg"
id="reference-0"
> >
<div <header
class="gl-flex gl-flex-col gl-grow gl-self-center" 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 <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 <gl-button-stub
aria-controls="" aria-label="Approved list help popover"
aria-expanded="true"
aria-label="Collapse"
buttontextclasses="" buttontextclasses=""
category="tertiary" category="primary"
class="-gl-mr-2 btn-icon gl-self-start" class="gl-mr-2 gl-self-center"
data-testid="crud-collapse-toggle" icon="information-o"
icon="" size="medium"
size="small"
tag="button" tag="button"
title="Collapse" title=""
type="button" 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 <gl-button-stub
is-on="true" 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" variant="default"
/> >
</gl-button-stub> <gl-animated-chevron-lg-down-up-icon-stub
is-on="true"
variant="default"
/>
</gl-button-stub>
</div>
</div> </div>
</div> </header>
</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
<div <div
class="crud-pagination gl-border-t gl-border-t-section gl-flex gl-justify-center gl-p-5" class="!gl-mx-0 crud-body gl-mb-0 gl-mx-5 gl-my-4 gl-rounded-b-lg"
data-testid="crud-pagination" data-testid="crud-body"
/> >
</div> content
</section> <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 { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; 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 CrudComponent from '~/vue_shared/components/crud_component.vue';
import CollapsibleSection from '~/merge_request_dashboard/components/collapsible_section.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 collapseToggle = () => wrapper.findByTestId('crud-collapse-toggle');
const sectionContent = () => wrapper.findByTestId('crud-body'); const sectionContent = () => wrapper.findByTestId('crud-body');
const emptyState = () => wrapper.findByTestId('crud-empty'); const emptyState = () => wrapper.findByTestId('crud-empty');
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
function createComponent({ count = 3, hasMergeRequests = count > 0, loading = false } = {}) { function createComponent({ count = 3, hasMergeRequests = count > 0, loading = false } = {}) {
wrapper = shallowMountExtended(CollapsibleSection, { wrapper = shallowMountExtended(CollapsibleSection, {
@ -16,6 +18,7 @@ describe('Merge request dashboard collapsible section', () => {
default: 'content', default: 'content',
}, },
propsData: { propsData: {
id: 'list',
title: 'Approved', title: 'Approved',
count, count,
hasMergeRequests, hasMergeRequests,
@ -74,4 +77,16 @@ describe('Merge request dashboard collapsible section', () => {
expect(sectionContent().exists()).toBe(true); 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") } it { is_expected.to eq("#{diff_file.file_hash[0..8]}-heading") }
end 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 end

View File

@ -178,23 +178,30 @@ RSpec.describe Gitlab::APIAuthentication::TokenLocator, feature_category: :syste
end end
end end
context 'with :token_param' do %i[
let(:type) { :token_param } 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 context 'without credentials' do
let(:request) { double(query_parameters: {}) } let(:request) { double(query_parameters: {}) }
it 'returns nil' do it 'returns nil' do
expect(subject).to be_nil expect(subject).to be_nil
end
end end
end
context 'with credentials' do context 'with credentials' do
let(:password) { 'bar' } let(:password) { 'bar' }
let(:request) { double(query_parameters: { 'token' => password }) } let(:request) { double(query_parameters: { token_type.to_s => password }) }
it 'returns the credentials' do it 'returns the credentials' do
expect(subject.password).to eq(password) expect(subject.password).to eq(password)
end
end end
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) { 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(: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) } let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:oauth_application) { create(:oauth_application, owner: user) }
shared_examples 'an authorized request' do let_it_be(:oauth_token) do
it 'returns the correct token' do create(:oauth_access_token, application_id: oauth_application.id, resource_owner_id: user.id, scopes: [:api])
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
end end
describe '.new' do describe '.new' do
@ -45,7 +31,25 @@ RSpec.describe Gitlab::APIAuthentication::TokenResolver, feature_category: :syst
describe '#resolve' do describe '#resolve' do
let(:resolver) { described_class.new(type) } 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 context 'with :personal_access_token_with_username' do
let(:type) { :personal_access_token_with_username } 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' it_behaves_like 'an unauthorized request'
end end
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 end
def username_and_password(username, password) 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) } 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 describe '#snowplow_options' do
let_it_be(:group) { create :group } let_it_be(:group) { create :group }
before do before do
stub_config(snowplow_micro: snowplow_micro_settings) stub_config(snowplow_micro: snowplow_micro_settings)
stub_application_setting(snowplow_enabled?: true)
end end
it 'adds Snowplow micro specific options to the parent Snowplow options' do 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) expect(options).to include(base_options)
end end
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 end

View File

@ -43,6 +43,10 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_b
describe '#event' do describe '#event' do
context 'when event is eligible' do context 'when event is eligible' do
before 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) expect(SnowplowTracker::AsyncEmitter)
.to receive(:new) .to receive(:new)
.with(endpoint: 'gitfoo.com', .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(SnowplowTracker::Tracker).to receive(:new).and_return(tracker)
allow(tracker).to receive(:track_struct_event).and_call_original 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) expect(SnowplowTracker::AsyncEmitter)
.to receive(:new) .to receive(:new)
.with(endpoint: 'gitfoo.com', .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) stub_application_setting(snowplow_enabled?: false)
end end
context 'and collect_product_usage_events is enabled' do it 'returns true' do
it 'returns true' do expect(subject.enabled?).to be_truthy
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
end end
end end
end end
describe '#hostname' do describe '#app_id' do
subject { described_class.new.app_id }
context 'when snowplow is enabled' do context 'when snowplow is enabled' do
before do before do
stub_application_setting(snowplow_enabled?: true) stub_application_setting(snowplow_enabled?: true)
end end
it 'returns snowplow_collector_hostname' do it { is_expected.to eq('_abc123_') }
expect(subject.hostname).to eq('gitfoo.com')
end
end end
context 'when snowplow is disabled' do context 'when snowplow is disabled' do
before do before do
stub_application_setting(snowplow_enabled?: false) 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 end
context "with use_staging_endpoint_for_product_usage_events FF disabled" do context 'when dedicated instance' do
let(:enable_stg_events) { false } let(:dedicated_instance) { true }
it 'returns product usage event collection hostname' do it { is_expected.to eq('gitlab_dedicated') }
expect(subject.hostname).to eq('events.gitlab.net')
end
end end
context "with use_staging_endpoint_for_product_usage_events FF enabled" do context 'when self-hosted instance' do
let(:enable_stg_events) { true } let(:dedicated_instance) { false }
it 'returns product usage event collection hostname' do it { is_expected.to eq('gitlab_sm') }
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
end end
end end
end end

View File

@ -12,42 +12,21 @@ RSpec.describe Gitlab::Tracking::EventEligibilityChecker, feature_category: :ser
subject { checker.eligible?(event_name) } 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
where(:product_usage_data_enabled, :snowplow_enabled, :result) do true | false | true
true | false | true false | true | true
false | true | true false | false | false
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
end end
context 'when collect_product_usage_events feature flag is disabled' do before do
where(:product_usage_data_enabled, :snowplow_enabled, :result) do stub_application_setting(
true | false | false snowplow_enabled: snowplow_enabled,
false | true | true gitlab_product_usage_data_enabled?: product_usage_data_enabled
false | false | false )
end end
before do with_them do
stub_feature_flags(collect_product_usage_events: false) it { is_expected.to eq(result) }
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
end end
end end
@ -62,7 +41,6 @@ RSpec.describe Gitlab::Tracking::EventEligibilityChecker, feature_category: :ser
end end
before do before do
stub_feature_flags(collect_product_usage_events: false)
stub_application_setting( stub_application_setting(
snowplow_enabled?: snowplow_enabled, gitlab_product_usage_data_enabled?: product_usage_data_enabled 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::Hierarchy),
instance_of(WorkItems::Widgets::Labels), instance_of(WorkItems::Widgets::Labels),
instance_of(WorkItems::Widgets::LinkedItems), instance_of(WorkItems::Widgets::LinkedItems),
instance_of(WorkItems::Widgets::LinkedResources),
instance_of(WorkItems::Widgets::Milestone), instance_of(WorkItems::Widgets::Milestone),
instance_of(WorkItems::Widgets::Notes), instance_of(WorkItems::Widgets::Notes),
instance_of(WorkItems::Widgets::Notifications), 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::Hierarchy),
instance_of(WorkItems::Widgets::Labels), instance_of(WorkItems::Widgets::Labels),
instance_of(WorkItems::Widgets::LinkedItems), instance_of(WorkItems::Widgets::LinkedItems),
instance_of(WorkItems::Widgets::LinkedResources),
instance_of(WorkItems::Widgets::Notes), instance_of(WorkItems::Widgets::Notes),
instance_of(WorkItems::Widgets::Notifications), instance_of(WorkItems::Widgets::Notifications),
instance_of(WorkItems::Widgets::Participants), 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::CurrentUserTodos,
::WorkItems::Widgets::AwardEmoji, ::WorkItems::Widgets::AwardEmoji,
::WorkItems::Widgets::LinkedItems, ::WorkItems::Widgets::LinkedItems,
::WorkItems::Widgets::LinkedResources,
::WorkItems::Widgets::Participants, ::WorkItems::Widgets::Participants,
::WorkItems::Widgets::TimeTracking, ::WorkItems::Widgets::TimeTracking,
::WorkItems::Widgets::Designs, ::WorkItems::Widgets::Designs,
@ -129,9 +130,7 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do
end end
describe '.widget_classes' do describe '.widget_classes' do
# Excluding LinkedResources as the linked_resources widget is still not added to any work item type subject { described_class.widget_classes }
# 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] }
it 'returns all widget classes no matter if disabled or not' do it 'returns all widget classes no matter if disabled or not' do
is_expected.to match_array(all_widget_classes) 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" => "NOTIFICATIONS" },
{ "type" => "PARTICIPANTS" }, { "type" => "PARTICIPANTS" },
{ "type" => "START_AND_DUE_DATE" }, { "type" => "START_AND_DUE_DATE" },
{ "type" => "TIME_TRACKING" } { "type" => "TIME_TRACKING" },
{ "type" => "LINKED_RESOURCES" }
] ]
) )
end end

View File

@ -19,12 +19,11 @@ RSpec.describe EventForward::EventForwardController, feature_category: :product_
before do before do
allow(Gitlab::Tracking).to receive(:tracker).and_return(tracker) 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(Gitlab::Tracking::EventEligibilityChecker).to receive(:new).and_return(event_eligibility_checker)
allow(event_eligibility_checker).to receive(:eligible?).and_return(true) allow(event_eligibility_checker).to receive(:eligible?).and_return(true)
allow(EventForward::Logger).to receive(:build).and_return(logger) allow(EventForward::Logger).to receive(:build).and_return(logger)
allow(logger).to receive(:info) allow(logger).to receive(:info)
stub_feature_flags(collect_product_usage_events: true)
end end
describe 'POST #forward' do describe 'POST #forward' do
@ -75,20 +74,6 @@ RSpec.describe EventForward::EventForwardController, feature_category: :product_
expect(response.body).to be_empty expect(response.body).to be_empty
end 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 context 'when filtering events by eligibility' do
before do before do
allow(event_eligibility_checker).to receive(:eligible?).with("event_1", "app_id_1").and_return(true) 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(assigns(:environment)).to be_nil
expect(response).to render_template(:rapid_diffs) expect(response).to render_template(:rapid_diffs)
end 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 end
describe 'GET #diff_files_metadata' do 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 expect(flash[:alert]).to be_present
end end
it 'assigns show_whitespace_default' do
get_diffs
expect(assigns(:show_whitespace_default)).to be(true)
end
end end
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) expect(response.body.scan('<diff-file ').size).to eq(5)
end 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 end
private private

View File

@ -2,6 +2,9 @@
module StubSnowplow module StubSnowplow
def stub_snowplow 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` # WebMock is set up to allow requests to `localhost`
host = 'localhost' host = 'localhost'

View File

@ -5,8 +5,8 @@ go 1.23.0
toolchain go1.23.2 toolchain go1.23.2
require ( require (
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.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.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.4.0
github.com/alecthomas/chroma/v2 v2.18.0 github.com/alecthomas/chroma/v2 v2.18.0
github.com/alicebob/miniredis/v2 v2.34.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/storage v1.44.0 // indirect
cloud.google.com/go/trace v1.11.0 // indirect cloud.google.com/go/trace v1.11.0 // indirect
contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // 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/azcore v1.18.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.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 v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/to v0.4.0 // 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/datadog-go v4.4.0+incompatible // indirect
github.com/DataDog/sketches-go v1.0.0 // indirect github.com/DataDog/sketches-go v1.0.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.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 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= 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= 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.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= 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.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= 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.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= 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.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw= 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.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ= 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.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= 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.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= 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.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c= 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.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc= 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.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM= 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.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI= 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 h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 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 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= 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 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= 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.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
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/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 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/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 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 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.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= 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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=

View File

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