Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1d107471ad
commit
2b2854cf44
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
# Cop supports --autocorrect.
|
||||
InternalAffairs/NodePatternGroups:
|
||||
Details: grace period
|
||||
Exclude:
|
||||
- 'rubocop/cop/user_admin.rb'
|
||||
|
|
@ -170,6 +170,7 @@ export default {
|
|||
}"
|
||||
>
|
||||
<collapsible-section
|
||||
:id="list.id"
|
||||
:count="count"
|
||||
:has-merge-requests="mergeRequests.length > 0"
|
||||
:title="list.title"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlBadge, GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
import CrudComponent from '~/vue_shared/components/crud_component.vue';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
||||
|
|
@ -7,12 +8,17 @@ export default {
|
|||
components: {
|
||||
GlBadge,
|
||||
GlButton,
|
||||
LocalStorageSync,
|
||||
CrudComponent,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
|
@ -56,6 +62,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
open: true,
|
||||
savedOpenState: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -78,6 +85,14 @@ export default {
|
|||
helpPopoverAriaLabel() {
|
||||
return sprintf(__('%{list} list help popover'), { list: this.title });
|
||||
},
|
||||
storageKey() {
|
||||
return `mr_list_${this.id}`;
|
||||
},
|
||||
isSectionOpen() {
|
||||
if (this.savedOpenState === null) return this.open;
|
||||
|
||||
return this.savedOpenState;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
loading(newVal) {
|
||||
|
|
@ -87,60 +102,67 @@ export default {
|
|||
methods: {
|
||||
onCollapsedSection() {
|
||||
this.open = false;
|
||||
this.savedOpenState = false;
|
||||
this.$emit('clear-new');
|
||||
},
|
||||
onExpandSection() {
|
||||
this.open = true;
|
||||
this.savedOpenState = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
2
|
||||
|
||||
<template>
|
||||
<crud-component
|
||||
is-collapsible
|
||||
:collapsed="!open"
|
||||
:toggle-aria-label="toggleButtonLabel"
|
||||
body-class="!gl-mx-0 gl-mb-0"
|
||||
@collapsed="onCollapsedSection"
|
||||
@expanded="onExpandSection"
|
||||
<local-storage-sync
|
||||
:storage-key="storageKey"
|
||||
:value="savedOpenState"
|
||||
@input="(val) => (savedOpenState = val)"
|
||||
>
|
||||
<template #title>
|
||||
{{ title }}
|
||||
<gl-badge v-if="count !== null" size="sm">{{ count }}</gl-badge>
|
||||
</template>
|
||||
<crud-component
|
||||
is-collapsible
|
||||
:collapsed="!isSectionOpen"
|
||||
:toggle-aria-label="toggleButtonLabel"
|
||||
body-class="!gl-mx-0 gl-mb-0"
|
||||
@collapsed="onCollapsedSection"
|
||||
@expanded="onExpandSection"
|
||||
>
|
||||
<template #title>
|
||||
{{ title }}
|
||||
<gl-badge v-if="count !== null" size="sm">{{ count }}</gl-badge>
|
||||
</template>
|
||||
|
||||
<template #actions>
|
||||
<gl-badge
|
||||
v-if="!open && newMergeRequests.length"
|
||||
:variant="activeList ? 'success' : 'muted'"
|
||||
class="gl-font-bold"
|
||||
>
|
||||
{{ newMergeRequestsBadgeText }}
|
||||
</gl-badge>
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
:title="helpContent"
|
||||
:aria-label="helpPopoverAriaLabel"
|
||||
icon="information-o"
|
||||
variant="link"
|
||||
class="gl-mr-2 gl-self-center"
|
||||
/>
|
||||
</template>
|
||||
<template #actions>
|
||||
<gl-badge
|
||||
v-if="!open && newMergeRequests.length"
|
||||
:variant="activeList ? 'success' : 'muted'"
|
||||
class="gl-font-bold"
|
||||
>
|
||||
{{ newMergeRequestsBadgeText }}
|
||||
</gl-badge>
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
:title="helpContent"
|
||||
:aria-label="helpPopoverAriaLabel"
|
||||
icon="information-o"
|
||||
variant="link"
|
||||
class="gl-mr-2 gl-self-center"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="!hasMergeRequests && !loading" #empty>
|
||||
<p class="gl-pt-1 gl-text-center gl-text-subtle">
|
||||
{{ __('No merge requests match this list.') }}
|
||||
</p>
|
||||
</template>
|
||||
<template v-if="!hasMergeRequests && !loading" #empty>
|
||||
<p class="gl-pt-1 gl-text-center gl-text-subtle">
|
||||
{{ __('No merge requests match this list.') }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
<slot></slot>
|
||||
</template>
|
||||
<template #default>
|
||||
<slot></slot>
|
||||
</template>
|
||||
|
||||
<template v-if="open" #pagination>
|
||||
<slot name="pagination"></slot>
|
||||
</template>
|
||||
</crud-component>
|
||||
<template v-if="open" #pagination>
|
||||
<slot name="pagination"></slot>
|
||||
</template>
|
||||
</crud-component>
|
||||
</local-storage-sync>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
- if !@lazy
|
||||
- helpers.add_page_startup_api_call @diffs_stats_endpoint
|
||||
- helpers.add_page_startup_api_call @diff_files_endpoint
|
||||
- if @stream_url
|
||||
- if !lazy?
|
||||
- helpers.add_page_startup_api_call diffs_stats_endpoint
|
||||
- helpers.add_page_startup_api_call diff_files_endpoint
|
||||
- if diffs_stream_url
|
||||
- helpers.content_for :startup_js do
|
||||
- javascript_tag nonce: content_security_policy_nonce do
|
||||
:plain
|
||||
var controller = new AbortController();
|
||||
window.gl.rapidDiffsPreload = {
|
||||
controller: controller,
|
||||
streamRequest: fetch('#{Gitlab::UrlSanitizer.sanitize(@stream_url)}', { signal: controller.signal })
|
||||
streamRequest: fetch('#{Gitlab::UrlSanitizer.sanitize(diffs_stream_url)}', { signal: controller.signal })
|
||||
}
|
||||
|
||||
%article.rd-app{ aria: { label: root_label }, data: { rapid_diffs: true, app_data: app_data.to_json } }
|
||||
|
|
@ -24,14 +24,14 @@
|
|||
-# using label produces better results in VoiceOver than labelledby + hidden h2
|
||||
%section.rd-app-content{ aria: { label: content_label } }
|
||||
.rd-app-content-header{ data: { hidden_files_warning: true } }
|
||||
- if empty_diff? && !@lazy
|
||||
- if empty_diff? && !lazy?
|
||||
= render RapidDiffs::EmptyStateComponent.new
|
||||
.rd-app-code-theme.code{ class: helpers.user_color_scheme }
|
||||
.rd-app-diffs-list
|
||||
-# performance optimization: using a sibling element to cover diffs list is faster than changing opacity on the parent
|
||||
.rd-app-diffs-list-loading-overlay{ data: { diffs_overlay: true } }
|
||||
%div{ data: { diffs_list: true } }
|
||||
- if @lazy
|
||||
- if lazy?
|
||||
.rd-app-diffs-loading{ data: { testid: 'rd-diffs-list-loading' } }
|
||||
= helpers.gl_loading_icon(size: 'lg')
|
||||
- else
|
||||
|
|
@ -41,8 +41,8 @@
|
|||
- if diffs_list?
|
||||
= diffs_list
|
||||
- elsif !empty_diff?
|
||||
= render RapidDiffs::DiffFileComponent.with_collection(@diffs_slice, parallel_view: parallel_view?)
|
||||
- if @stream_url
|
||||
= render RapidDiffs::DiffFileComponent.with_collection(diffs_slice, parallel_view: parallel_view?)
|
||||
- if diffs_stream_url
|
||||
%div{ data: { stream_remaining_diffs: true } }
|
||||
- else
|
||||
= javascript_tag nonce: content_security_policy_nonce do
|
||||
|
|
|
|||
|
|
@ -4,53 +4,46 @@ module RapidDiffs
|
|||
class AppComponent < ViewComponent::Base
|
||||
renders_one :diffs_list
|
||||
|
||||
def initialize(
|
||||
diffs_slice:,
|
||||
reload_stream_url:,
|
||||
stream_url:,
|
||||
show_whitespace:,
|
||||
diff_view:,
|
||||
update_user_endpoint:,
|
||||
diffs_stats_endpoint:,
|
||||
diff_files_endpoint:,
|
||||
diff_file_endpoint:,
|
||||
should_sort_metadata_files: false,
|
||||
lazy: false
|
||||
)
|
||||
@diffs_slice = diffs_slice
|
||||
@reload_stream_url = reload_stream_url
|
||||
@stream_url = stream_url
|
||||
@show_whitespace = show_whitespace
|
||||
@diff_view = diff_view
|
||||
@update_user_endpoint = update_user_endpoint
|
||||
@diffs_stats_endpoint = diffs_stats_endpoint
|
||||
@diff_files_endpoint = diff_files_endpoint
|
||||
@should_sort_metadata_files = should_sort_metadata_files
|
||||
@diff_file_endpoint = diff_file_endpoint
|
||||
@lazy = lazy
|
||||
attr_reader :presenter
|
||||
|
||||
delegate :diffs_stream_url, :reload_stream_url, :diffs_stats_endpoint, :diff_files_endpoint, :diff_file_endpoint,
|
||||
:should_sort_metadata_files?, :diffs_slice, :lazy?, to: :presenter
|
||||
|
||||
delegate :diff_view, :current_user, to: :helpers
|
||||
|
||||
def initialize(presenter)
|
||||
@presenter = presenter
|
||||
end
|
||||
|
||||
def app_data
|
||||
{
|
||||
diffs_stream_url: @stream_url,
|
||||
reload_stream_url: @reload_stream_url,
|
||||
diffs_stats_endpoint: @diffs_stats_endpoint,
|
||||
diff_files_endpoint: @diff_files_endpoint,
|
||||
should_sort_metadata_files: @should_sort_metadata_files,
|
||||
show_whitespace: @show_whitespace,
|
||||
diff_view_type: @diff_view,
|
||||
diff_file_endpoint: @diff_file_endpoint,
|
||||
update_user_endpoint: @update_user_endpoint,
|
||||
lazy: @lazy
|
||||
diffs_stream_url: diffs_stream_url,
|
||||
reload_stream_url: reload_stream_url,
|
||||
diffs_stats_endpoint: diffs_stats_endpoint,
|
||||
diff_files_endpoint: diff_files_endpoint,
|
||||
should_sort_metadata_files: should_sort_metadata_files?,
|
||||
show_whitespace: show_whitespace?,
|
||||
diff_view_type: diff_view,
|
||||
diff_file_endpoint: diff_file_endpoint,
|
||||
update_user_endpoint: update_user_endpoint,
|
||||
lazy: lazy?
|
||||
}
|
||||
end
|
||||
|
||||
def update_user_endpoint
|
||||
helpers.expose_path(helpers.api_v4_user_preferences_path)
|
||||
end
|
||||
|
||||
def show_whitespace?
|
||||
!helpers.hide_whitespace?
|
||||
end
|
||||
|
||||
def parallel_view?
|
||||
@diff_view == :parallel
|
||||
diff_view == :parallel
|
||||
end
|
||||
|
||||
def empty_diff?
|
||||
@diffs_slice.nil? || @diffs_slice.empty?
|
||||
diffs_slice.nil? || diffs_slice.empty?
|
||||
end
|
||||
|
||||
def browser_visible?
|
||||
|
|
|
|||
|
|
@ -4,12 +4,6 @@ module RapidDiffs
|
|||
module Resource
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def diffs_stream_url(resource, offset = nil, diff_view = nil)
|
||||
return if offset && offset > resource.diffs_for_streaming.diff_files.count
|
||||
|
||||
diffs_stream_resource_url(resource, offset, diff_view)
|
||||
end
|
||||
|
||||
def diff_files_metadata
|
||||
return render_404 unless rapid_diffs_enabled?
|
||||
return render_404 unless diffs_resource.present?
|
||||
|
|
@ -77,10 +71,6 @@ module RapidDiffs
|
|||
end
|
||||
end
|
||||
|
||||
def diffs_stream_resource_url(resource, offset, diff_view)
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
# When overridden this mthod should return a path to view diffs in an email-friendly format.
|
||||
def email_format_path
|
||||
nil
|
||||
|
|
|
|||
|
|
@ -6,13 +6,9 @@ module EventForward
|
|||
SELF_MANAGED_SUFFIX = 'sm'
|
||||
|
||||
def forward
|
||||
if ::Feature.enabled?(:collect_product_usage_events, :instance)
|
||||
process_events
|
||||
process_events
|
||||
|
||||
head :ok
|
||||
else
|
||||
head :not_found
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -150,14 +150,11 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
return render_404 unless ::Feature.enabled?(:rapid_diffs, current_user, type: :wip) &&
|
||||
::Feature.enabled?(:rapid_diffs_on_commit_show, current_user, type: :wip)
|
||||
|
||||
streaming_offset = 5
|
||||
@reload_stream_url = diffs_stream_url(@commit)
|
||||
@stream_url = diffs_stream_url(@commit, streaming_offset, diff_view)
|
||||
@diffs_slice = @commit.first_diffs_slice(streaming_offset, commit_diff_options)
|
||||
@diff_files_endpoint = diff_files_metadata_namespace_project_commit_path
|
||||
@diff_file_endpoint = diff_file_namespace_project_commit_path
|
||||
@diffs_stats_endpoint = diffs_stats_namespace_project_commit_path
|
||||
@update_current_user_path = expose_path(api_v4_user_preferences_path)
|
||||
@rapid_diffs_presenter = RapidDiffs::CommitPresenter.new(
|
||||
@commit,
|
||||
diff_view,
|
||||
commit_diff_options
|
||||
)
|
||||
|
||||
show
|
||||
end
|
||||
|
|
@ -277,16 +274,6 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
payload[:metadata]['meta.diffs_files_count'] = @diffs.size
|
||||
end
|
||||
|
||||
def diffs_stream_resource_url(commit, offset, diff_view)
|
||||
diffs_stream_namespace_project_commit_path(
|
||||
namespace_id: commit.project.namespace.to_param,
|
||||
project_id: commit.project.to_param,
|
||||
id: commit.id,
|
||||
offset: offset,
|
||||
view: diff_view
|
||||
)
|
||||
end
|
||||
|
||||
def rate_limit_for_expanded_diff_files
|
||||
return unless diffs_expanded?
|
||||
|
||||
|
|
|
|||
|
|
@ -81,12 +81,12 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
return render_404 unless ::Feature.enabled?(:rapid_diffs, current_user, type: :wip) &&
|
||||
::Feature.enabled?(:rapid_diffs_on_compare_show, current_user, type: :wip)
|
||||
|
||||
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
|
||||
@reload_stream_url = diffs_stream_namespace_project_compare_index_path(**compare_params)
|
||||
@diff_files_endpoint = diff_files_metadata_namespace_project_compare_index_path(**compare_params)
|
||||
@diff_file_endpoint = diff_file_namespace_project_compare_index_path(**compare_params)
|
||||
@diffs_stats_endpoint = diffs_stats_namespace_project_compare_index_path(**compare_params)
|
||||
@update_current_user_path = expose_path(api_v4_user_preferences_path)
|
||||
@rapid_diffs_presenter = ::RapidDiffs::ComparePresenter.new(
|
||||
compare,
|
||||
diff_view,
|
||||
diff_options,
|
||||
compare_params
|
||||
)
|
||||
|
||||
show
|
||||
end
|
||||
|
|
|
|||
|
|
@ -33,7 +33,13 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
|
|||
|
||||
# load 'rapid_diffs' javascript entrypoint instead of 'new'
|
||||
@js_action_name = 'rapid_diffs'
|
||||
define_rapid_diffs_vars
|
||||
@rapid_diffs_presenter = ::RapidDiffs::MergeRequestCreationPresenter.new(
|
||||
@merge_request,
|
||||
project,
|
||||
diff_view,
|
||||
diff_options,
|
||||
{ merge_request: merge_request_params }
|
||||
)
|
||||
render action: :rapid_diffs
|
||||
end
|
||||
|
||||
|
|
@ -159,16 +165,6 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
|
|||
::Feature.enabled?(:rapid_diffs_debug, current_user, type: :ops) && params[:rapid_diffs_disabled] == 'true'
|
||||
end
|
||||
|
||||
def define_rapid_diffs_vars
|
||||
merge_request = { source_branch: @merge_request.source_branch, target_branch: @merge_request.target_branch }
|
||||
@show_whitespace_default = current_user.nil? || current_user.show_whitespace_in_diffs
|
||||
@stream_url = project_new_merge_request_diffs_stream_path(@project, merge_request: merge_request)
|
||||
@reload_stream_url = project_new_merge_request_diffs_stream_path(@project, merge_request: merge_request)
|
||||
@diff_files_endpoint = project_new_merge_request_diff_files_metadata_path(@project, merge_request: merge_request)
|
||||
@diff_file_endpoint = project_new_merge_request_diff_file_path(@project, merge_request: merge_request)
|
||||
@diffs_stats_endpoint = project_new_merge_request_diffs_stats_path(@project, merge_request: merge_request)
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def selected_target_project
|
||||
return @project unless @project.forked?
|
||||
|
|
|
|||
|
|
@ -104,13 +104,11 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
def rapid_diffs
|
||||
return render_404 unless rapid_diffs_page_enabled?
|
||||
|
||||
streaming_offset = 5
|
||||
@reload_stream_url = diffs_stream_url(@merge_request)
|
||||
@stream_url = diffs_stream_url(@merge_request, streaming_offset, diff_view)
|
||||
@diffs_slice = @merge_request.first_diffs_slice(streaming_offset, diff_options)
|
||||
@diff_files_endpoint = diff_files_metadata_namespace_project_merge_request_path
|
||||
@diff_file_endpoint = diff_file_namespace_project_merge_request_path
|
||||
@diffs_stats_endpoint = diffs_stats_namespace_project_merge_request_path
|
||||
@rapid_diffs_presenter = ::RapidDiffs::MergeRequestPresenter.new(
|
||||
@merge_request,
|
||||
diff_view,
|
||||
diff_options
|
||||
)
|
||||
|
||||
show_merge_request
|
||||
end
|
||||
|
|
@ -678,16 +676,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
payload[:metadata]['meta.diffs_files_count'] = @merge_request.merge_request_diff.files_count
|
||||
end
|
||||
|
||||
def diffs_stream_resource_url(merge_request, offset, diff_view)
|
||||
diffs_stream_namespace_project_merge_request_path(
|
||||
id: merge_request.iid,
|
||||
project_id: merge_request.project.to_param,
|
||||
namespace_id: merge_request.project.namespace.to_param,
|
||||
offset: offset,
|
||||
view: diff_view
|
||||
)
|
||||
end
|
||||
|
||||
def display_limit_warnings
|
||||
if @merge_request.reached_versions_limit?
|
||||
flash[:alert] = format(
|
||||
|
|
|
|||
|
|
@ -295,6 +295,12 @@ module DiffHelper
|
|||
"#{diff_file.file_hash[0..8]}-heading"
|
||||
end
|
||||
|
||||
def hide_whitespace?
|
||||
return params[:w] == '1' if params.key?(:w)
|
||||
|
||||
current_user.nil? || !current_user.show_whitespace_in_diffs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cached_conflicts_with_types
|
||||
|
|
@ -340,12 +346,6 @@ module DiffHelper
|
|||
toggle_whitespace_link(url, options)
|
||||
end
|
||||
|
||||
def hide_whitespace?
|
||||
return params[:w] == '1' if params.key?(:w)
|
||||
|
||||
current_user.nil? || !current_user.show_whitespace_in_diffs
|
||||
end
|
||||
|
||||
def toggle_whitespace_link(url, options)
|
||||
toggle_text = hide_whitespace? ? s_('Diffs|Show whitespace changes') : s_('Diffs|Hide whitespace changes')
|
||||
link_button_to toggle_text, url, class: options[:class]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
= preload_link_tag(universal_path_to_stylesheet('application_utilities'), as: 'style', crossorigin: css_crossorigin)
|
||||
= preload_link_tag(universal_path_to_stylesheet('application'), as: 'style', crossorigin: css_crossorigin)
|
||||
= preload_link_tag(universal_path_to_stylesheet("highlight/themes/#{user_color_scheme}"), as: 'style', crossorigin: css_crossorigin)
|
||||
- if Gitlab::Tracking.enabled? && Gitlab::Tracking.collector_hostname
|
||||
- if Gitlab::Tracking.frontend_connect_directly_to_snowplow_collector?
|
||||
- unless Rails.env.development?
|
||||
%link{ rel: 'preconnect', href: "https://#{Gitlab::Tracking.collector_hostname}", crossorigin: '' }
|
||||
-# Do not use preload_link_tag for fonts, to work around Firefox double-fetch bug.
|
||||
|
|
|
|||
|
|
@ -13,5 +13,4 @@
|
|||
.container-fluid{ class: [container_class] }
|
||||
= render "commit_box"
|
||||
= render "ci_menu"
|
||||
- args = { diffs_slice: @diffs_slice, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: @diff_view, update_user_endpoint: @update_current_user_path, diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint, diff_file_endpoint: @diff_file_endpoint }
|
||||
= render ::RapidDiffs::AppComponent.new(**args)
|
||||
= render ::RapidDiffs::AppComponent.new(@rapid_diffs_presenter)
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@
|
|||
.container-fluid{ class: [container_class] }
|
||||
= render "projects/commits/commit_list" unless hide_commit_list
|
||||
.container-fluid
|
||||
- args = { diffs_slice: nil, reload_stream_url: @reload_stream_url, stream_url: nil, show_whitespace: @show_whitespace_default, diff_view: diff_view, diffs_stats_endpoint: @diffs_stats_endpoint, update_user_endpoint: @update_current_user_path, diff_files_endpoint: @diff_files_endpoint, diff_file_endpoint: @diff_file_endpoint, lazy: true }
|
||||
= render ::RapidDiffs::AppComponent.new(**args)
|
||||
= render ::RapidDiffs::AppComponent.new(@rapid_diffs_presenter)
|
||||
- else
|
||||
.container-fluid
|
||||
= render Pajamas::CardComponent.new(card_options: { class: "gl-bg-subtle" }) do |c|
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
- args = { diffs_slice: @diffs_slice, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: @diff_view, update_user_endpoint: @update_current_user_path, diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint, should_sort_metadata_files: true, diff_file_endpoint: @diff_file_endpoint }
|
||||
|
||||
= render ::RapidDiffs::AppComponent.new(**args) do |c|
|
||||
= render ::RapidDiffs::AppComponent.new(@rapid_diffs_presenter) do |c|
|
||||
- c.with_diffs_list do
|
||||
= render RapidDiffs::MergeRequestDiffFileComponent.with_collection(@diffs_slice, merge_request: @merge_request, parallel_view: @diff_view == :parallel)
|
||||
= render RapidDiffs::MergeRequestDiffFileComponent.with_collection(c.diffs_slice, merge_request: @merge_request, parallel_view: c.diff_view == :parallel)
|
||||
|
|
|
|||
|
|
@ -3,5 +3,4 @@
|
|||
- add_page_specific_style 'page_bundles/merge_request_creation_rapid_diffs'
|
||||
|
||||
= render "page" do
|
||||
- args = { diffs_slice: nil, reload_stream_url: @reload_stream_url, stream_url: @stream_url, show_whitespace: @show_whitespace_default, diff_view: diff_view, update_user_endpoint: expose_path(api_v4_user_preferences_path), diffs_stats_endpoint: @diffs_stats_endpoint, diff_files_endpoint: @diff_files_endpoint, diff_file_endpoint: @diff_file_endpoint, lazy: true }
|
||||
= render ::RapidDiffs::AppComponent.new(**args)
|
||||
= render ::RapidDiffs::AppComponent.new(@rapid_diffs_presenter)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -5,4 +5,4 @@ feature_category: source_code_management
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/180440
|
||||
milestone: '17.9'
|
||||
queued_migration_version: 20250205200242
|
||||
finalized_by: # version of the migration that finalized this BBM
|
||||
finalized_by: '20250623175338'
|
||||
|
|
|
|||
|
|
@ -10,23 +10,6 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111708
|
|||
milestone: '15.10'
|
||||
table_size: small
|
||||
gitlab_schema: gitlab_main_cell
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: export_id
|
||||
table: bulk_import_exports
|
||||
sharding_key: project_id
|
||||
belongs_to: export
|
||||
group_id:
|
||||
references: namespaces
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: export_id
|
||||
table: bulk_import_exports
|
||||
sharding_key: group_id
|
||||
belongs_to: export
|
||||
desired_sharding_key_migration_job_name:
|
||||
- BackfillBulkImportExportBatchesProjectId
|
||||
- BackfillBulkImportExportBatchesGroupId
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
group_id: namespaces
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
11ab2abf5afce30a90368f7eeee7e2f7de7e612feb419a1be4018f325ae8f492
|
||||
|
|
@ -0,0 +1 @@
|
|||
263565d50e6bad7adf42abbf072c958a53bd8bcd1b6ebcd651b13429dde5d6a8
|
||||
|
|
@ -0,0 +1 @@
|
|||
d15115f89fb926b7fb789547cd12cd8eeed3d907d17a9e539c53b9849d3099d3
|
||||
|
|
@ -10855,7 +10855,8 @@ CREATE TABLE bulk_import_export_batches (
|
|||
error text,
|
||||
project_id bigint,
|
||||
group_id bigint,
|
||||
CONSTRAINT check_046dc60dfe CHECK ((char_length(error) <= 255))
|
||||
CONSTRAINT check_046dc60dfe CHECK ((char_length(error) <= 255)),
|
||||
CONSTRAINT check_31f6b54459 CHECK ((num_nonnulls(group_id, project_id) = 1))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE bulk_import_export_batches_id_seq
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ The following API resources are available outside of project and group contexts
|
|||
| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
|
||||
| [Namespaces](namespaces.md) | `/namespaces` |
|
||||
| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
|
||||
| [Policy settings](policy_settings.md) | `/admin/security/policy_settings` |
|
||||
| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
|
||||
| [Personal access tokens](personal_access_tokens.md) | `/personal_access_tokens` |
|
||||
| [Plan limits](plan_limits.md) | `/application/plan_limits` |
|
||||
|
|
|
|||
|
|
@ -681,7 +681,7 @@ These endpoints are intended for internal use by GitLab, and generally not meant
|
|||
|
||||
These endpoints do not adhere to the [REST API authentication methods](rest/authentication.md).
|
||||
For more information on which headers and token types are supported,
|
||||
see [Maven package registry](../user/packages/maven_repository/_index.md). Undocumented authentication methods might be removed in the future.
|
||||
see [Maven virtual registry](../user/packages/virtual_registry/maven/_index.md). Undocumented authentication methods might be removed in the future.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
|
|
|
|||
|
|
@ -580,5 +580,7 @@ registries, as they:
|
|||
- Do not allow users to authenticate to:
|
||||
- The GitLab [container registry](../user/packages/container_registry/authenticate_with_container_registry.md).
|
||||
- Packages listed in the GitLab [Package registry](../user/packages/package_registry/_index.md).
|
||||
- [Virtual registries](../user/packages/virtual_registry/_index.md).
|
||||
- Allow users to get, list, and delete registries through
|
||||
the [container registry API](container_registry.md).
|
||||
- Allow users to get, list, and delete registry objects through the [Maven virtual registry API](maven_virtual_registries.md).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
```
|
||||
|
|
@ -21,7 +21,6 @@ namespaces running jobs on either type of instance runners.
|
|||
For GitLab-hosted runners:
|
||||
|
||||
- You can view your estimated usage in the [GitLab-hosted runner usage dashboard](#view-compute-usage).
|
||||
- Usage billing is based on build duration logs collected from GitLab-hosted runners.
|
||||
- Quota enforcement and notifications are not available.
|
||||
|
||||
For self-managed instance runners registered to your GitLab Dedicated instance, see [view instance runner usage](instance_runner_compute_minutes.md#view-usage).
|
||||
|
|
@ -44,14 +43,6 @@ You can see compute usage:
|
|||
- By month, which you can filter by year and runner.
|
||||
- By namespace, which you can filter by month and runner.
|
||||
|
||||
{{< alert type="note" >}}
|
||||
|
||||
Compute usage data provides only an estimate of total usage.
|
||||
Bills are generated directly from the raw runner logs,
|
||||
which may show discrepancies compared to the usage data in GitLab.
|
||||
|
||||
{{< /alert >}}
|
||||
|
||||
To view GitLab-hosted runner compute usage for all namespaces across your entire GitLab instance:
|
||||
|
||||
1. On the left sidebar, at the bottom, select **Admin**.
|
||||
|
|
|
|||
|
|
@ -2,14 +2,17 @@
|
|||
stage: Fulfillment
|
||||
group: Subscription Management
|
||||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
|
||||
description: Payment and company details.
|
||||
description: Customers Portal is a comprehensive self-service hub for purchasing and managing GitLab subscriptions and billing.
|
||||
title: The Customers Portal
|
||||
---
|
||||
|
||||
For some management tasks for your subscription and account, such as purchasing additional seats or storage and viewing invoices, you use the Customers Portal. See the following pages for specific instructions on managing your subscription:
|
||||
The Customers Portal is your comprehensive self-service hub for managing GitLab subscriptions and billing. You can purchase GitLab products, manage your subscriptions throughout the entire subscription lifecycle, view and pay invoices, and access your billing details and contact information.
|
||||
|
||||
See the following pages for specific instructions on managing your subscription:
|
||||
|
||||
- [GitLab SaaS subscription](gitlab_com/_index.md)
|
||||
- [GitLab Self-Managed subscription](self_managed/_index.md)
|
||||
- [Manage subscription](manage_subscription.md)
|
||||
|
||||
If you made your purchase through an authorized reseller, you must contact them directly to make changes to your subscription.
|
||||
For more information, see [Customers that purchased through a reseller](#customers-that-purchased-through-a-reseller).
|
||||
|
|
|
|||
|
|
@ -451,6 +451,8 @@ You can include additional instructions to be considered. For example:
|
|||
- Focus on performance, for example `/refactor improving performance`.
|
||||
- Focus on potential vulnerabilities, for example `/refactor avoiding memory leaks and exploits`.
|
||||
|
||||
`/refactor` uses [Repository X-Ray](../project/repository/code_suggestions/repository_xray.md) to deliver more accurate, context-aware suggestions.
|
||||
|
||||
For more information, see:
|
||||
|
||||
- <i class="fa-youtube-play" aria-hidden="true"></i> [Application modernization with GitLab Duo (C++ to Java)](https://youtu.be/FjoAmt5eeXA?si=SLv9Mv8eSUAVwW5Z).
|
||||
|
|
@ -492,6 +494,8 @@ You can include additional instructions to be considered. For example:
|
|||
- Focus on code performance problems, for example, `/fix performance problems`.
|
||||
- Focus on fixing the build when the code does not compile, for example, `/fix the build`.
|
||||
|
||||
`/fix` uses [Repository X-Ray](../project/repository/code_suggestions/repository_xray.md) to deliver more accurate, context-aware suggestions.
|
||||
|
||||
## Write tests in the IDE
|
||||
|
||||
{{< details >}}
|
||||
|
|
@ -527,6 +531,8 @@ You can include additional instructions to be considered. For example:
|
|||
- Focus on performance, for example `/tests focus on performance`.
|
||||
- Focus on regressions and potential exploits, for example `/tests focus on regressions and potential exploits`.
|
||||
|
||||
`/tests` uses [Repository X-Ray](../project/repository/code_suggestions/repository_xray.md) to deliver more accurate, context-aware suggestions.
|
||||
|
||||
For more information, see [Use GitLab Duo Chat in VS Code](_index.md#use-gitlab-duo-chat-in-vs-code).
|
||||
|
||||
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Watch an overview](https://www.youtube.com/watch?v=zWhwuixUkYU)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ to the following endpoints:
|
|||
|
||||
- GitLab package registry public API.
|
||||
- [Git commands](https://git-scm.com/docs/gitcredentials#_description).
|
||||
- [GitLab virtual registry package operations](../../../api/maven_virtual_registries.md#manage-package-operations).
|
||||
|
||||
You can create deploy tokens at either the project or group level:
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ module Gitlab
|
|||
http_private_token_header
|
||||
http_header
|
||||
token_param
|
||||
private_token_param
|
||||
job_token_param
|
||||
access_token_param
|
||||
].freeze
|
||||
|
||||
attr_reader :location
|
||||
|
|
@ -31,21 +34,27 @@ module Gitlab
|
|||
def extract(request)
|
||||
case @location
|
||||
when :http_basic_auth
|
||||
extract_from_http_basic_auth request
|
||||
extract_from_http_basic_auth(request)
|
||||
when :http_token
|
||||
extract_from_http_token request
|
||||
extract_from_http_token(request)
|
||||
when :http_bearer_token
|
||||
extract_from_http_bearer_token request
|
||||
extract_from_http_bearer_token(request)
|
||||
when :http_deploy_token_header
|
||||
extract_from_http_deploy_token_header request
|
||||
extract_from_http_deploy_token_header(request)
|
||||
when :http_job_token_header
|
||||
extract_from_http_job_token_header request
|
||||
extract_from_http_job_token_header(request)
|
||||
when :http_private_token_header
|
||||
extract_from_http_private_token_header request
|
||||
extract_from_http_private_token_header(request)
|
||||
when :http_header
|
||||
extract_from_http_header request
|
||||
extract_from_http_header(request)
|
||||
when :token_param
|
||||
extract_from_token_param request
|
||||
extract_from_query_param(request, 'token')
|
||||
when :private_token_param
|
||||
extract_from_query_param(request, 'private_token')
|
||||
when :job_token_param
|
||||
extract_from_query_param(request, 'job_token')
|
||||
when :access_token_param
|
||||
extract_from_query_param(request, 'access_token')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -103,15 +112,15 @@ module Gitlab
|
|||
UsernameAndPassword.new(nil, password)
|
||||
end
|
||||
|
||||
def extract_from_token_param(request)
|
||||
password = request.query_parameters['token']
|
||||
def extract_from_http_header(request)
|
||||
password = request.headers[@token_identifier]
|
||||
return unless password.present?
|
||||
|
||||
UsernameAndPassword.new(nil, password)
|
||||
end
|
||||
|
||||
def extract_from_http_header(request)
|
||||
password = request.headers[@token_identifier]
|
||||
def extract_from_query_param(request, param_name)
|
||||
password = request.query_parameters[param_name]
|
||||
return unless password.present?
|
||||
|
||||
UsernameAndPassword.new(nil, password)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ module Gitlab
|
|||
personal_access_token_from_jwt
|
||||
deploy_token_from_jwt
|
||||
job_token_from_jwt
|
||||
oauth_token
|
||||
]
|
||||
}
|
||||
|
||||
|
|
@ -45,31 +46,25 @@ module Gitlab
|
|||
def resolve(raw)
|
||||
case @token_type
|
||||
when :personal_access_token
|
||||
resolve_personal_access_token raw
|
||||
|
||||
resolve_personal_access_token(raw)
|
||||
when :job_token
|
||||
resolve_job_token raw
|
||||
|
||||
resolve_job_token(raw)
|
||||
when :deploy_token
|
||||
resolve_deploy_token raw
|
||||
|
||||
resolve_deploy_token(raw)
|
||||
when :personal_access_token_with_username
|
||||
resolve_personal_access_token_with_username raw
|
||||
|
||||
resolve_personal_access_token_with_username(raw)
|
||||
when :job_token_with_username
|
||||
resolve_job_token_with_username raw
|
||||
|
||||
resolve_job_token_with_username(raw)
|
||||
when :deploy_token_with_username
|
||||
resolve_deploy_token_with_username raw
|
||||
|
||||
resolve_deploy_token_with_username(raw)
|
||||
when :personal_access_token_from_jwt
|
||||
resolve_personal_access_token_from_jwt raw
|
||||
|
||||
resolve_personal_access_token_from_jwt(raw)
|
||||
when :deploy_token_from_jwt
|
||||
resolve_deploy_token_from_jwt raw
|
||||
|
||||
resolve_deploy_token_from_jwt(raw)
|
||||
when :job_token_from_jwt
|
||||
resolve_job_token_from_jwt raw
|
||||
resolve_job_token_from_jwt(raw)
|
||||
when :oauth_token
|
||||
resolve_oauth_token(raw)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -157,6 +152,19 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def resolve_oauth_token(raw)
|
||||
oauth_token = OauthAccessToken.by_token(raw.password)
|
||||
raise ::Gitlab::Auth::UnauthorizedError unless oauth_token
|
||||
|
||||
oauth_token.revoke_previous_refresh_token!
|
||||
|
||||
::Gitlab::Auth::Identity.link_from_oauth_token(oauth_token).tap do |identity|
|
||||
raise ::Gitlab::Auth::UnauthorizedError if identity && !identity.valid?
|
||||
end
|
||||
|
||||
oauth_token
|
||||
end
|
||||
|
||||
def with_personal_access_token(raw, &block)
|
||||
pat = ::PersonalAccessToken.find_by_token(raw.password)
|
||||
return unless pat
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ module Gitlab
|
|||
current_user_todos: 'Current user todos',
|
||||
award_emoji: 'Award emoji',
|
||||
linked_items: 'Linked items',
|
||||
linked_resources: 'Linked Resources',
|
||||
color: 'Color',
|
||||
participants: 'Participants',
|
||||
time_tracking: 'Time tracking',
|
||||
|
|
@ -60,6 +61,7 @@ module Gitlab
|
|||
:start_and_due_date,
|
||||
:time_tracking,
|
||||
:vulnerabilities,
|
||||
:linked_resources,
|
||||
[:weight, { editable: true, rollup: false }],
|
||||
:status
|
||||
],
|
||||
|
|
@ -76,6 +78,7 @@ module Gitlab
|
|||
:iteration,
|
||||
:labels,
|
||||
:linked_items,
|
||||
:linked_resources,
|
||||
:milestone,
|
||||
:notes,
|
||||
:notifications,
|
||||
|
|
@ -119,6 +122,7 @@ module Gitlab
|
|||
:iteration,
|
||||
:labels,
|
||||
:linked_items,
|
||||
:linked_resources,
|
||||
:milestone,
|
||||
:notes,
|
||||
:notifications,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ module Gitlab
|
|||
tracker.enabled?
|
||||
end
|
||||
|
||||
def frontend_connect_directly_to_snowplow_collector?
|
||||
Gitlab::CurrentSettings.snowplow_enabled? && !Gitlab::CurrentSettings.snowplow_collector_hostname.blank?
|
||||
end
|
||||
|
||||
def micro_verification_enabled?
|
||||
Gitlab::Utils.to_boolean(ENV['VERIFY_TRACKING'], default: false)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -7,12 +7,15 @@ module Gitlab
|
|||
module Destinations
|
||||
class Snowplow
|
||||
SNOWPLOW_NAMESPACE = 'gl'
|
||||
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT = 'events.gitlab.net'
|
||||
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT_STG = 'events-stg.gitlab.net'
|
||||
DEDICATED_APP_ID = 'gitlab_dedicated'
|
||||
SELF_MANAGED_APP_ID = 'gitlab_sm'
|
||||
|
||||
def initialize
|
||||
delegate :hostname, :uri, :protocol, to: :@destination_configuration
|
||||
|
||||
attr_reader :destination_configuration
|
||||
|
||||
def initialize(destination_configuration = DestinationConfiguration.snowplow_configuration)
|
||||
@destination_configuration = destination_configuration
|
||||
@event_eligibility_checker = Gitlab::Tracking::EventEligibilityChecker.new
|
||||
|
||||
return if batching_disabled?
|
||||
|
|
@ -42,7 +45,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def frontend_client_options(group)
|
||||
if Gitlab::CurrentSettings.snowplow_enabled? || ::Feature.disabled?(:collect_product_usage_events, :instance)
|
||||
if Gitlab::CurrentSettings.snowplow_enabled?
|
||||
snowplow_options(group)
|
||||
else
|
||||
product_usage_events_options
|
||||
|
|
@ -50,18 +53,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def enabled?
|
||||
Gitlab::CurrentSettings.snowplow_enabled? ||
|
||||
::Feature.enabled?(:collect_product_usage_events, :instance)
|
||||
end
|
||||
|
||||
def hostname
|
||||
if Gitlab::CurrentSettings.snowplow_enabled?
|
||||
Gitlab::CurrentSettings.snowplow_collector_hostname
|
||||
elsif Feature.enabled?(:use_staging_endpoint_for_product_usage_events, :instance)
|
||||
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT_STG
|
||||
else
|
||||
PRODUCT_USAGE_EVENT_COLLECT_ENDPOINT
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def app_id
|
||||
|
|
@ -111,10 +103,6 @@ module Gitlab
|
|||
Gitlab::Utils.to_boolean(ENV['GITLAB_DISABLE_PRODUCT_USAGE_EVENT_LOGGING'], default: false)
|
||||
end
|
||||
|
||||
def protocol
|
||||
'https'
|
||||
end
|
||||
|
||||
def cookie_domain
|
||||
Gitlab::CurrentSettings.snowplow_cookie_domain
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ module Gitlab
|
|||
COOKIE_DOMAIN = '.gitlab.com'
|
||||
DEFAULT_URI = 'http://localhost:9090'
|
||||
|
||||
def initialize
|
||||
super(DestinationConfiguration.snowplow_micro_configuration)
|
||||
end
|
||||
|
||||
override :snowplow_options
|
||||
def snowplow_options(group)
|
||||
# Using camel case as these keys will be used only in JavaScript
|
||||
|
|
@ -27,27 +31,6 @@ module Gitlab
|
|||
def enabled?
|
||||
true
|
||||
end
|
||||
|
||||
override :hostname
|
||||
def hostname
|
||||
"#{uri.host}:#{uri.port}"
|
||||
end
|
||||
|
||||
def uri
|
||||
url = Gitlab.config.snowplow_micro.address
|
||||
scheme = Gitlab.config.gitlab.https ? 'https' : 'http'
|
||||
URI("#{scheme}://#{url}")
|
||||
rescue GitlabSettings::MissingSetting
|
||||
URI(DEFAULT_URI)
|
||||
end
|
||||
strong_memoize_attr :uri
|
||||
|
||||
private
|
||||
|
||||
override :protocol
|
||||
def protocol
|
||||
uri.scheme
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,11 +15,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def eligible?(_event, _app_id = nil)
|
||||
if ::Feature.enabled?(:collect_product_usage_events, :instance)
|
||||
snowplow_enabled? || send_usage_data?
|
||||
else
|
||||
snowplow_enabled?
|
||||
end
|
||||
snowplow_enabled? || send_usage_data?
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
"@gitlab/ui": "114.8.1",
|
||||
"@gitlab/vue-router-vue3": "npm:vue-router@4.5.1",
|
||||
"@gitlab/vuex-vue3": "npm:vuex@4.1.0",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20250618150607",
|
||||
"@gitlab/web-ide": "^0.0.1-dev-20250625063230",
|
||||
"@gleam-lang/highlight.js-gleam": "^1.5.0",
|
||||
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
|
||||
"@rails/actioncable": "7.1.501",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module RuboCop
|
|||
'with `User#can_admin_all_resources?` or `User#can_read_all_resources?`.'
|
||||
|
||||
def_node_matcher :admin_call?, <<~PATTERN
|
||||
({send | csend} _ :admin? ...)
|
||||
(call _ :admin? ...)
|
||||
PATTERN
|
||||
|
||||
def on_send(node)
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@
|
|||
require "spec_helper"
|
||||
|
||||
RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :code_review_workflow do
|
||||
let(:diffs_slice) { Array.new(2) { build(:diff_file) } }
|
||||
let(:stream_url) { '/stream' }
|
||||
let_it_be(:diffs_slice) { Array.new(2, build(:diff_file)) }
|
||||
let(:diffs_stream_url) { '/stream' }
|
||||
let(:reload_stream_url) { '/reload_stream' }
|
||||
let(:show_whitespace) { true }
|
||||
let(:diff_view) { 'inline' }
|
||||
let(:diff_view) { :inline }
|
||||
let(:update_user_endpoint) { '/update_user' }
|
||||
let(:diffs_stats_endpoint) { '/diffs_stats' }
|
||||
let(:diff_files_endpoint) { '/diff_files_metadata' }
|
||||
|
|
@ -15,6 +15,34 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
|
|||
let(:should_sort_metadata_files) { false }
|
||||
let(:lazy) { false }
|
||||
|
||||
let(:diff_presenter) do
|
||||
instance_double(
|
||||
::RapidDiffs::BasePresenter,
|
||||
diffs_slice: diffs_slice,
|
||||
diffs_stream_url: diffs_stream_url,
|
||||
reload_stream_url: reload_stream_url,
|
||||
diffs_stats_endpoint: diffs_stats_endpoint,
|
||||
diff_files_endpoint: diff_files_endpoint,
|
||||
diff_file_endpoint: diff_file_endpoint,
|
||||
should_sort_metadata_files?: should_sort_metadata_files,
|
||||
lazy?: lazy
|
||||
)
|
||||
end
|
||||
|
||||
subject(:component) { described_class.new(diff_presenter) }
|
||||
|
||||
before do
|
||||
allow(component).to receive(:helpers).and_wrap_original do |original_method, *args|
|
||||
helpers = original_method.call(*args)
|
||||
allow(helpers).to receive_messages(
|
||||
hide_whitespace?: !show_whitespace,
|
||||
diff_view: diff_view,
|
||||
api_v4_user_preferences_path: update_user_endpoint
|
||||
)
|
||||
helpers
|
||||
end
|
||||
end
|
||||
|
||||
it "renders app" do
|
||||
render_component
|
||||
expect(page).to have_css('[data-rapid-diffs]')
|
||||
|
|
@ -29,14 +57,14 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
|
|||
render_component
|
||||
app = page.find('[data-rapid-diffs]')
|
||||
data = Gitlab::Json.parse(app['data-app-data'])
|
||||
expect(data['diffs_stream_url']).to eq(diffs_stream_url)
|
||||
expect(data['reload_stream_url']).to eq(reload_stream_url)
|
||||
expect(data['diffs_stats_endpoint']).to eq(diffs_stats_endpoint)
|
||||
expect(data['diff_files_endpoint']).to eq(diff_files_endpoint)
|
||||
expect(data['show_whitespace']).to eq(show_whitespace)
|
||||
expect(data['diff_view_type']).to eq(diff_view)
|
||||
expect(data['update_user_endpoint']).to eq(update_user_endpoint)
|
||||
expect(data['diffs_stream_url']).to eq(stream_url)
|
||||
expect(data['diff_file_endpoint']).to eq(diff_file_endpoint)
|
||||
expect(data['update_user_endpoint']).to eq(update_user_endpoint)
|
||||
expect(data['show_whitespace']).to eq(show_whitespace)
|
||||
expect(data['diff_view_type']).to eq(diff_view.to_s)
|
||||
expect(data['lazy']).to eq(lazy)
|
||||
end
|
||||
|
||||
|
|
@ -109,10 +137,9 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
|
|||
end
|
||||
|
||||
it 'preloads' do
|
||||
instance = create_instance
|
||||
render_inline(instance)
|
||||
expect(instance.helpers.page_startup_api_calls).to include(diffs_stats_endpoint)
|
||||
expect(instance.helpers.page_startup_api_calls).to include(diff_files_endpoint)
|
||||
render_component
|
||||
expect(component.helpers.page_startup_api_calls).to include(diffs_stats_endpoint)
|
||||
expect(component.helpers.page_startup_api_calls).to include(diff_files_endpoint)
|
||||
expect(vc_test_controller.view_context.content_for?(:startup_js)).not_to be_nil
|
||||
end
|
||||
|
||||
|
|
@ -143,23 +170,7 @@ RSpec.describe RapidDiffs::AppComponent, type: :component, feature_category: :co
|
|||
end
|
||||
end
|
||||
|
||||
def create_instance
|
||||
described_class.new(
|
||||
diffs_slice:,
|
||||
stream_url:,
|
||||
reload_stream_url:,
|
||||
show_whitespace:,
|
||||
diff_view:,
|
||||
update_user_endpoint:,
|
||||
diffs_stats_endpoint:,
|
||||
diff_files_endpoint:,
|
||||
diff_file_endpoint:,
|
||||
should_sort_metadata_files:,
|
||||
lazy:
|
||||
)
|
||||
end
|
||||
|
||||
def render_component(&block)
|
||||
render_inline(create_instance, &block)
|
||||
render_inline(component, &block)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
require "spec_helper"
|
||||
|
||||
RSpec.shared_context "with diff file component tests" do
|
||||
let_it_be(:diff_file) { build(:diff_file) }
|
||||
let(:web_component_selector) { 'diff-file' }
|
||||
let(:web_component) { page.find(web_component_selector) }
|
||||
let(:diff_file) { build(:diff_file) }
|
||||
let(:repository) { diff_file.repository }
|
||||
let(:project) { repository.container }
|
||||
let(:namespace) { project.namespace }
|
||||
|
|
|
|||
|
|
@ -7,14 +7,6 @@ RSpec.describe RapidDiffs::Resource, type: :controller, feature_category: :sourc
|
|||
Class.new(ApplicationController) do
|
||||
include RapidDiffs::Resource
|
||||
|
||||
def call_diffs_stream_resource_url(resource, offset, diff_view)
|
||||
diffs_stream_resource_url(resource, offset, diff_view)
|
||||
end
|
||||
|
||||
def call_diffs_stream_url(resource, offset, diff_view)
|
||||
diffs_stream_url(resource, offset, diff_view)
|
||||
end
|
||||
|
||||
def call_diffs_resource
|
||||
diffs_resource
|
||||
end
|
||||
|
|
@ -47,24 +39,6 @@ RSpec.describe RapidDiffs::Resource, type: :controller, feature_category: :sourc
|
|||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:commit) { project.commit_by(oid: sha) }
|
||||
|
||||
describe '#diffs_stream_resource_url' do
|
||||
it 'raises NotImplementedError' do
|
||||
expect do
|
||||
controller.new.call_diffs_stream_resource_url(commit, offset, diff_view)
|
||||
end.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diffs_stream_url' do
|
||||
context 'when offset is greater than the number of diffs' do
|
||||
let_it_be(:offset) { 9999 }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(controller.new.call_diffs_stream_url(commit, offset, diff_view)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#diffs_resource' do
|
||||
it 'raises NotImplementedError' do
|
||||
expect do
|
||||
|
|
|
|||
|
|
@ -1,83 +1,88 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Merge request dashboard collapsible section renders section 1`] = `
|
||||
<section
|
||||
class="crud gl-bg-subtle gl-border gl-border-section gl-mt-5 gl-rounded-lg"
|
||||
id=""
|
||||
<local-storage-sync-stub
|
||||
persist="true"
|
||||
storagekey="mr_list_list"
|
||||
>
|
||||
<header
|
||||
class="crud-header gl-bg-section gl-border-b gl-border-section gl-flex gl-flex-wrap gl-gap-x-5 gl-gap-y-2 gl-justify-between gl-p-4 gl-pl-5 gl-pr-10 gl-relative gl-rounded-t-lg md:gl-flex-nowrap"
|
||||
<section
|
||||
class="crud gl-bg-subtle gl-border gl-border-section gl-mt-5 gl-rounded-lg"
|
||||
id="reference-0"
|
||||
>
|
||||
<div
|
||||
class="gl-flex gl-flex-col gl-grow gl-self-center"
|
||||
<header
|
||||
class="crud-header gl-bg-section gl-border-b gl-border-section gl-flex gl-flex-wrap gl-gap-x-5 gl-gap-y-2 gl-justify-between gl-p-4 gl-pl-5 gl-pr-10 gl-relative gl-rounded-t-lg md:gl-flex-nowrap"
|
||||
>
|
||||
<h2
|
||||
class="gl-font-bold gl-gap-3 gl-inline-flex gl-items-center gl-leading-normal gl-m-0 gl-text-base"
|
||||
data-testid="crud-title"
|
||||
>
|
||||
Approved
|
||||
<gl-badge-stub
|
||||
iconsize="md"
|
||||
size="sm"
|
||||
tag="span"
|
||||
target="_self"
|
||||
variant="muted"
|
||||
>
|
||||
3
|
||||
</gl-badge-stub>
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
class="gl-flex gl-gap-3 gl-items-center"
|
||||
data-testid="crud-actions"
|
||||
>
|
||||
<gl-button-stub
|
||||
aria-label="Approved list help popover"
|
||||
buttontextclasses=""
|
||||
category="primary"
|
||||
class="gl-mr-2 gl-self-center"
|
||||
icon="information-o"
|
||||
size="medium"
|
||||
tag="button"
|
||||
title=""
|
||||
type="button"
|
||||
variant="link"
|
||||
/>
|
||||
<div
|
||||
class="gl-absolute gl-border-l gl-border-l-section gl-h-6 gl-pl-3 gl-right-5 gl-top-4"
|
||||
class="gl-flex gl-flex-col gl-grow gl-self-center"
|
||||
>
|
||||
<h2
|
||||
class="gl-font-bold gl-gap-3 gl-inline-flex gl-items-center gl-leading-normal gl-m-0 gl-text-base"
|
||||
data-testid="crud-title"
|
||||
>
|
||||
Approved
|
||||
<gl-badge-stub
|
||||
iconsize="md"
|
||||
size="sm"
|
||||
tag="span"
|
||||
target="_self"
|
||||
variant="muted"
|
||||
>
|
||||
3
|
||||
</gl-badge-stub>
|
||||
</h2>
|
||||
</div>
|
||||
<div
|
||||
class="gl-flex gl-gap-3 gl-items-center"
|
||||
data-testid="crud-actions"
|
||||
>
|
||||
<gl-button-stub
|
||||
aria-controls=""
|
||||
aria-expanded="true"
|
||||
aria-label="Collapse"
|
||||
aria-label="Approved list help popover"
|
||||
buttontextclasses=""
|
||||
category="tertiary"
|
||||
class="-gl-mr-2 btn-icon gl-self-start"
|
||||
data-testid="crud-collapse-toggle"
|
||||
icon=""
|
||||
size="small"
|
||||
category="primary"
|
||||
class="gl-mr-2 gl-self-center"
|
||||
icon="information-o"
|
||||
size="medium"
|
||||
tag="button"
|
||||
title="Collapse"
|
||||
title=""
|
||||
type="button"
|
||||
variant="default"
|
||||
variant="link"
|
||||
/>
|
||||
<div
|
||||
class="gl-absolute gl-border-l gl-border-l-section gl-h-6 gl-pl-3 gl-right-5 gl-top-4"
|
||||
>
|
||||
<gl-animated-chevron-lg-down-up-icon-stub
|
||||
is-on="true"
|
||||
<gl-button-stub
|
||||
aria-controls=""
|
||||
aria-expanded="true"
|
||||
aria-label="Collapse"
|
||||
buttontextclasses=""
|
||||
category="tertiary"
|
||||
class="-gl-mr-2 btn-icon gl-self-start"
|
||||
data-testid="crud-collapse-toggle"
|
||||
icon=""
|
||||
size="small"
|
||||
tag="button"
|
||||
title="Collapse"
|
||||
type="button"
|
||||
variant="default"
|
||||
/>
|
||||
</gl-button-stub>
|
||||
>
|
||||
<gl-animated-chevron-lg-down-up-icon-stub
|
||||
is-on="true"
|
||||
variant="default"
|
||||
/>
|
||||
</gl-button-stub>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
class="!gl-mx-0 crud-body gl-mb-0 gl-mx-5 gl-my-4 gl-rounded-b-lg"
|
||||
data-testid="crud-body"
|
||||
>
|
||||
content
|
||||
</header>
|
||||
<div
|
||||
class="crud-pagination gl-border-t gl-border-t-section gl-flex gl-justify-center gl-p-5"
|
||||
data-testid="crud-pagination"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
class="!gl-mx-0 crud-body gl-mb-0 gl-mx-5 gl-my-4 gl-rounded-b-lg"
|
||||
data-testid="crud-body"
|
||||
>
|
||||
content
|
||||
<div
|
||||
class="crud-pagination gl-border-t gl-border-t-section gl-flex gl-justify-center gl-p-5"
|
||||
data-testid="crud-pagination"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</local-storage-sync-stub>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { nextTick } from 'vue';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
import CrudComponent from '~/vue_shared/components/crud_component.vue';
|
||||
import CollapsibleSection from '~/merge_request_dashboard/components/collapsible_section.vue';
|
||||
|
||||
|
|
@ -9,6 +10,7 @@ describe('Merge request dashboard collapsible section', () => {
|
|||
const collapseToggle = () => wrapper.findByTestId('crud-collapse-toggle');
|
||||
const sectionContent = () => wrapper.findByTestId('crud-body');
|
||||
const emptyState = () => wrapper.findByTestId('crud-empty');
|
||||
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
|
||||
|
||||
function createComponent({ count = 3, hasMergeRequests = count > 0, loading = false } = {}) {
|
||||
wrapper = shallowMountExtended(CollapsibleSection, {
|
||||
|
|
@ -16,6 +18,7 @@ describe('Merge request dashboard collapsible section', () => {
|
|||
default: 'content',
|
||||
},
|
||||
propsData: {
|
||||
id: 'list',
|
||||
title: 'Approved',
|
||||
count,
|
||||
hasMergeRequests,
|
||||
|
|
@ -74,4 +77,16 @@ describe('Merge request dashboard collapsible section', () => {
|
|||
|
||||
expect(sectionContent().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('collapsed state sync', () => {
|
||||
it('collapses content when local storage value is set to false', async () => {
|
||||
createComponent({ count: 1 });
|
||||
|
||||
findLocalStorageSync().vm.$emit('input', false);
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(sectionContent().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -864,4 +864,40 @@ RSpec.describe DiffHelper, feature_category: :code_review_workflow do
|
|||
|
||||
it { is_expected.to eq("#{diff_file.file_hash[0..8]}-heading") }
|
||||
end
|
||||
|
||||
describe "#hide_whitespace?" do
|
||||
subject { helper.hide_whitespace? }
|
||||
|
||||
context 'when request has w param set' do
|
||||
before do
|
||||
allow(controller).to receive(:params) { { w: '1' } }
|
||||
end
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when user is guest' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
|
||||
context 'when user has preference' do
|
||||
before do
|
||||
allow(helper).to receive_message_chain(:current_user, :show_whitespace_in_diffs).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to be(false) }
|
||||
|
||||
context 'when request has w param set' do
|
||||
before do
|
||||
allow(controller).to receive(:params) { { w: '1' } }
|
||||
end
|
||||
|
||||
it { is_expected.to be(true) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -178,23 +178,30 @@ RSpec.describe Gitlab::APIAuthentication::TokenLocator, feature_category: :syste
|
|||
end
|
||||
end
|
||||
|
||||
context 'with :token_param' do
|
||||
let(:type) { :token_param }
|
||||
%i[
|
||||
token
|
||||
private_token
|
||||
job_token
|
||||
access_token
|
||||
].each do |token_type|
|
||||
context "with :#{token_type}_param" do
|
||||
let(:type) { :"#{token_type}_param" }
|
||||
|
||||
context 'without credentials' do
|
||||
let(:request) { double(query_parameters: {}) }
|
||||
context 'without credentials' do
|
||||
let(:request) { double(query_parameters: {}) }
|
||||
|
||||
it 'returns nil' do
|
||||
expect(subject).to be_nil
|
||||
it 'returns nil' do
|
||||
expect(subject).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with credentials' do
|
||||
let(:password) { 'bar' }
|
||||
let(:request) { double(query_parameters: { 'token' => password }) }
|
||||
context 'with credentials' do
|
||||
let(:password) { 'bar' }
|
||||
let(:request) { double(query_parameters: { token_type.to_s => password }) }
|
||||
|
||||
it 'returns the credentials' do
|
||||
expect(subject.password).to eq(password)
|
||||
it 'returns the credentials' do
|
||||
expect(subject.password).to eq(password)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,23 +9,9 @@ RSpec.describe Gitlab::APIAuthentication::TokenResolver, feature_category: :syst
|
|||
let_it_be(:ci_job) { create(:ci_build, project: project, user: user, status: :running) }
|
||||
let_it_be(:ci_job_done) { create(:ci_build, project: project, user: user, status: :success) }
|
||||
let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
|
||||
|
||||
shared_examples 'an authorized request' do
|
||||
it 'returns the correct token' do
|
||||
expect(subject).to eq(token)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'an unauthorized request' do
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'an anoymous request' do
|
||||
it 'returns nil' do
|
||||
expect(subject).to eq(nil)
|
||||
end
|
||||
let_it_be(:oauth_application) { create(:oauth_application, owner: user) }
|
||||
let_it_be(:oauth_token) do
|
||||
create(:oauth_access_token, application_id: oauth_application.id, resource_owner_id: user.id, scopes: [:api])
|
||||
end
|
||||
|
||||
describe '.new' do
|
||||
|
|
@ -45,7 +31,25 @@ RSpec.describe Gitlab::APIAuthentication::TokenResolver, feature_category: :syst
|
|||
describe '#resolve' do
|
||||
let(:resolver) { described_class.new(type) }
|
||||
|
||||
subject { resolver.resolve(raw) }
|
||||
subject(:resolve) { resolver.resolve(raw) }
|
||||
|
||||
shared_examples 'an authorized request' do
|
||||
it 'returns the correct token' do
|
||||
expect(resolve).to eq(token)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'an unauthorized request' do
|
||||
it 'raises an error' do
|
||||
expect { resolve }.to raise_error(Gitlab::Auth::UnauthorizedError)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'an anoymous request' do
|
||||
it 'returns nil' do
|
||||
expect(resolve).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :personal_access_token_with_username' do
|
||||
let(:type) { :personal_access_token_with_username }
|
||||
|
|
@ -217,6 +221,32 @@ RSpec.describe Gitlab::APIAuthentication::TokenResolver, feature_category: :syst
|
|||
it_behaves_like 'an unauthorized request'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :oauth_token' do
|
||||
let(:type) { :oauth_token }
|
||||
let(:token) { oauth_token }
|
||||
|
||||
context 'with valid credentials' do
|
||||
let(:raw) { username_and_password(nil, oauth_token.plaintext_token) }
|
||||
|
||||
it_behaves_like 'an authorized request'
|
||||
|
||||
it 'refreshes token expiry and links the composite identity' do
|
||||
expect_next_found_instance_of(OauthAccessToken) do |token|
|
||||
expect(token).to receive(:revoke_previous_refresh_token!)
|
||||
expect(::Gitlab::Auth::Identity).to receive(:link_from_oauth_token).with(token)
|
||||
end
|
||||
|
||||
resolve
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid credentials' do
|
||||
let(:raw) { username_and_password(nil, 'invalid') }
|
||||
|
||||
it_behaves_like 'an unauthorized request'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def username_and_password(username, password)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -20,48 +20,11 @@ RSpec.describe Gitlab::Tracking::Destinations::SnowplowMicro, feature_category:
|
|||
|
||||
it { is_expected.to delegate_method(:flush).to(:tracker) }
|
||||
|
||||
describe '#hostname' do
|
||||
context 'when snowplow_micro config is set' do
|
||||
let(:address) { '127.0.0.1:9091' }
|
||||
|
||||
before do
|
||||
stub_config(snowplow_micro: snowplow_micro_settings)
|
||||
end
|
||||
|
||||
it 'returns proper URI' do
|
||||
expect(subject.hostname).to eq('127.0.0.1:9091')
|
||||
expect(subject.uri.scheme).to eq('http')
|
||||
end
|
||||
|
||||
context 'when gitlab config has https scheme' do
|
||||
before do
|
||||
stub_config_setting(https: true)
|
||||
end
|
||||
|
||||
it 'returns proper URI' do
|
||||
expect(subject.hostname).to eq('127.0.0.1:9091')
|
||||
expect(subject.uri.scheme).to eq('https')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when snowplow_micro config is not set' do
|
||||
before do
|
||||
allow(Gitlab.config).to receive(:snowplow_micro).and_raise(GitlabSettings::MissingSetting)
|
||||
end
|
||||
|
||||
it 'returns localhost hostname' do
|
||||
expect(subject.hostname).to eq('localhost:9090')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#snowplow_options' do
|
||||
let_it_be(:group) { create :group }
|
||||
|
||||
before do
|
||||
stub_config(snowplow_micro: snowplow_micro_settings)
|
||||
stub_application_setting(snowplow_enabled?: true)
|
||||
end
|
||||
|
||||
it 'adds Snowplow micro specific options to the parent Snowplow options' do
|
||||
|
|
@ -88,51 +51,4 @@ RSpec.describe Gitlab::Tracking::Destinations::SnowplowMicro, feature_category:
|
|||
expect(options).to include(base_options)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#frontend_client_options' do
|
||||
let_it_be(:group) { create :group }
|
||||
|
||||
before do
|
||||
stub_config(snowplow_micro: snowplow_micro_settings)
|
||||
end
|
||||
|
||||
context 'when snowplow is enabled' do
|
||||
before do
|
||||
stub_application_setting(snowplow_enabled?: true)
|
||||
stub_feature_flags(additional_snowplow_tracking: true)
|
||||
end
|
||||
|
||||
it 'includes snowplow_options with Snowplow micro-specific overrides' do
|
||||
expect(subject).to receive(:snowplow_options).with(group).and_call_original
|
||||
|
||||
options = subject.frontend_client_options(group)
|
||||
|
||||
expect(options).to include(
|
||||
protocol: 'http',
|
||||
port: 9091,
|
||||
forceSecureTracker: false
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when snowplow is disabled' do
|
||||
before do
|
||||
stub_application_setting(snowplow_enabled?: false)
|
||||
allow(Gitlab).to receive(:host_with_port).and_return('gitlab.example.com')
|
||||
allow(Gitlab.config.gitlab).to receive(:https).and_return(true)
|
||||
end
|
||||
|
||||
it 'returns product_usage_events options' do
|
||||
expect(subject).not_to receive(:snowplow_options)
|
||||
|
||||
options = subject.frontend_client_options(group)
|
||||
|
||||
expect(options).to include(
|
||||
hostname: 'gitlab.example.com',
|
||||
postPath: '/-/collect_events',
|
||||
forceSecureTracker: true
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_b
|
|||
describe '#event' do
|
||||
context 'when event is eligible' do
|
||||
before do
|
||||
allow_next_instance_of(Gitlab::Tracking::Destinations::DestinationConfiguration) do |config|
|
||||
allow(config).to receive_messages(hostname: 'gitfoo.com', protocol: 'https')
|
||||
end
|
||||
|
||||
expect(SnowplowTracker::AsyncEmitter)
|
||||
.to receive(:new)
|
||||
.with(endpoint: 'gitfoo.com',
|
||||
|
|
@ -115,6 +119,10 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_b
|
|||
allow(SnowplowTracker::Tracker).to receive(:new).and_return(tracker)
|
||||
allow(tracker).to receive(:track_struct_event).and_call_original
|
||||
|
||||
allow_next_instance_of(Gitlab::Tracking::Destinations::DestinationConfiguration) do |config|
|
||||
allow(config).to receive_messages(hostname: 'gitfoo.com', protocol: 'https')
|
||||
end
|
||||
|
||||
expect(SnowplowTracker::AsyncEmitter)
|
||||
.to receive(:new)
|
||||
.with(endpoint: 'gitfoo.com',
|
||||
|
|
@ -249,86 +257,39 @@ RSpec.describe Gitlab::Tracking::Destinations::Snowplow, :do_not_stub_snowplow_b
|
|||
stub_application_setting(snowplow_enabled?: false)
|
||||
end
|
||||
|
||||
context 'and collect_product_usage_events is enabled' do
|
||||
it 'returns true' do
|
||||
expect(subject.enabled?).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'and collect_product_usage_events is disabled' do
|
||||
before do
|
||||
stub_feature_flags(collect_product_usage_events: false)
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
expect(subject.enabled?).to be_falsey
|
||||
end
|
||||
it 'returns true' do
|
||||
expect(subject.enabled?).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#hostname' do
|
||||
describe '#app_id' do
|
||||
subject { described_class.new.app_id }
|
||||
|
||||
context 'when snowplow is enabled' do
|
||||
before do
|
||||
stub_application_setting(snowplow_enabled?: true)
|
||||
end
|
||||
|
||||
it 'returns snowplow_collector_hostname' do
|
||||
expect(subject.hostname).to eq('gitfoo.com')
|
||||
end
|
||||
it { is_expected.to eq('_abc123_') }
|
||||
end
|
||||
|
||||
context 'when snowplow is disabled' do
|
||||
before do
|
||||
stub_application_setting(snowplow_enabled?: false)
|
||||
stub_feature_flags(use_staging_endpoint_for_product_usage_events: enable_stg_events)
|
||||
stub_application_setting(gitlab_dedicated_instance?: dedicated_instance)
|
||||
end
|
||||
|
||||
context "with use_staging_endpoint_for_product_usage_events FF disabled" do
|
||||
let(:enable_stg_events) { false }
|
||||
context 'when dedicated instance' do
|
||||
let(:dedicated_instance) { true }
|
||||
|
||||
it 'returns product usage event collection hostname' do
|
||||
expect(subject.hostname).to eq('events.gitlab.net')
|
||||
end
|
||||
it { is_expected.to eq('gitlab_dedicated') }
|
||||
end
|
||||
|
||||
context "with use_staging_endpoint_for_product_usage_events FF enabled" do
|
||||
let(:enable_stg_events) { true }
|
||||
context 'when self-hosted instance' do
|
||||
let(:dedicated_instance) { false }
|
||||
|
||||
it 'returns product usage event collection hostname' do
|
||||
expect(subject.hostname).to eq('events-stg.gitlab.net')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#app_id' do
|
||||
subject { described_class.new.app_id }
|
||||
|
||||
context 'when snowplow is enabled' do
|
||||
before do
|
||||
stub_application_setting(snowplow_enabled?: true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq('_abc123_') }
|
||||
end
|
||||
|
||||
context 'when snowplow is disabled' do
|
||||
before do
|
||||
stub_application_setting(snowplow_enabled?: false)
|
||||
stub_application_setting(gitlab_dedicated_instance?: dedicated_instance)
|
||||
end
|
||||
|
||||
context 'when dedicated instance' do
|
||||
let(:dedicated_instance) { true }
|
||||
|
||||
it { is_expected.to eq('gitlab_dedicated') }
|
||||
end
|
||||
|
||||
context 'when self-hosted instance' do
|
||||
let(:dedicated_instance) { false }
|
||||
|
||||
it { is_expected.to eq('gitlab_sm') }
|
||||
end
|
||||
it { is_expected.to eq('gitlab_sm') }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,42 +12,21 @@ RSpec.describe Gitlab::Tracking::EventEligibilityChecker, feature_category: :ser
|
|||
|
||||
subject { checker.eligible?(event_name) }
|
||||
|
||||
context 'when collect_product_usage_events feature flag is enabled' do
|
||||
where(:product_usage_data_enabled, :snowplow_enabled, :result) do
|
||||
true | false | true
|
||||
false | true | true
|
||||
false | false | false
|
||||
end
|
||||
|
||||
before do
|
||||
stub_application_setting(
|
||||
snowplow_enabled: snowplow_enabled,
|
||||
gitlab_product_usage_data_enabled?: product_usage_data_enabled
|
||||
)
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { is_expected.to eq(result) }
|
||||
end
|
||||
where(:product_usage_data_enabled, :snowplow_enabled, :result) do
|
||||
true | false | true
|
||||
false | true | true
|
||||
false | false | false
|
||||
end
|
||||
|
||||
context 'when collect_product_usage_events feature flag is disabled' do
|
||||
where(:product_usage_data_enabled, :snowplow_enabled, :result) do
|
||||
true | false | false
|
||||
false | true | true
|
||||
false | false | false
|
||||
end
|
||||
before do
|
||||
stub_application_setting(
|
||||
snowplow_enabled: snowplow_enabled,
|
||||
gitlab_product_usage_data_enabled?: product_usage_data_enabled
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(collect_product_usage_events: false)
|
||||
stub_application_setting(
|
||||
snowplow_enabled?: snowplow_enabled, gitlab_product_usage_data_enabled?: product_usage_data_enabled
|
||||
)
|
||||
end
|
||||
|
||||
with_them do
|
||||
it { is_expected.to eq(result) }
|
||||
end
|
||||
with_them do
|
||||
it { is_expected.to eq(result) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -62,7 +41,6 @@ RSpec.describe Gitlab::Tracking::EventEligibilityChecker, feature_category: :ser
|
|||
end
|
||||
|
||||
before do
|
||||
stub_feature_flags(collect_product_usage_events: false)
|
||||
stub_application_setting(
|
||||
snowplow_enabled?: snowplow_enabled, gitlab_product_usage_data_enabled?: product_usage_data_enabled
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -244,6 +244,7 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
|
|||
instance_of(WorkItems::Widgets::Hierarchy),
|
||||
instance_of(WorkItems::Widgets::Labels),
|
||||
instance_of(WorkItems::Widgets::LinkedItems),
|
||||
instance_of(WorkItems::Widgets::LinkedResources),
|
||||
instance_of(WorkItems::Widgets::Milestone),
|
||||
instance_of(WorkItems::Widgets::Notes),
|
||||
instance_of(WorkItems::Widgets::Notifications),
|
||||
|
|
@ -293,6 +294,7 @@ RSpec.describe WorkItem, feature_category: :portfolio_management do
|
|||
instance_of(WorkItems::Widgets::Hierarchy),
|
||||
instance_of(WorkItems::Widgets::Labels),
|
||||
instance_of(WorkItems::Widgets::LinkedItems),
|
||||
instance_of(WorkItems::Widgets::LinkedResources),
|
||||
instance_of(WorkItems::Widgets::Notes),
|
||||
instance_of(WorkItems::Widgets::Notifications),
|
||||
instance_of(WorkItems::Widgets::Participants),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do
|
|||
::WorkItems::Widgets::CurrentUserTodos,
|
||||
::WorkItems::Widgets::AwardEmoji,
|
||||
::WorkItems::Widgets::LinkedItems,
|
||||
::WorkItems::Widgets::LinkedResources,
|
||||
::WorkItems::Widgets::Participants,
|
||||
::WorkItems::Widgets::TimeTracking,
|
||||
::WorkItems::Widgets::Designs,
|
||||
|
|
@ -129,9 +130,7 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do
|
|||
end
|
||||
|
||||
describe '.widget_classes' do
|
||||
# Excluding LinkedResources as the linked_resources widget is still not added to any work item type
|
||||
# Follow-up will add the widget to the types as part of https://gitlab.com/gitlab-org/gitlab/-/issues/372482
|
||||
subject { described_class.widget_classes - [::WorkItems::Widgets::LinkedResources] }
|
||||
subject { described_class.widget_classes }
|
||||
|
||||
it 'returns all widget classes no matter if disabled or not' do
|
||||
is_expected.to match_array(all_widget_classes)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -877,7 +877,8 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do
|
|||
{ "type" => "NOTIFICATIONS" },
|
||||
{ "type" => "PARTICIPANTS" },
|
||||
{ "type" => "START_AND_DUE_DATE" },
|
||||
{ "type" => "TIME_TRACKING" }
|
||||
{ "type" => "TIME_TRACKING" },
|
||||
{ "type" => "LINKED_RESOURCES" }
|
||||
]
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19,12 +19,11 @@ RSpec.describe EventForward::EventForwardController, feature_category: :product_
|
|||
|
||||
before do
|
||||
allow(Gitlab::Tracking).to receive(:tracker).and_return(tracker)
|
||||
allow(tracker).to receive(:emit_event_payload)
|
||||
allow(tracker).to receive_messages(emit_event_payload: nil, enabled?: true, hostname: 'localhost')
|
||||
allow(Gitlab::Tracking::EventEligibilityChecker).to receive(:new).and_return(event_eligibility_checker)
|
||||
allow(event_eligibility_checker).to receive(:eligible?).and_return(true)
|
||||
allow(EventForward::Logger).to receive(:build).and_return(logger)
|
||||
allow(logger).to receive(:info)
|
||||
stub_feature_flags(collect_product_usage_events: true)
|
||||
end
|
||||
|
||||
describe 'POST #forward' do
|
||||
|
|
@ -75,20 +74,6 @@ RSpec.describe EventForward::EventForwardController, feature_category: :product_
|
|||
expect(response.body).to be_empty
|
||||
end
|
||||
|
||||
context 'when feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(collect_product_usage_events: false)
|
||||
end
|
||||
|
||||
it 'returns 404 and do not call tracker' do
|
||||
expect(tracker).not_to receive(:emit_event_payload)
|
||||
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when filtering events by eligibility' do
|
||||
before do
|
||||
allow(event_eligibility_checker).to receive(:eligible?).with("event_1", "app_id_1").and_return(true)
|
||||
|
|
|
|||
|
|
@ -70,28 +70,6 @@ RSpec.describe Projects::CommitController, feature_category: :source_code_manage
|
|||
expect(assigns(:environment)).to be_nil
|
||||
expect(response).to render_template(:rapid_diffs)
|
||||
end
|
||||
|
||||
context 'for stream_url' do
|
||||
it 'returns stream_url with offset' do
|
||||
send_request
|
||||
|
||||
url = "/#{project.full_path}/-/commit/#{commit.id}/diffs_stream?offset=5&view=inline"
|
||||
|
||||
expect(assigns(:stream_url)).to eq(url)
|
||||
end
|
||||
|
||||
context 'when view is set to parallel' do
|
||||
let_it_be(:diff_view) { :parallel }
|
||||
|
||||
it 'returns stream_url with parallel view' do
|
||||
send_request
|
||||
|
||||
url = "/#{project.full_path}/-/commit/#{commit.id}/diffs_stream?offset=5&view=parallel"
|
||||
|
||||
expect(assigns(:stream_url)).to eq(url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #diff_files_metadata' do
|
||||
|
|
|
|||
|
|
@ -65,12 +65,6 @@ RSpec.describe 'Merge Request Creation', feature_category: :code_review_workflow
|
|||
|
||||
expect(flash[:alert]).to be_present
|
||||
end
|
||||
|
||||
it 'assigns show_whitespace_default' do
|
||||
get_diffs
|
||||
|
||||
expect(assigns(:show_whitespace_default)).to be(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -293,26 +293,6 @@ RSpec.describe Projects::MergeRequestsController, feature_category: :source_code
|
|||
|
||||
expect(response.body.scan('<diff-file ').size).to eq(5)
|
||||
end
|
||||
|
||||
context 'for stream_url' do
|
||||
it 'returns stream_url with offset' do
|
||||
get diffs_project_merge_request_path(project, merge_request, rapid_diffs: 'true')
|
||||
|
||||
url = "/#{project.full_path}/-/merge_requests/#{merge_request.iid}/diffs_stream?offset=5&view=inline"
|
||||
|
||||
expect(assigns(:stream_url)).to eq(url)
|
||||
end
|
||||
|
||||
context 'when view is set to parallel' do
|
||||
it 'returns stream_url with parallel view' do
|
||||
get diffs_project_merge_request_path(project, merge_request, rapid_diffs: 'true', view: 'parallel')
|
||||
|
||||
url = "/#{project.full_path}/-/merge_requests/#{merge_request.iid}/diffs_stream?offset=5&view=parallel"
|
||||
|
||||
expect(assigns(:stream_url)).to eq(url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
module StubSnowplow
|
||||
def stub_snowplow
|
||||
# Make sure that a new tracker with the stubed data is used
|
||||
Gitlab::Tracking.remove_instance_variable(:@tracker) if Gitlab::Tracking.instance_variable_defined?(:@tracker)
|
||||
|
||||
# WebMock is set up to allow requests to `localhost`
|
||||
host = 'localhost'
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ go 1.23.0
|
|||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1
|
||||
github.com/BurntSushi/toml v1.4.0
|
||||
github.com/alecthomas/chroma/v2 v2.18.0
|
||||
github.com/alicebob/miniredis/v2 v2.34.0
|
||||
|
|
@ -54,11 +54,11 @@ require (
|
|||
cloud.google.com/go/storage v1.44.0 // indirect
|
||||
cloud.google.com/go/trace v1.11.0 // indirect
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||
github.com/DataDog/datadog-go v4.4.0+incompatible // indirect
|
||||
github.com/DataDog/sketches-go v1.0.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
|
||||
|
|
|
|||
|
|
@ -70,26 +70,26 @@ contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waT
|
|||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0 h1:+m0M/LFxN43KvULkDNfdXOgrjtg6UYJPFBJyuEcRCAw=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.0/go.mod h1:PwOyop78lveYMRs6oCxjiVyBdyCgIYH6XHIVZO9/SFQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0 h1:LR0kAX9ykz8G4YgLCaRDVJ3+n43R8MneB5dTy2konZo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0/go.mod h1:DWAciXemNf++PQJLeXUB4HHH5OpsAh12HZnu2wXE1jA=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 h1:lhZdRq7TIx0GJQvSyX2Si406vrYsov2FXGp/RnSEtcs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
|
|
@ -460,8 +460,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
|||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs=
|
||||
github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
|
|
|
|||
|
|
@ -1485,10 +1485,10 @@
|
|||
dependencies:
|
||||
"@vue/devtools-api" "^6.0.0-beta.11"
|
||||
|
||||
"@gitlab/web-ide@^0.0.1-dev-20250618150607":
|
||||
version "0.0.1-dev-20250618150607"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20250618150607.tgz#fba03e3053990921fcdacba021e84303de20b605"
|
||||
integrity sha512-vij4a2yu0iXAy0wAJhVXmJUsyGFwzblOfNtl8YP+SpEBXF1xs6AvRb9p5m3oucp8xDN2hkFSQ6xkHvF0N2G2/g==
|
||||
"@gitlab/web-ide@^0.0.1-dev-20250625063230":
|
||||
version "0.0.1-dev-20250625063230"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20250625063230.tgz#ec6c72716c0dd688898f5037f63ac253ad6924f5"
|
||||
integrity sha512-pvKeWRuZb4LSXkbUYEmxvQ3Nony9s8CHNY9XywEgEQs49upUPzmfSLl6tftQVs3pKsq2KK8AokR8DJ6bD1Egvg==
|
||||
|
||||
"@gleam-lang/highlight.js-gleam@^1.5.0":
|
||||
version "1.5.0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue