Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-01-19 12:17:41 +00:00
parent a87e8cad90
commit e67f3f55d2
90 changed files with 711 additions and 1991 deletions

View file

@ -325,7 +325,6 @@ Performance/Sum:
- 'lib/container_registry/tag.rb'
- 'lib/gitlab/ci/reports/test_suite_comparer.rb'
- 'lib/gitlab/diff/file.rb'
- 'lib/gitlab/sherlock/transaction.rb'
- 'lib/gitlab/usage_data.rb'
- 'lib/peek/views/detailed_view.rb'
- 'spec/models/namespace/root_storage_statistics_spec.rb'

View file

@ -26,7 +26,6 @@ Database/MultipleDatabases:
- lib/gitlab/import_export/group/relation_tree_restorer.rb
- lib/gitlab/legacy_github_import/importer.rb
- lib/gitlab/seeder.rb
- lib/gitlab/sherlock/query.rb
- lib/system_check/orphans/repository_check.rb
- spec/db/schema_spec.rb
- spec/initializers/database_config_spec.rb

View file

@ -76,7 +76,6 @@ Rails/TimeZone:
- lib/gitlab/prometheus/queries/additional_metrics_environment_query.rb
- lib/gitlab/prometheus/queries/matched_metric_query.rb
- lib/gitlab/prometheus_client.rb
- lib/gitlab/sherlock/transaction.rb
- lib/gitlab/task_helpers.rb
- lib/gitlab/x509/tag.rb
- lib/grafana/time_window.rb
@ -141,7 +140,6 @@ Rails/TimeZone:
- spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
- spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
- spec/lib/gitlab/prometheus/queries/validate_query_spec.rb
- spec/lib/gitlab/sherlock/transaction_spec.rb
- spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
- spec/lib/gitlab/sidekiq_middleware/duplicate_jobs/strategies/until_executing_spec.rb
- spec/lib/gitlab/updated_notes_paginator_spec.rb

View file

@ -6,7 +6,7 @@ export default {
i18n: {
approvalNeeded: s__('mrWidget|Merge blocked: this merge request must be approved.'),
blockingMergeRequests: s__(
'mrWidget|Merge blocked: you can only merge once above items are resolved.',
'mrWidget|Merge blocked: you can only merge after the above items are resolved.',
),
},
components: {

View file

@ -179,6 +179,9 @@ export default {
this.searchKey = '';
this.setFocus();
},
selectFirstItem() {
this.$refs.dropdownContentsView.selectFirstItem();
},
},
};
</script>
@ -204,11 +207,13 @@ export default {
@toggleDropdownContentsCreateView="toggleDropdownContent"
@closeDropdown="$emit('closeDropdown')"
@input="debouncedSearchKeyUpdate"
@searchEnter="selectFirstItem"
/>
</template>
<template #default>
<component
:is="dropdownContentsView"
ref="dropdownContentsView"
v-model="localSelectedLabels"
:search-key="searchKey"
:allow-multiselect="allowMultiselect"

View file

@ -84,6 +84,9 @@ export default {
showNoMatchingResultsMessage() {
return Boolean(this.searchKey) && this.visibleLabels.length === 0;
},
shouldHighlightFirstItem() {
return this.searchKey !== '' && this.visibleLabels.length > 0;
},
},
methods: {
isLabelSelected(label) {
@ -128,6 +131,11 @@ export default {
onDropdownAppear() {
this.isVisible = true;
},
selectFirstItem() {
if (this.shouldHighlightFirstItem) {
this.handleLabelClick(this.visibleLabels[0]);
}
},
},
};
</script>
@ -143,11 +151,13 @@ export default {
/>
<template v-else>
<gl-dropdown-item
v-for="label in visibleLabels"
v-for="(label, index) in visibleLabels"
:key="label.id"
:is-checked="isLabelSelected(label)"
:is-check-centered="true"
:is-check-item="true"
:active="shouldHighlightFirstItem && index === 0"
active-class="is-focused"
data-testid="labels-list"
@click.native.capture.stop="handleLabelClick(label)"
>

View file

@ -83,6 +83,7 @@ export default {
data-qa-selector="dropdown_input_field"
data-testid="dropdown-input-field"
@input="$emit('input', $event)"
@keydown.enter="$emit('searchEnter', $event)"
/>
</div>
</template>

View file

@ -28,7 +28,6 @@
@import './pages/service_desk';
@import './pages/settings';
@import './pages/settings_ci_cd';
@import './pages/sherlock';
@import './pages/storage_quota';
@import './pages/tree';
@import './pages/users';

View file

@ -1,31 +0,0 @@
table .sherlock-code {
max-width: 700px;
}
.sherlock-code {
pre {
word-wrap: normal;
code {
white-space: pre;
}
}
}
.sherlock-line-samples-table {
thead th,
tbody td {
font-size: 13px !important;
text-align: right;
padding: 0 10px !important;
}
.slow {
color: $red-500;
font-weight: $gl-font-weight-bold;
}
}
.sherlock-file-sample pre {
padding-top: 28px !important;
}

View file

@ -1,14 +0,0 @@
# frozen_string_literal: true
module Sherlock
class ApplicationController < ::ApplicationController
before_action :find_transaction
def find_transaction
if params[:transaction_id]
@transaction = Gitlab::Sherlock.collection
.find_transaction(params[:transaction_id])
end
end
end
end

View file

@ -1,9 +0,0 @@
# frozen_string_literal: true
module Sherlock
class FileSamplesController < Sherlock::ApplicationController
def show
@file_sample = @transaction.find_file_sample(params[:id])
end
end
end

View file

@ -1,9 +0,0 @@
# frozen_string_literal: true
module Sherlock
class QueriesController < Sherlock::ApplicationController
def show
@query = @transaction.find_query(params[:id])
end
end
end

View file

@ -1,21 +0,0 @@
# frozen_string_literal: true
module Sherlock
class TransactionsController < Sherlock::ApplicationController
def index
@transactions = Gitlab::Sherlock.collection.newest_first
end
def show
@transaction = Gitlab::Sherlock.collection.find_transaction(params[:id])
render_404 unless @transaction
end
def destroy_all
Gitlab::Sherlock.collection.clear
redirect_back_or_default(options: { status: :found })
end
end
end

View file

@ -189,15 +189,6 @@ module Nav
end
end
# rubocop: enable Cop/UserAdmin
if Gitlab::Sherlock.enabled?
builder.add_secondary_menu_item(
id: 'sherlock',
title: _('Sherlock Transactions'),
icon: 'admin',
href: sherlock_transactions_path
)
end
end
def projects_menu_item_attrs

View file

@ -46,10 +46,6 @@ module NavHelper
class_names
end
def has_extra_nav_icons?
Gitlab::Sherlock.enabled? || current_user.admin?
end
def page_has_markdown?
current_path?('merge_requests#show') ||
current_path?('projects/merge_requests/conflicts#show') ||

View file

@ -78,6 +78,10 @@ class ApplicationSetting < ApplicationRecord
chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds
chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
chronic_duration_attr :group_runner_token_expiration_interval_human_readable, :group_runner_token_expiration_interval
chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval
validates :grafana_url,
system_hook_url: {
blocked_message: "is blocked: %{exception_message}. " + GRAFANA_URL_ERROR_MESSAGE

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
module RunnerTokenExpirationInterval
extend ActiveSupport::Concern
def enforced_runner_token_expiration_interval_human_readable
interval = enforced_runner_token_expiration_interval
ChronicDuration.output(interval, format: :short) if interval
end
def effective_runner_token_expiration_interval
[
enforced_runner_token_expiration_interval,
runner_token_expiration_interval&.seconds
].compact.min
end
def effective_runner_token_expiration_interval_human_readable
interval = effective_runner_token_expiration_interval
ChronicDuration.output(interval, format: :short) if interval
end
end

View file

@ -17,6 +17,8 @@ class Group < Namespace
include GroupAPICompatibility
include EachBatch
include BulkMemberAccessLoad
include ChronicDurationAttribute
include RunnerTokenExpirationInterval
def self.sti_name
'Group'
@ -91,6 +93,9 @@ class Group < Namespace
has_many :group_callouts, class_name: 'Users::GroupCallout', foreign_key: :group_id
delegate :prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap, :setup_for_company, :jobs_to_be_done, to: :namespace_settings
delegate :runner_token_expiration_interval, :runner_token_expiration_interval=, :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
delegate :subgroup_runner_token_expiration_interval, :subgroup_runner_token_expiration_interval=, :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
delegate :project_runner_token_expiration_interval, :project_runner_token_expiration_interval=, :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
has_one :crm_settings, class_name: 'Group::CrmSettings', inverse_of: :group
@ -788,6 +793,17 @@ class Group < Namespace
shared_with_group_links.preload_shared_with_groups.filter { |link| Ability.allowed?(user, :read_group, link.shared_with_group) }
end
def enforced_runner_token_expiration_interval
all_parent_groups = Gitlab::ObjectHierarchy.new(Group.where(id: id)).ancestors
all_group_settings = NamespaceSetting.where(namespace_id: all_parent_groups)
group_interval = all_group_settings.where.not(subgroup_runner_token_expiration_interval: nil).minimum(:subgroup_runner_token_expiration_interval)&.seconds
[
Gitlab::CurrentSettings.group_runner_token_expiration_interval&.seconds,
group_interval
].compact.min
end
private
def max_member_access(user_ids)

View file

@ -3,6 +3,7 @@
class NamespaceSetting < ApplicationRecord
include CascadingNamespaceSettingAttribute
include Sanitizable
include ChronicDurationAttribute
cascading_attr :delayed_project_removal
@ -16,10 +17,15 @@ class NamespaceSetting < ApplicationRecord
enum jobs_to_be_done: { basics: 0, move_repository: 1, code_storage: 2, exploring: 3, ci: 4, other: 5 }, _suffix: true
chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
chronic_duration_attr :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval
chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval
NAMESPACE_SETTINGS_PARAMS = [:default_branch_name, :delayed_project_removal,
:lock_delayed_project_removal, :resource_access_token_creation_allowed,
:prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap,
:setup_for_company, :jobs_to_be_done].freeze
:setup_for_company, :jobs_to_be_done, :runner_token_expiration_interval,
:subgroup_runner_token_expiration_interval, :project_runner_token_expiration_interval].freeze
self.primary_key = :namespace_id

View file

@ -37,6 +37,7 @@ class Project < ApplicationRecord
include EachBatch
include GitlabRoutingHelper
include BulkMemberAccessLoad
include RunnerTokenExpirationInterval
extend Gitlab::Cache::RequestCache
extend Gitlab::Utils::Override
@ -454,6 +455,7 @@ class Project < ApplicationRecord
delegate :job_token_scope_enabled, :job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :keep_latest_artifact, :keep_latest_artifact=, to: :ci_cd_settings, allow_nil: true
delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, to: :ci_cd_settings, allow_nil: true
delegate :runner_token_expiration_interval, :runner_token_expiration_interval=, :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval_human_readable=, to: :ci_cd_settings, allow_nil: true
delegate :actual_limits, :actual_plan_name, :actual_plan, to: :namespace, allow_nil: true
delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?,
:allow_merge_on_skipped_pipeline=, :has_confluence?, :has_shimo?,
@ -2697,6 +2699,10 @@ class Project < ApplicationRecord
ci_cd_settings.keep_latest_artifact?
end
def runner_token_expiration_interval
ci_cd_settings&.runner_token_expiration_interval
end
def group_runners_enabled?
return false unless ci_cd_settings
@ -2728,6 +2734,17 @@ class Project < ApplicationRecord
end
end
def enforced_runner_token_expiration_interval
all_parent_groups = Gitlab::ObjectHierarchy.new(Group.where(id: group)).base_and_ancestors
all_group_settings = NamespaceSetting.where(namespace_id: all_parent_groups)
group_interval = all_group_settings.where.not(project_runner_token_expiration_interval: nil).minimum(:project_runner_token_expiration_interval)&.seconds
[
Gitlab::CurrentSettings.project_runner_token_expiration_interval&.seconds,
group_interval
].compact.min
end
private
# overridden in EE

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class ProjectCiCdSetting < ApplicationRecord
include ChronicDurationAttribute
belongs_to :project, inverse_of: :ci_cd_settings
DEFAULT_GIT_DEPTH = 20
@ -17,6 +19,8 @@ class ProjectCiCdSetting < ApplicationRecord
default_value_for :forward_deployment_enabled, true
chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
def forward_deployment_enabled?
super && ::Feature.enabled?(:forward_deployment_enabled, project, default_enabled: true)
end

View file

@ -46,7 +46,6 @@ module Issues
super
params.delete(:issue_type) unless create_issue_type_allowed?(issue, params[:issue_type])
filter_incident_label(issue) if params[:issue_type]
moved_issue = params.delete(:moved_issue)
@ -89,37 +88,6 @@ module Issues
Milestones::IssuesCountService.new(milestone).delete_cache
end
# @param issue [Issue]
def filter_incident_label(issue)
return unless add_incident_label?(issue) || remove_incident_label?(issue)
label = ::IncidentManagement::CreateIncidentLabelService
.new(project, current_user)
.execute
.payload[:label]
# These(add_label_ids, remove_label_ids) are being added ahead of time
# to be consumed by #process_label_ids, this allows system notes
# to be applied correctly alongside the label updates.
if add_incident_label?(issue)
params[:add_label_ids] ||= []
params[:add_label_ids] << label.id
else
params[:remove_label_ids] ||= []
params[:remove_label_ids] << label.id
end
end
# @param issue [Issue]
def add_incident_label?(issue)
issue.incident?
end
# @param _issue [Issue, nil]
def remove_incident_label?(_issue)
false
end
end
end

View file

@ -236,16 +236,6 @@ module Issues
SystemNoteService.change_issue_confidentiality(issue, issue.project, current_user)
end
override :add_incident_label?
def add_incident_label?(issue)
issue.issue_type != params[:issue_type] && !issue.incident?
end
override :remove_incident_label?
def remove_incident_label?(issue)
issue.issue_type != params[:issue_type] && issue.incident?
end
def handle_issue_type_change(issue)
return unless issue.previous_changes.include?('issue_type')

View file

@ -36,5 +36,5 @@
#{diff_file.a_mode} → #{diff_file.b_mode}
- if diff_file.stored_externally? && diff_file.external_storage == :lfs
%span.badge.label-lfs.gl-mr-2 LFS
= gl_badge_tag(_('LFS'), variant: :neutral)

View file

@ -1,55 +0,0 @@
- page_title t('sherlock.title'), t('sherlock.transaction'),
t('sherlock.file_sample')
- header_title t('sherlock.title'), sherlock_transactions_path
.row-content-block
.float-right
= link_to(sherlock_transaction_path(@transaction), class: 'btn gl-button') do
= sprite_icon('arrow-left')
= t('sherlock.transaction')
.oneline
= t('sherlock.file_sample')
= @file_sample.id
.gl-mt-3
%p
%span.light
#{t('sherlock.time')}:
%strong
= @file_sample.duration.round(2)
= t('sherlock.milliseconds')
%p
%span.light
#{t('sherlock.events')}:
%strong
= @file_sample.events
%article.file-holder
.js-file-title.file-title
= sprite_icon("doc-text")
%strong
= @file_sample.file
.code.file-content.js-syntax-highlight
.line-numbers
%table.sherlock-line-samples-table.gl-mb-0
%thead
%tr
%th= t('sherlock.line_capitalized')
%th= t('sherlock.events')
%th= t('sherlock.time')
%th= t('sherlock.percent')
%tbody
- @file_sample.line_samples.each_with_index do |sample, index|
%tr{ class: sample.majority_of?(@file_sample.duration) ? 'slow' : '' }
%td= index + 1
%td= sample.events
%td
= sample.duration.round(2)
= t('sherlock.milliseconds')
%td
= sample.percentage_of(@file_sample.duration).round
= t('sherlock.percent')
.sherlock-file-sample
= highlight(@file_sample.file, @file_sample.source)

View file

@ -1,31 +0,0 @@
.gl-mt-3
.card
.card-header
%strong
= t('sherlock.application_backtrace')
%ul.content-list
- @query.application_backtrace.each do |location|
%li
%strong
- if defined?(BetterErrors)
= link_to(location.path, BetterErrors.editor.url(location.path, location.line))
- else
= location.path
%small.light
= t('sherlock.line')
= location.line
.card
.card-header
%strong
= t('sherlock.full_backtrace')
%ul.content-list
- @query.backtrace.each do |location|
%li
- if location.application?
%strong= location.path
- else
= location.path
%small.light
= t('sherlock.line')
= location.line

View file

@ -1,54 +0,0 @@
.gl-mt-3
.card
.card-header
%strong
= t('sherlock.general')
%ul.content-list
%li
%span.light
#{t('sherlock.time')}:
%strong
= @query.duration.round(4)
= t('sherlock.milliseconds')
%li
- frame = @query.last_application_frame
%span.light
#{t('sherlock.origin')}:
%strong
- if defined?(BetterErrors)
= link_to(frame.path, BetterErrors.editor.url(frame.path, frame.line))
- else
= frame.path
%small.light
= t('sherlock.line')
= frame.line
.card
.card-header
.float-right
%button.js-clipboard-trigger.gl-button.btn.btn-default.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button }
= sprite_icon('copy-to-clipboard')
%pre.hidden
= @query.formatted_query
%strong
= t('sherlock.query')
%ul.content-list
%li
.code.js-syntax-highlight.sherlock-code
:preserve
#{highlight("#{@query.id}.sql", @query.formatted_query, language: 'sql')}
.card
.card-header
.float-right
%button.js-clipboard-trigger.gl-button.btn.btn-default.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button }
= sprite_icon('copy-to-clipboard')
%pre.hidden
= @query.explain
%strong
= t('sherlock.query_plan')
%ul.content-list
%li
.code.js-syntax-highlight.sherlock-code
%pre
%code= @query.explain

View file

@ -1,26 +0,0 @@
- page_title t('sherlock.title'), t('sherlock.transaction'), t('sherlock.query')
- header_title t('sherlock.title'), sherlock_transactions_path
%ul.nav-links.nav.nav-tabs
%li.active
%a{ href: "#tab-general", data: { toggle: "tab" } }
= t('sherlock.general')
%li
%a{ href: "#tab-backtrace", data: { toggle: "tab" } }
= t('sherlock.backtrace')
.row-content-block
.float-right
= link_to(sherlock_transaction_path(@transaction), class: 'btn gl-button btn-default') do
= sprite_icon('arrow-left')
= t('sherlock.transaction')
.oneline
= t('sherlock.query')
= @query.id
.tab-content
.tab-pane.active#tab-general
= render(partial: 'general')
.tab-pane#tab-backtrace
= render(partial: 'backtrace')

View file

@ -1,24 +0,0 @@
- if @transaction.file_samples.empty?
.nothing-here-block
= t('sherlock.no_file_samples')
- else
.table-holder
%table.table
%thead
%tr
%th= t('sherlock.time_inclusive')
%th= t('sherlock.count')
%th= t('sherlock.path')
%th
%tbody
- @transaction.sorted_file_samples.each do |sample|
%tr
%td
= sample.duration.round(2)
= t('sherlock.milliseconds')
%td= @transaction.view_counts.fetch(sample.file, 1)
%td= sample.relative_path
%td
= link_to(t('sherlock.view'),
sherlock_transaction_file_sample_path(@transaction, sample),
class: 'gl-button btn btn-sm')

View file

@ -1,38 +0,0 @@
.gl-mt-3
.card
.card-header
%strong
= t('sherlock.general')
%ul.content-list
%li
%span.light
#{t('sherlock.id')}:
%strong
= @transaction.id
%li
%span.light
#{t('sherlock.type')}:
%strong
= @transaction.type
%li
%span.light
#{t('sherlock.path')}:
%strong
= @transaction.path
%li
%span.light
#{t('sherlock.time')}:
%strong
= @transaction.duration.round(2)
= t('sherlock.seconds')
%li
%span.light
#{t('sherlock.query_time')}
%strong
= @transaction.query_duration.round(2)
= t('sherlock.seconds')
%li
%span.light
#{t('sherlock.finished_at')}:
%strong
= time_ago_with_tooltip @transaction.finished_at

View file

@ -1,24 +0,0 @@
- if @transaction.queries.empty?
.nothing-here-block
= t('sherlock.no_queries')
- else
.table-holder
%table.table#sherlock-queries
%thead
%tr
%th= t('sherlock.time')
%th= t('sherlock.query')
%th
%tbody
- @transaction.sorted_queries.each do |query|
%tr
%td
= query.duration.round(2)
= t('sherlock.milliseconds')
%td
.code.js-syntax-highlight.sherlock-code
= highlight("#{query.id}.sql", query.formatted_query, language: 'sql')
%td
= link_to(t('sherlock.view'),
sherlock_transaction_query_path(@transaction, query),
class: 'gl-button btn btn-sm')

View file

@ -1,41 +0,0 @@
- page_title t('sherlock.title')
- header_title t('sherlock.title'), sherlock_transactions_path
.row-content-block
.float-right
= link_to(destroy_all_sherlock_transactions_path,
class: 'gl-button btn btn-danger',
method: :delete) do
= sprite_icon('remove')
= t('sherlock.delete_all_transactions')
.oneline= t('sherlock.introduction')
- if @transactions.empty?
.nothing-here-block= t('sherlock.no_transactions')
- else
.table-holder
%table.table
%thead
%tr
%th= t('sherlock.type')
%th= t('sherlock.path')
%th= t('sherlock.time')
%th= t('sherlock.queries')
%th= t('sherlock.finished_at')
%th
%tbody
- @transactions.each do |trans|
%tr
%td= trans.type
%td
%span{ title: trans.path }
= truncate(trans.path, length: 70)
%td
= trans.duration.round(2)
= t('sherlock.seconds')
%td= trans.queries.length
%td
= time_ago_with_tooltip trans.finished_at
%td
= link_to(sherlock_transaction_path(trans), class: 'gl-button btn btn-sm') do
= t('sherlock.view')

View file

@ -1,34 +0,0 @@
- page_title t('sherlock.title'), t('sherlock.transaction')
- header_title t('sherlock.title'), sherlock_transactions_path
%ul.nav-links.nav.nav-tabs
%li.active
%a{ href: "#tab-general", data: { toggle: "tab" } }
= t('sherlock.general')
%li
%a{ href: "#tab-queries", data: { toggle: "tab" } }
= t('sherlock.queries')
= gl_badge_tag @transaction.queries.length.to_s
%li
%a{ href: "#tab-file-samples", data: { toggle: "tab" } }
= t('sherlock.file_samples')
= gl_badge_tag @transaction.file_samples.length.to_s
.row-content-block
.float-right
= link_to(sherlock_transactions_path, class: 'gl-button btn') do
= sprite_icon('arrow-left', css_class: 'gl-mr-3')
= t('sherlock.all_transactions')
.oneline
= t('sherlock.transaction')
= @transaction.id
.tab-content
.tab-pane.active#tab-general
= render(partial: 'general')
.tab-pane#tab-queries
= render(partial: 'queries')
.tab-pane#tab-file-samples
= render(partial: 'file_samples')

View file

@ -1,8 +0,0 @@
---
name: scim_token_vue
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74743
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/347270
milestone: '14.6'
type: development
group: group::access
default_enabled: true

View file

@ -1,8 +0,0 @@
---
name: webhook_recursion_detection
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75821
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349845
milestone: '14.7'
type: development
group: group::integrations
default_enabled: false

View file

@ -1,7 +0,0 @@
# frozen_string_literal: true
if Gitlab::Sherlock.enabled?
Rails.application.configure do |config|
config.middleware.use(Gitlab::Sherlock::Middleware)
end
end

View file

@ -1,38 +0,0 @@
en:
sherlock:
title: Sherlock
delete_all_transactions: Delete All Transactions
introduction: >
Below is a list of all transactions recorded by Sherlock. Requests to
Sherlock's own routes are ignored.
no_transactions: No transactions to show
no_queries: No queries to show
no_file_samples: No file samples to show
all_transactions: All Transactions
transaction: Transaction
query: Query
file_sample: File Sample
type: Type
path: Path
time: Time
queries: Queries
finished_at: Finished at
ago: ago
view: View
seconds: seconds
milliseconds: ms
general: General
id: ID
time_inclusive: Time (inclusive)
backtrace: Backtrace
application_backtrace: Application Backtrace
full_backtrace: Full Backtrace
origin: Origin
line: line
line_capitalized: Line
copy_to_clipboard: Copy
query_plan: Query Plan
events: Events
percent: '%'
count: Count
query_time: Query Time

View file

@ -20,7 +20,6 @@ Rails.application.routes.draw do
get 'favicon.png', to: favicon_redirect
get 'favicon.ico', to: favicon_redirect
draw :sherlock
draw :development
use_doorkeeper do

View file

@ -1,14 +0,0 @@
# frozen_string_literal: true
if Gitlab::Sherlock.enabled?
namespace :sherlock do
resources :transactions, only: [:index, :show] do
resources :queries, only: [:show]
resources :file_samples, only: [:show]
collection do
delete :destroy_all
end
end
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddRunnerTokenExpirationIntervalSettingsToApplicationSettings < Gitlab::Database::Migration[1.0]
def change
[:runner_token_expiration_interval, :group_runner_token_expiration_interval, :project_runner_token_expiration_interval].each do |field|
add_column :application_settings, field, :integer
end
end
end

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
class AddRunnerTokenExpirationIntervalSettingsToNamespaceSettings < Gitlab::Database::Migration[1.0]
enable_lock_retries!
def change
[:runner_token_expiration_interval, :subgroup_runner_token_expiration_interval, :project_runner_token_expiration_interval].each do |field|
add_column :namespace_settings, field, :integer
end
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
class AddRunnerTokenExpirationIntervalSettingsToProjectSettings < Gitlab::Database::Migration[1.0]
enable_lock_retries!
def change
add_column :project_ci_cd_settings, :runner_token_expiration_interval, :integer
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
class RemoveSecurityScansBuildIdFk < Gitlab::Database::Migration[1.0]
disable_ddl_transaction!
CONSTRAINT_NAME = 'fk_rails_4ef1e6b4c6'
def up
with_lock_retries do
execute('LOCK ci_builds, security_scans IN ACCESS EXCLUSIVE MODE')
remove_foreign_key_if_exists(:security_scans, :ci_builds, name: CONSTRAINT_NAME)
end
end
def down
add_concurrent_foreign_key(:security_scans, :ci_builds, column: :build_id, on_delete: :cascade, name: CONSTRAINT_NAME)
end
end

View file

@ -0,0 +1 @@
34759cbf09171f6057b87af791f5e9f3045ac5e06558147436ba32e276f40a19

View file

@ -0,0 +1 @@
88935eee0781ee1faaf52e3bc89dc5c490c2095d6ccf83e4fd4186641507c173

View file

@ -0,0 +1 @@
aa73b04aa355111564cdc7adb036a2030a28fbb3b524c3b3dbb8248e27b845d7

View file

@ -0,0 +1 @@
4d5adffe1a3e835d6d23f6bd9634993c778fef23d134552d54b003af2a3ff4da

View file

@ -10483,6 +10483,9 @@ CREATE TABLE application_settings (
future_subscriptions jsonb DEFAULT '[]'::jsonb NOT NULL,
user_email_lookup_limit integer DEFAULT 60 NOT NULL,
packages_cleanup_package_file_worker_capacity smallint DEFAULT 2 NOT NULL,
runner_token_expiration_interval integer,
group_runner_token_expiration_interval integer,
project_runner_token_expiration_interval integer,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
@ -16505,6 +16508,9 @@ CREATE TABLE namespace_settings (
new_user_signups_cap integer,
setup_for_company boolean,
jobs_to_be_done smallint,
runner_token_expiration_interval integer,
subgroup_runner_token_expiration_interval integer,
project_runner_token_expiration_interval integer,
CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255))
);
@ -18169,7 +18175,8 @@ CREATE TABLE project_ci_cd_settings (
auto_rollback_enabled boolean DEFAULT false NOT NULL,
keep_latest_artifact boolean DEFAULT true NOT NULL,
restrict_user_defined_variables boolean DEFAULT false NOT NULL,
job_token_scope_enabled boolean DEFAULT false NOT NULL
job_token_scope_enabled boolean DEFAULT false NOT NULL,
runner_token_expiration_interval integer
);
CREATE SEQUENCE project_ci_cd_settings_id_seq
@ -30497,9 +30504,6 @@ ALTER TABLE ONLY geo_repository_renamed_events
ALTER TABLE ONLY aws_roles
ADD CONSTRAINT fk_rails_4ed56f4720 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY security_scans
ADD CONSTRAINT fk_rails_4ef1e6b4c6 FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
ALTER TABLE ONLY packages_debian_publications
ADD CONSTRAINT fk_rails_4fc8ebd03e FOREIGN KEY (distribution_id) REFERENCES packages_debian_project_distributions(id) ON DELETE CASCADE;

View file

@ -141,6 +141,8 @@ end
### Subscription Plans
> The `opensource` plan was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/346399) in GitLab 14.7.
Self-managed:
- `default`: Everyone.
@ -156,5 +158,6 @@ GitLab.com:
- `gold`: Namespaces and projects with an Ultimate subscription.
- `ultimate`: Namespaces and projects with an Ultimate subscription.
- `ultimate_trial`: Namespaces and projects with an Ultimate Trial subscription.
- `opensource`: Namespaces and projects that are member of GitLab Open Source program.
The `test` environment doesn't have any plans.

View file

@ -132,6 +132,7 @@ module API
Ability.allowed?(options[:current_user], :change_repository_storage, project)
}
expose :keep_latest_artifacts_available?, as: :keep_latest_artifact
expose :runner_token_expiration_interval
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_resource(project)

View file

@ -120,6 +120,7 @@ included_attributes:
- :name
ci_cd_settings:
- :group_runners_enabled
- :runner_token_expiration_interval
metrics_setting:
- :dashboard_timezone
- :external_dashboard_url

View file

@ -1,21 +0,0 @@
# frozen_string_literal: true
require 'securerandom'
module Gitlab
module Sherlock
@collection = Collection.new
class << self
attr_reader :collection
end
def self.enabled?
Rails.env.development? && !!ENV['ENABLE_SHERLOCK']
end
def self.enable_line_profiler?
RUBY_ENGINE == 'ruby'
end
end
end

View file

@ -1,51 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Sherlock
# A collection of transactions recorded by Sherlock.
#
# Method calls for this class are synchronized using a mutex to allow
# sharing of a single Collection instance between threads (e.g. when using
# Puma as a webserver).
class Collection
include Enumerable
def initialize
@transactions = []
@mutex = Mutex.new
end
def add(transaction)
synchronize { @transactions << transaction }
end
alias_method :<<, :add
def each(&block)
synchronize { @transactions.each(&block) }
end
def clear
synchronize { @transactions.clear }
end
def empty?
synchronize { @transactions.empty? }
end
def find_transaction(id)
find { |trans| trans.id == id }
end
def newest_first
sort { |a, b| b.finished_at <=> a.finished_at }
end
private
def synchronize(&block)
@mutex.synchronize(&block)
end
end
end
end

View file

@ -1,33 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Sherlock
class FileSample
attr_reader :id, :file, :line_samples, :events, :duration
# file - The full path to the file this sample belongs to.
# line_samples - An array of LineSample objects.
# duration - The total execution time in milliseconds.
# events - The total amount of events.
def initialize(file, line_samples, duration, events)
@id = SecureRandom.uuid
@file = file
@line_samples = line_samples
@duration = duration
@events = events
end
def relative_path
@relative_path ||= @file.gsub(%r{^#{Rails.root}/?}, '')
end
def to_param
@id
end
def source
@source ||= File.read(@file)
end
end
end
end

View file

@ -1,100 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Sherlock
# Class for profiling code on a per line basis.
#
# The LineProfiler class can be used to profile code on per line basis
# without littering your code with Ruby implementation specific profiling
# methods.
#
# This profiler only includes samples taking longer than a given threshold
# and those that occur in the actual application (e.g. files from Gems are
# ignored).
class LineProfiler
# The minimum amount of time that has to be spent in a file for it to be
# included in a list of samples.
MINIMUM_DURATION = 10.0
# Profiles the given block.
#
# Example:
#
# profiler = LineProfiler.new
#
# retval, samples = profiler.profile do
# "cats are amazing"
# end
#
# retval # => "cats are amazing"
# samples # => [#<Gitlab::Sherlock::FileSample ...>, ...]
#
# Returns an Array containing the block's return value and an Array of
# FileSample objects.
def profile(&block)
if mri?
profile_mri(&block)
else
raise NotImplementedError,
'Line profiling is not supported on this platform'
end
end
# Profiles the given block using rblineprof (MRI only).
def profile_mri
require 'rblineprof'
retval = nil
samples = lineprof(/^#{Rails.root}/) { retval = yield }
file_samples = aggregate_rblineprof(samples)
[retval, file_samples]
end
# Returns an Array of file samples based on the output of rblineprof.
#
# lineprof_stats - A Hash containing rblineprof statistics on a per file
# basis.
#
# Returns an Array of FileSample objects.
def aggregate_rblineprof(lineprof_stats)
samples = []
lineprof_stats.each do |(file, stats)|
source_lines = File.read(file).each_line.to_a
line_samples = []
total_duration = microsec_to_millisec(stats[0][0])
total_events = stats[0][2]
next if total_duration <= MINIMUM_DURATION
stats[1..].each_with_index do |data, index|
next unless source_lines[index]
duration = microsec_to_millisec(data[0])
events = data[2]
line_samples << LineSample.new(duration, events)
end
samples << FileSample
.new(file, line_samples, total_duration, total_events)
end
samples
end
private
def microsec_to_millisec(microsec)
microsec / 1000.0
end
def mri?
RUBY_ENGINE == 'ruby'
end
end
end
end

View file

@ -1,38 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Sherlock
class LineSample
attr_reader :duration, :events
# duration - The execution time in milliseconds.
# events - The amount of events.
def initialize(duration, events)
@duration = duration
@events = events
end
# Returns the sample duration percentage relative to the given duration.
#
# Example:
#
# sample.duration # => 150
# sample.percentage_of(1500) # => 10.0
#
# total_duration - The total duration to compare with.
#
# Returns a float
def percentage_of(total_duration)
(duration.to_f / total_duration) * 100.0
end
# Returns true if the current sample takes up the majority of the given
# duration.
#
# total_duration - The total duration to compare with.
def majority_of?(total_duration)
percentage_of(total_duration) >= 30
end
end
end
end

View file

@ -1,28 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Sherlock
class Location
attr_reader :path, :line
SHERLOCK_DIR = File.dirname(__FILE__)
# Creates a new Location from a `Thread::Backtrace::Location`.
def self.from_ruby_location(location)
new(location.path, location.lineno)
end
# path - The full path of the frame as a String.
# line - The line number of the frame as a Fixnum.
def initialize(path, line)
@path = path
@line = line
end
# Returns true if the current frame originated from the application.
def application?
@path.start_with?(Rails.root.to_s) && !path.start_with?(SHERLOCK_DIR)
end
end
end
end

View file

@ -1,43 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Sherlock
# Rack middleware used for tracking request metrics.
class Middleware
CONTENT_TYPES = %r{text/html|application/json}i.freeze
IGNORE_PATHS = %r{^/sherlock}.freeze
def initialize(app)
@app = app
end
# env - A Hash containing Rack environment details.
def call(env)
if instrument?(env)
call_with_instrumentation(env)
else
@app.call(env)
end
end
def call_with_instrumentation(env)
trans = transaction_from_env(env)
retval = trans.run { @app.call(env) }
Sherlock.collection.add(trans)
retval
end
def instrument?(env)
!!(env['HTTP_ACCEPT'] =~ CONTENT_TYPES &&
env['REQUEST_URI'] !~ IGNORE_PATHS)
end
def transaction_from_env(env)
Transaction.new(env['REQUEST_METHOD'], env['REQUEST_URI'])
end
end
end
end

View file

@ -1,112 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Sherlock
class Query
attr_reader :id, :query, :started_at, :finished_at, :backtrace
# SQL identifiers that should be prefixed with newlines.
PREFIX_NEWLINE = %r{
\s+(FROM
|(LEFT|RIGHT)?INNER\s+JOIN
|(LEFT|RIGHT)?OUTER\s+JOIN
|WHERE
|AND
|GROUP\s+BY
|ORDER\s+BY
|LIMIT
|OFFSET)\s+}ix.freeze # Vim indent breaks when this is on a newline :<
# Creates a new Query using a String and a separate Array of bindings.
#
# query - A String containing a SQL query, optionally with numeric
# placeholders (`$1`, `$2`, etc).
#
# bindings - An Array of ActiveRecord columns and their values.
# started_at - The start time of the query as a Time-like object.
# finished_at - The completion time of the query as a Time-like object.
#
# Returns a new Query object.
def self.new_with_bindings(query, bindings, started_at, finished_at)
bindings.each_with_index do |(_, value), index|
quoted_value = ActiveRecord::Base.connection.quote(value)
query = query.gsub("$#{index + 1}", quoted_value)
end
new(query, started_at, finished_at)
end
# query - The SQL query as a String (without placeholders).
# started_at - The start time of the query as a Time-like object.
# finished_at - The completion time of the query as a Time-like object.
def initialize(query, started_at, finished_at)
@id = SecureRandom.uuid
@query = query
@started_at = started_at
@finished_at = finished_at
@backtrace = caller_locations.map do |loc|
Location.from_ruby_location(loc)
end
unless @query.end_with?(';')
@query = "#{@query};"
end
end
# Returns the query duration in milliseconds.
def duration
@duration ||= (@finished_at - @started_at) * 1000.0
end
def to_param
@id
end
# Returns a human readable version of the query.
def formatted_query
@formatted_query ||= format_sql(@query)
end
# Returns the last application frame of the backtrace.
def last_application_frame
@last_application_frame ||= @backtrace.find(&:application?)
end
# Returns an Array of application frames (excluding Gems and the likes).
def application_backtrace
@application_backtrace ||= @backtrace.select(&:application?)
end
# Returns the query plan as a String.
def explain
unless @explain
ActiveRecord::Base.connection.transaction do
@explain = raw_explain(@query).values.flatten.join("\n")
# Roll back any queries that mutate data so we don't mess up
# anything when running explain on an INSERT, UPDATE, DELETE, etc.
raise ActiveRecord::Rollback
end
end
@explain
end
private
def raw_explain(query)
explain = "EXPLAIN ANALYZE #{query};"
ActiveRecord::Base.connection.execute(explain)
end
def format_sql(query)
query.each_line
.map { |line| line.strip }
.join("\n")
.gsub(PREFIX_NEWLINE) { "\n#{Regexp.last_match(1)} " }
end
end
end
end

View file

@ -1,140 +0,0 @@
# frozen_string_literal: true
module Gitlab
module Sherlock
class Transaction
attr_reader :id, :type, :path, :queries, :file_samples, :started_at,
:finished_at, :view_counts
# type - The type of transaction (e.g. "GET", "POST", etc)
# path - The path of the transaction (e.g. the HTTP request path)
def initialize(type, path)
@id = SecureRandom.uuid
@type = type
@path = path
@queries = []
@file_samples = []
@started_at = nil
@finished_at = nil
@thread = Thread.current
@view_counts = Hash.new(0)
end
# Runs the transaction and returns the block's return value.
def run
@started_at = Time.now
retval = with_subscriptions do
profile_lines { yield }
end
@finished_at = Time.now
retval
end
# Returns the duration in seconds.
def duration
@duration ||= started_at && finished_at ? finished_at - started_at : 0
end
# Returns the total query duration in seconds.
def query_duration
@query_duration ||= @queries.map { |q| q.duration }.inject(:+) / 1000.0
end
def to_param
@id
end
# Returns the queries sorted in descending order by their durations.
def sorted_queries
@queries.sort { |a, b| b.duration <=> a.duration }
end
# Returns the file samples sorted in descending order by their durations.
def sorted_file_samples
@file_samples.sort { |a, b| b.duration <=> a.duration }
end
# Finds a query by the given ID.
#
# id - The query ID as a String.
#
# Returns a Query object if one could be found, nil otherwise.
def find_query(id)
@queries.find { |query| query.id == id }
end
# Finds a file sample by the given ID.
#
# id - The query ID as a String.
#
# Returns a FileSample object if one could be found, nil otherwise.
def find_file_sample(id)
@file_samples.find { |sample| sample.id == id }
end
def profile_lines
retval = nil
if Sherlock.enable_line_profiler?
retval, @file_samples = LineProfiler.new.profile { yield }
else
retval = yield
end
retval
end
def subscribe_to_active_record
ActiveSupport::Notifications.subscribe('sql.active_record') do |_, start, finish, _, data|
next unless same_thread?
unless data.fetch(:cached, data[:name] == 'CACHE')
track_query(data[:sql].strip, data[:binds], start, finish)
end
end
end
def subscribe_to_action_view
regex = /render_(template|partial)\.action_view/
ActiveSupport::Notifications.subscribe(regex) do |_, start, finish, _, data|
next unless same_thread?
track_view(data[:identifier])
end
end
private
def track_query(query, bindings, start, finish)
@queries << Query.new_with_bindings(query, bindings, start, finish)
end
def track_view(path)
@view_counts[path] += 1
end
def with_subscriptions
ar_subscriber = subscribe_to_active_record
av_subscriber = subscribe_to_action_view
retval = yield
ActiveSupport::Notifications.unsubscribe(ar_subscriber)
ActiveSupport::Notifications.unsubscribe(av_subscriber)
retval
end
# In case somebody uses a multi-threaded server locally (e.g. Puma) we
# _only_ want to track notifications that originate from the transaction
# thread.
def same_thread?
Thread.current == @thread
end
end
end
end

View file

@ -37,8 +37,6 @@ module Gitlab
# Adds the webhook ID to a cache (see `#cache_key_for_hook` for
# details of the cache).
def register!(hook)
return if disabled?(hook)
cache_key = cache_key_for_hook(hook)
::Gitlab::Redis::SharedState.with do |redis|
@ -53,8 +51,6 @@ module Gitlab
# number of IDs in the cache exceeds the limit (see
# `#cache_key_for_hook` for details of the cache).
def block?(hook)
return false if disabled?(hook)
# If a request UUID has not been set then we know the request was not
# made by a webhook, and no recursion is possible.
return false unless UUID.instance.request_uuid
@ -80,10 +76,6 @@ module Gitlab
private
def disabled?(hook)
Feature.disabled?(:webhook_recursion_detection, hook.parent)
end
# Returns a cache key scoped to a UUID.
#
# The particular UUID will be either:

View file

@ -8,7 +8,7 @@ module Sidebars
override :configure_menu_items
def configure_menu_items
return unless can?(context.current_user, :read_issue, context.project)
return false unless show_issues_menu_items?
add_item(list_menu_item)
add_item(boards_menu_item)
@ -70,6 +70,10 @@ module Sidebars
private
def show_issues_menu_items?
can?(context.current_user, :read_issue, context.project)
end
def list_menu_item
::Sidebars::MenuItem.new(
title: _('List'),

View file

@ -4712,9 +4712,6 @@ msgstr ""
msgid "Are you sure you want to remove this list?"
msgstr ""
msgid "Are you sure you want to reset the SCIM token? SCIM provisioning will stop working until the new token is updated."
msgstr ""
msgid "Are you sure you want to reset the health check token?"
msgstr ""
@ -17057,18 +17054,12 @@ msgstr ""
msgid "GroupSAML|SAML group link was successfully removed."
msgstr ""
msgid "GroupSAML|SCIM API endpoint URL"
msgstr ""
msgid "GroupSAML|SCIM Token"
msgstr ""
msgid "GroupSAML|SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
msgstr ""
msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to "
msgstr ""
msgid "GroupSAML|The SCIM token is now hidden. To see the value of the token again, you need to %{linkStart}reset it%{linkEnd}."
msgstr ""
@ -17093,9 +17084,6 @@ msgstr ""
msgid "GroupSAML|With prohibit outer forks flag enabled group members will be able to fork project only inside your group."
msgstr ""
msgid "GroupSAML|Your SCIM token"
msgstr ""
msgid "GroupSAML|as %{access_level}"
msgstr ""
@ -31828,6 +31816,9 @@ msgstr ""
msgid "SecurityPolicies|Policy type"
msgstr ""
msgid "SecurityReports|%{count}+ projects"
msgstr ""
msgid "SecurityReports|%{firstProject} and %{secondProject}"
msgstr ""
@ -32868,9 +32859,6 @@ msgstr ""
msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes"
msgstr ""
msgid "Sherlock Transactions"
msgstr ""
msgid "Shimo|Go to Shimo Workspace"
msgstr ""
@ -41415,9 +41403,6 @@ msgstr ""
msgid "Your new %{type}"
msgstr ""
msgid "Your new SCIM token"
msgstr ""
msgid "Your new access token has been created."
msgstr ""
@ -42726,7 +42711,7 @@ msgstr ""
msgid "mrWidget|Merge blocked: this merge request must be approved."
msgstr ""
msgid "mrWidget|Merge blocked: you can only merge once above items are resolved."
msgid "mrWidget|Merge blocked: you can only merge after the above items are resolved."
msgstr ""
msgid "mrWidget|Merge failed."
@ -43156,9 +43141,6 @@ msgstr ""
msgid "required"
msgstr ""
msgid "reset it."
msgstr ""
msgid "satisfied"
msgstr ""

View file

@ -58,7 +58,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "2.2.0",
"@gitlab/tributejs": "1.0.0",
"@gitlab/ui": "32.56.0",
"@gitlab/ui": "32.67.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "6.1.4-1",
"@rails/ujs": "6.1.4-1",

View file

@ -1,7 +1,11 @@
# frozen_string_literal: true
module QA
RSpec.describe 'Manage', :requires_admin do
RSpec.describe 'Manage', :requires_admin, quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/350598',
type: :needs_update,
only: { subdomain: :staging }
} do
describe 'Add project member' do
before do
Runtime::Feature.enable(:invite_members_group_modal)

View file

@ -49,6 +49,8 @@ FactoryBot.define do
forward_deployment_enabled { nil }
restrict_user_defined_variables { nil }
ci_job_token_scope_enabled { nil }
runner_token_expiration_interval { nil }
runner_token_expiration_interval_human_readable { nil }
end
after(:build) do |project, evaluator|
@ -92,6 +94,8 @@ FactoryBot.define do
project.keep_latest_artifact = evaluator.keep_latest_artifact unless evaluator.keep_latest_artifact.nil?
project.restrict_user_defined_variables = evaluator.restrict_user_defined_variables unless evaluator.restrict_user_defined_variables.nil?
project.ci_job_token_scope_enabled = evaluator.ci_job_token_scope_enabled unless evaluator.ci_job_token_scope_enabled.nil?
project.runner_token_expiration_interval = evaluator.runner_token_expiration_interval unless evaluator.runner_token_expiration_interval.nil?
project.runner_token_expiration_interval_human_readable = evaluator.runner_token_expiration_interval_human_readable unless evaluator.runner_token_expiration_interval_human_readable.nil?
if evaluator.import_status
import_state = project.import_state || project.build_import_state

View file

@ -42,6 +42,8 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = `
</span>
</a>
</li>
<!---->
<li
class="breadcrumb-item gl-breadcrumb-item"
>
@ -57,6 +59,8 @@ exports[`Registry Breadcrumb when is not rootRoute renders 1`] = `
<!---->
</a>
</li>
<!---->
</ol>
</nav>
`;
@ -85,6 +89,8 @@ exports[`Registry Breadcrumb when is rootRoute renders 1`] = `
<!---->
</a>
</li>
<!---->
</ol>
</nav>
`;

View file

@ -110,6 +110,19 @@ describe('DropdownContentsLabelsView', () => {
});
});
it('first item is highlighted when search is not empty', async () => {
createComponent({
queryHandler: jest.fn().mockResolvedValue(workspaceLabelsQueryResponse),
searchKey: 'Label',
});
await makeObserverAppear();
await waitForPromises();
await nextTick();
expect(findLabelsList().exists()).toBe(true);
expect(findFirstLabel().attributes('active')).toBe('true');
});
it('when search returns 0 results', async () => {
createComponent({
queryHandler: jest.fn().mockResolvedValue({

View file

@ -20,7 +20,6 @@ RSpec.describe Nav::TopNavHelper do
let(:current_group) { nil }
let(:with_current_settings_admin_mode) { false }
let(:with_header_link_admin_mode) { false }
let(:with_sherlock_enabled) { false }
let(:with_projects) { false }
let(:with_groups) { false }
let(:with_milestones) { false }
@ -34,7 +33,6 @@ RSpec.describe Nav::TopNavHelper do
before do
allow(Gitlab::CurrentSettings).to receive(:admin_mode) { with_current_settings_admin_mode }
allow(helper).to receive(:header_link?).with(:admin_mode) { with_header_link_admin_mode }
allow(Gitlab::Sherlock).to receive(:enabled?) { with_sherlock_enabled }
# Defaulting all `dashboard_nav_link?` calls to false ensures the EE-specific behavior
# is not enabled in this CE spec
@ -434,27 +432,6 @@ RSpec.describe Nav::TopNavHelper do
expect(subject[:shortcuts]).to eq([expected_shortcuts])
end
end
context 'when sherlock is enabled' do
let(:with_sherlock_enabled) { true }
before do
# Note: We have to mock the sherlock route because the route is conditional on
# sherlock being enabled, but it parsed at Rails load time and can't be overridden
# in a spec.
allow(helper).to receive(:sherlock_transactions_path) { '/fake_sherlock_path' }
end
it 'has sherlock as last :secondary item' do
expected_sherlock_item = ::Gitlab::Nav::TopNavMenuItem.build(
id: 'sherlock',
title: 'Sherlock Transactions',
icon: 'admin',
href: '/fake_sherlock_path'
)
expect(subject[:secondary].last).to eq(expected_sherlock_item)
end
end
end
context 'when current_user is admin' do

View file

@ -692,6 +692,7 @@ Badge:
- type
ProjectCiCdSetting:
- group_runners_enabled
- runner_token_expiration_interval
ProjectSetting:
- allow_merge_on_skipped_pipeline
- has_confluence

View file

@ -1,84 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sherlock::Collection do
let(:collection) { described_class.new }
let(:transaction) do
Gitlab::Sherlock::Transaction.new('POST', '/cat_pictures')
end
describe '#add' do
it 'adds a new transaction' do
collection.add(transaction)
expect(collection).not_to be_empty
end
it 'is aliased as <<' do
collection << transaction
expect(collection).not_to be_empty
end
end
describe '#each' do
it 'iterates over every transaction' do
collection.add(transaction)
expect { |b| collection.each(&b) }.to yield_with_args(transaction)
end
end
describe '#clear' do
it 'removes all transactions' do
collection.add(transaction)
collection.clear
expect(collection).to be_empty
end
end
describe '#empty?' do
it 'returns true for an empty collection' do
expect(collection).to be_empty
end
it 'returns false for a collection with a transaction' do
collection.add(transaction)
expect(collection).not_to be_empty
end
end
describe '#find_transaction' do
it 'returns the transaction for the given ID' do
collection.add(transaction)
expect(collection.find_transaction(transaction.id)).to eq(transaction)
end
it 'returns nil when no transaction could be found' do
collection.add(transaction)
expect(collection.find_transaction('cats')).to be_nil
end
end
describe '#newest_first' do
it 'returns transactions sorted from new to old' do
trans1 = Gitlab::Sherlock::Transaction.new('POST', '/cat_pictures')
trans2 = Gitlab::Sherlock::Transaction.new('POST', '/more_cat_pictures')
allow(trans1).to receive(:finished_at).and_return(Time.utc(2015, 1, 1))
allow(trans2).to receive(:finished_at).and_return(Time.utc(2015, 1, 2))
collection.add(trans1)
collection.add(trans2)
expect(collection.newest_first).to eq([trans2, trans1])
end
end
end

View file

@ -1,56 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sherlock::FileSample do
let(:sample) { described_class.new(__FILE__, [], 150.4, 2) }
describe '#id' do
it 'returns the ID' do
expect(sample.id).to be_an_instance_of(String)
end
end
describe '#file' do
it 'returns the file path' do
expect(sample.file).to eq(__FILE__)
end
end
describe '#line_samples' do
it 'returns the line samples' do
expect(sample.line_samples).to eq([])
end
end
describe '#events' do
it 'returns the total number of events' do
expect(sample.events).to eq(2)
end
end
describe '#duration' do
it 'returns the total execution time' do
expect(sample.duration).to eq(150.4)
end
end
describe '#relative_path' do
it 'returns the relative path' do
expect(sample.relative_path)
.to eq('spec/lib/gitlab/sherlock/file_sample_spec.rb')
end
end
describe '#to_param' do
it 'returns the sample ID' do
expect(sample.to_param).to eq(sample.id)
end
end
describe '#source' do
it 'returns the contents of the file' do
expect(sample.source).to eq(File.read(__FILE__))
end
end
end

View file

@ -1,75 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sherlock::LineProfiler do
let(:profiler) { described_class.new }
describe '#profile' do
it 'runs the profiler when using MRI' do
allow(profiler).to receive(:mri?).and_return(true)
allow(profiler).to receive(:profile_mri)
profiler.profile { 'cats' }
end
it 'raises NotImplementedError when profiling an unsupported platform' do
allow(profiler).to receive(:mri?).and_return(false)
expect { profiler.profile { 'cats' } }.to raise_error(NotImplementedError)
end
end
describe '#profile_mri' do
it 'returns an Array containing the return value and profiling samples' do
allow(profiler).to receive(:lineprof)
.and_yield
.and_return({ __FILE__ => [[0, 0, 0, 0]] })
retval, samples = profiler.profile_mri { 42 }
expect(retval).to eq(42)
expect(samples).to eq([])
end
end
describe '#aggregate_rblineprof' do
let(:raw_samples) do
{ __FILE__ => [[30000, 30000, 5, 0], [15000, 15000, 4, 0]] }
end
it 'returns an Array of FileSample objects' do
samples = profiler.aggregate_rblineprof(raw_samples)
expect(samples).to be_an_instance_of(Array)
expect(samples[0]).to be_an_instance_of(Gitlab::Sherlock::FileSample)
end
describe 'the first FileSample object' do
let(:file_sample) do
profiler.aggregate_rblineprof(raw_samples)[0]
end
it 'uses the correct file path' do
expect(file_sample.file).to eq(__FILE__)
end
it 'contains a list of line samples' do
line_sample = file_sample.line_samples[0]
expect(line_sample).to be_an_instance_of(Gitlab::Sherlock::LineSample)
expect(line_sample.duration).to eq(15.0)
expect(line_sample.events).to eq(4)
end
it 'contains the total file execution time' do
expect(file_sample.duration).to eq(30.0)
end
it 'contains the total amount of file events' do
expect(file_sample.events).to eq(5)
end
end
end
end

View file

@ -1,35 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sherlock::LineSample do
let(:sample) { described_class.new(150.0, 4) }
describe '#duration' do
it 'returns the duration' do
expect(sample.duration).to eq(150.0)
end
end
describe '#events' do
it 'returns the amount of events' do
expect(sample.events).to eq(4)
end
end
describe '#percentage_of' do
it 'returns the percentage of 1500.0' do
expect(sample.percentage_of(1500.0)).to be_within(0.1).of(10.0)
end
end
describe '#majority_of' do
it 'returns true if the sample takes up the majority of the given duration' do
expect(sample.majority_of?(500.0)).to eq(true)
end
it "returns false if the sample doesn't take up the majority of the given duration" do
expect(sample.majority_of?(1500.0)).to eq(false)
end
end
end

View file

@ -1,42 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sherlock::Location do
let(:location) { described_class.new(__FILE__, 1) }
describe 'from_ruby_location' do
it 'creates a Location from a Thread::Backtrace::Location' do
input = caller_locations[0]
output = described_class.from_ruby_location(input)
expect(output).to be_an_instance_of(described_class)
expect(output.path).to eq(input.path)
expect(output.line).to eq(input.lineno)
end
end
describe '#path' do
it 'returns the file path' do
expect(location.path).to eq(__FILE__)
end
end
describe '#line' do
it 'returns the line number' do
expect(location.line).to eq(1)
end
end
describe '#application?' do
it 'returns true for an application frame' do
expect(location.application?).to eq(true)
end
it 'returns false for a non application frame' do
loc = described_class.new('/tmp/cats.rb', 1)
expect(loc.application?).to eq(false)
end
end
end

View file

@ -1,81 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sherlock::Middleware do
let(:app) { double(:app) }
let(:middleware) { described_class.new(app) }
describe '#call' do
describe 'when instrumentation is enabled' do
it 'instruments a request' do
allow(middleware).to receive(:instrument?).and_return(true)
allow(middleware).to receive(:call_with_instrumentation)
middleware.call({})
end
end
describe 'when instrumentation is disabled' do
it "doesn't instrument a request" do
allow(middleware).to receive(:instrument).and_return(false)
allow(app).to receive(:call)
middleware.call({})
end
end
end
describe '#call_with_instrumentation' do
it 'instruments a request' do
trans = double(:transaction)
retval = 'cats are amazing'
env = {}
allow(app).to receive(:call).with(env).and_return(retval)
allow(middleware).to receive(:transaction_from_env).and_return(trans)
allow(trans).to receive(:run).and_yield.and_return(retval)
allow(Gitlab::Sherlock.collection).to receive(:add).with(trans)
middleware.call_with_instrumentation(env)
end
end
describe '#instrument?' do
it 'returns false for a text/css request' do
env = { 'HTTP_ACCEPT' => 'text/css', 'REQUEST_URI' => '/' }
expect(middleware.instrument?(env)).to eq(false)
end
it 'returns false for a request to a Sherlock route' do
env = {
'HTTP_ACCEPT' => 'text/html',
'REQUEST_URI' => '/sherlock/transactions'
}
expect(middleware.instrument?(env)).to eq(false)
end
it 'returns true for a request that should be instrumented' do
env = {
'HTTP_ACCEPT' => 'text/html',
'REQUEST_URI' => '/cats'
}
expect(middleware.instrument?(env)).to eq(true)
end
end
describe '#transaction_from_env' do
it 'returns a Transaction' do
env = {
'HTTP_ACCEPT' => 'text/html',
'REQUEST_URI' => '/cats'
}
expect(middleware.transaction_from_env(env))
.to be_an_instance_of(Gitlab::Sherlock::Transaction)
end
end
end

View file

@ -1,115 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sherlock::Query do
let(:started_at) { Time.utc(2015, 1, 1) }
let(:finished_at) { started_at + 5 }
let(:query) do
described_class.new('SELECT COUNT(*) FROM users', started_at, finished_at)
end
describe 'new_with_bindings' do
it 'returns a Query' do
sql = 'SELECT COUNT(*) FROM users WHERE id = $1'
bindings = [[double(:column), 10]]
query = described_class
.new_with_bindings(sql, bindings, started_at, finished_at)
expect(query.query).to eq('SELECT COUNT(*) FROM users WHERE id = 10;')
end
end
describe '#id' do
it 'returns a String' do
expect(query.id).to be_an_instance_of(String)
end
end
describe '#query' do
it 'returns the query with a trailing semi-colon' do
expect(query.query).to eq('SELECT COUNT(*) FROM users;')
end
end
describe '#started_at' do
it 'returns the start time' do
expect(query.started_at).to eq(started_at)
end
end
describe '#finished_at' do
it 'returns the completion time' do
expect(query.finished_at).to eq(finished_at)
end
end
describe '#backtrace' do
it 'returns the backtrace' do
expect(query.backtrace).to be_an_instance_of(Array)
end
end
describe '#duration' do
it 'returns the duration in milliseconds' do
expect(query.duration).to be_within(0.1).of(5000.0)
end
end
describe '#to_param' do
it 'returns the query ID' do
expect(query.to_param).to eq(query.id)
end
end
describe '#formatted_query' do
it 'returns a formatted version of the query' do
expect(query.formatted_query).to eq(<<-EOF.strip)
SELECT COUNT(*)
FROM users;
EOF
end
end
describe '#last_application_frame' do
it 'returns the last application frame' do
frame = query.last_application_frame
expect(frame).to be_an_instance_of(Gitlab::Sherlock::Location)
expect(frame.path).to eq(__FILE__)
end
end
describe '#application_backtrace' do
it 'returns an Array of application frames' do
frames = query.application_backtrace
expect(frames).to be_an_instance_of(Array)
expect(frames).not_to be_empty
frames.each do |frame|
expect(frame.path).to start_with(Rails.root.to_s)
end
end
end
describe '#explain' do
it 'returns the query plan as a String' do
lines = [
['Aggregate (cost=123 rows=1)'],
[' -> Index Only Scan using index_cats_are_amazing']
]
result = double(:result, values: lines)
allow(query).to receive(:raw_explain).and_return(result)
expect(query.explain).to eq(<<-EOF.strip)
Aggregate (cost=123 rows=1)
-> Index Only Scan using index_cats_are_amazing
EOF
end
end
end

View file

@ -1,238 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sherlock::Transaction do
let(:transaction) { described_class.new('POST', '/cat_pictures') }
describe '#id' do
it 'returns the transaction ID' do
expect(transaction.id).to be_an_instance_of(String)
end
end
describe '#type' do
it 'returns the type' do
expect(transaction.type).to eq('POST')
end
end
describe '#path' do
it 'returns the path' do
expect(transaction.path).to eq('/cat_pictures')
end
end
describe '#queries' do
it 'returns an Array of queries' do
expect(transaction.queries).to be_an_instance_of(Array)
end
end
describe '#file_samples' do
it 'returns an Array of file samples' do
expect(transaction.file_samples).to be_an_instance_of(Array)
end
end
describe '#started_at' do
it 'returns the start time' do
allow(transaction).to receive(:profile_lines).and_yield
transaction.run { 'cats are amazing' }
expect(transaction.started_at).to be_an_instance_of(Time)
end
end
describe '#finished_at' do
it 'returns the completion time' do
allow(transaction).to receive(:profile_lines).and_yield
transaction.run { 'cats are amazing' }
expect(transaction.finished_at).to be_an_instance_of(Time)
end
end
describe '#view_counts' do
it 'returns a Hash' do
expect(transaction.view_counts).to be_an_instance_of(Hash)
end
it 'sets the default value of a key to 0' do
expect(transaction.view_counts['cats.rb']).to be_zero
end
end
describe '#run' do
it 'runs the transaction' do
allow(transaction).to receive(:profile_lines).and_yield
retval = transaction.run { 'cats are amazing' }
expect(retval).to eq('cats are amazing')
end
end
describe '#duration' do
it 'returns the duration in seconds' do
start_time = Time.now
allow(transaction).to receive(:started_at).and_return(start_time)
allow(transaction).to receive(:finished_at).and_return(start_time + 5)
expect(transaction.duration).to be_within(0.1).of(5.0)
end
end
describe '#query_duration' do
it 'returns the total query duration in seconds' do
time = Time.now
query1 = Gitlab::Sherlock::Query.new('SELECT 1', time, time + 5)
query2 = Gitlab::Sherlock::Query.new('SELECT 2', time, time + 2)
transaction.queries << query1
transaction.queries << query2
expect(transaction.query_duration).to be_within(0.1).of(7.0)
end
end
describe '#to_param' do
it 'returns the transaction ID' do
expect(transaction.to_param).to eq(transaction.id)
end
end
describe '#sorted_queries' do
it 'returns the queries in descending order' do
start_time = Time.now
query1 = Gitlab::Sherlock::Query.new('SELECT 1', start_time, start_time)
query2 = Gitlab::Sherlock::Query
.new('SELECT 2', start_time, start_time + 5)
transaction.queries << query1
transaction.queries << query2
expect(transaction.sorted_queries).to eq([query2, query1])
end
end
describe '#sorted_file_samples' do
it 'returns the file samples in descending order' do
sample1 = Gitlab::Sherlock::FileSample.new(__FILE__, [], 10.0, 1)
sample2 = Gitlab::Sherlock::FileSample.new(__FILE__, [], 15.0, 1)
transaction.file_samples << sample1
transaction.file_samples << sample2
expect(transaction.sorted_file_samples).to eq([sample2, sample1])
end
end
describe '#find_query' do
it 'returns a Query when found' do
query = Gitlab::Sherlock::Query.new('SELECT 1', Time.now, Time.now)
transaction.queries << query
expect(transaction.find_query(query.id)).to eq(query)
end
it 'returns nil when no query could be found' do
expect(transaction.find_query('cats')).to be_nil
end
end
describe '#find_file_sample' do
it 'returns a FileSample when found' do
sample = Gitlab::Sherlock::FileSample.new(__FILE__, [], 10.0, 1)
transaction.file_samples << sample
expect(transaction.find_file_sample(sample.id)).to eq(sample)
end
it 'returns nil when no file sample could be found' do
expect(transaction.find_file_sample('cats')).to be_nil
end
end
describe '#profile_lines' do
describe 'when line profiling is enabled' do
it 'yields the block using the line profiler' do
allow(Gitlab::Sherlock).to receive(:enable_line_profiler?)
.and_return(true)
allow_next_instance_of(Gitlab::Sherlock::LineProfiler) do |instance|
allow(instance).to receive(:profile).and_return('cats are amazing', [])
end
retval = transaction.profile_lines { 'cats are amazing' }
expect(retval).to eq('cats are amazing')
end
end
describe 'when line profiling is disabled' do
it 'yields the block' do
allow(Gitlab::Sherlock).to receive(:enable_line_profiler?)
.and_return(false)
retval = transaction.profile_lines { 'cats are amazing' }
expect(retval).to eq('cats are amazing')
end
end
end
describe '#subscribe_to_active_record' do
let(:subscription) { transaction.subscribe_to_active_record }
let(:time) { Time.now }
let(:query_data) { { sql: 'SELECT 1', binds: [] } }
after do
ActiveSupport::Notifications.unsubscribe(subscription)
end
it 'tracks executed queries' do
expect(transaction).to receive(:track_query)
.with('SELECT 1', [], time, time)
subscription.publish('test', time, time, nil, query_data)
end
it 'only tracks queries triggered from the transaction thread' do
expect(transaction).not_to receive(:track_query)
Thread.new { subscription.publish('test', time, time, nil, query_data) }
.join
end
end
describe '#subscribe_to_action_view' do
let(:subscription) { transaction.subscribe_to_action_view }
let(:time) { Time.now }
let(:view_data) { { identifier: 'foo.rb' } }
after do
ActiveSupport::Notifications.unsubscribe(subscription)
end
it 'tracks rendered views' do
expect(transaction).to receive(:track_view).with('foo.rb')
subscription.publish('test', time, time, nil, view_data)
end
it 'only tracks views rendered from the transaction thread' do
expect(transaction).not_to receive(:track_view)
Thread.new { subscription.publish('test', time, time, nil, view_data) }
.join
end
end
end

View file

@ -128,16 +128,6 @@ RSpec.describe Gitlab::WebHooks::RecursionDetection, :clean_gitlab_redis_shared_
end
end
end
it 'does not store anything if the feature flag is disabled' do
stub_feature_flags(webhook_recursion_detection: false)
described_class.register!(web_hook)
::Gitlab::Redis::SharedState.with do |redis|
expect(redis.exists(cache_key(web_hook))).to eq(false)
end
end
end
describe 'block?' do
@ -167,12 +157,6 @@ RSpec.describe Gitlab::WebHooks::RecursionDetection, :clean_gitlab_redis_shared_
is_expected.to eq(true)
end
it 'returns false if the feature flag is disabled' do
stub_feature_flags(webhook_recursion_detection: false)
is_expected.to eq(false)
end
context 'when the request UUID changes again' do
before do
uuid_class.instance.request_uuid = SecureRandom.uuid
@ -199,12 +183,6 @@ RSpec.describe Gitlab::WebHooks::RecursionDetection, :clean_gitlab_redis_shared_
is_expected.to eq(true)
end
it 'returns false if the feature flag is disabled' do
stub_feature_flags(webhook_recursion_detection: false)
is_expected.to eq(false)
end
context 'when the request UUID changes again' do
before do
uuid_class.instance.request_uuid = SecureRandom.uuid

View file

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Group do
include ReloadHelpers
include StubGitlabCalls
let!(:group) { create(:group) }
@ -2930,4 +2931,222 @@ RSpec.describe Group do
end
end
end
describe '#enforced_runner_token_expiration_interval and #effective_runner_token_expiration_interval' do
shared_examples 'no enforced expiration interval' do
it { expect(subject.enforced_runner_token_expiration_interval).to be_nil }
end
shared_examples 'enforced expiration interval' do |enforced_interval:|
it { expect(subject.enforced_runner_token_expiration_interval).to eq(enforced_interval) }
end
shared_examples 'no effective expiration interval' do
it { expect(subject.effective_runner_token_expiration_interval).to be_nil }
end
shared_examples 'effective expiration interval' do |effective_interval:|
it { expect(subject.effective_runner_token_expiration_interval).to eq(effective_interval) }
end
context 'when there is no interval in group settings' do
let_it_be(:group) { create(:group) }
subject { group }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
context 'when there is a group interval' do
let(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 3.days.to_i) }
subject { create(:group, namespace_settings: group_settings) }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'effective expiration interval', effective_interval: 3.days
end
# runner_token_expiration_interval should not affect the expiration interval, only
# group_runner_token_expiration_interval should.
context 'when there is a site-wide enforced shared interval' do
before do
stub_application_setting(runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:group) { create(:group) }
subject { group }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
context 'when there is a site-wide enforced group interval' do
before do
stub_application_setting(group_runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:group) { create(:group) }
subject { group }
it_behaves_like 'enforced expiration interval', enforced_interval: 5.days
it_behaves_like 'effective expiration interval', effective_interval: 5.days
end
# project_runner_token_expiration_interval should not affect the expiration interval, only
# group_runner_token_expiration_interval should.
context 'when there is a site-wide enforced project interval' do
before do
stub_application_setting(project_runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:group) { create(:group) }
subject { group }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
# runner_token_expiration_interval should not affect the expiration interval, only
# subgroup_runner_token_expiration_interval should.
context 'when there is a grandparent group enforced group interval' do
let_it_be(:grandparent_group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:grandparent_group) { create(:group, namespace_settings: grandparent_group_settings) }
let_it_be(:parent_group) { create(:group, parent: grandparent_group) }
let_it_be(:subgroup) { create(:group, parent: parent_group) }
subject { subgroup }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
context 'when there is a grandparent group enforced subgroup interval' do
let_it_be(:grandparent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:grandparent_group) { create(:group, namespace_settings: grandparent_group_settings) }
let_it_be(:parent_group) { create(:group, parent: grandparent_group) }
let_it_be(:subgroup) { create(:group, parent: parent_group) }
subject { subgroup }
it_behaves_like 'enforced expiration interval', enforced_interval: 4.days
it_behaves_like 'effective expiration interval', effective_interval: 4.days
end
# project_runner_token_expiration_interval should not affect the expiration interval, only
# subgroup_runner_token_expiration_interval should.
context 'when there is a grandparent group enforced project interval' do
let_it_be(:grandparent_group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:grandparent_group) { create(:group, namespace_settings: grandparent_group_settings) }
let_it_be(:parent_group) { create(:group, parent: grandparent_group) }
let_it_be(:subgroup) { create(:group, parent: parent_group) }
subject { subgroup }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
context 'when there is a parent group enforced interval overridden by group interval' do
let_it_be(:parent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 5.days.to_i) }
let_it_be(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
let_it_be(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:subgroup_with_settings) { create(:group, parent: parent_group, namespace_settings: group_settings) }
subject { subgroup_with_settings }
it_behaves_like 'enforced expiration interval', enforced_interval: 5.days
it_behaves_like 'effective expiration interval', effective_interval: 4.days
it 'has human-readable expiration intervals' do
expect(subject.enforced_runner_token_expiration_interval_human_readable).to eq('5d')
expect(subject.effective_runner_token_expiration_interval_human_readable).to eq('4d')
end
end
context 'when site-wide enforced interval overrides group interval' do
before do
stub_application_setting(group_runner_token_expiration_interval: 3.days.to_i)
end
let_it_be(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:group_with_settings) { create(:group, namespace_settings: group_settings) }
subject { group_with_settings }
it_behaves_like 'enforced expiration interval', enforced_interval: 3.days
it_behaves_like 'effective expiration interval', effective_interval: 3.days
end
context 'when group interval overrides site-wide enforced interval' do
before do
stub_application_setting(group_runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:group_with_settings) { create(:group, namespace_settings: group_settings) }
subject { group_with_settings }
it_behaves_like 'enforced expiration interval', enforced_interval: 5.days
it_behaves_like 'effective expiration interval', effective_interval: 4.days
end
context 'when site-wide enforced interval overrides parent group enforced interval' do
before do
stub_application_setting(group_runner_token_expiration_interval: 3.days.to_i)
end
let_it_be(:parent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
let_it_be(:subgroup) { create(:group, parent: parent_group) }
subject { subgroup }
it_behaves_like 'enforced expiration interval', enforced_interval: 3.days
it_behaves_like 'effective expiration interval', effective_interval: 3.days
end
context 'when parent group enforced interval overrides site-wide enforced interval' do
before do
stub_application_setting(group_runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:parent_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
let_it_be(:subgroup) { create(:group, parent: parent_group) }
subject { subgroup }
it_behaves_like 'enforced expiration interval', enforced_interval: 4.days
it_behaves_like 'effective expiration interval', effective_interval: 4.days
end
# Unrelated groups should not affect the expiration interval.
context 'when there is an enforced group interval in an unrelated group' do
let_it_be(:unrelated_group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:unrelated_group) { create(:group, namespace_settings: unrelated_group_settings) }
let_it_be(:group) { create(:group) }
subject { group }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
# Subgroups should not affect the parent group expiration interval.
context 'when there is an enforced group interval in a subgroup' do
let_it_be(:subgroup_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:subgroup) { create(:group, parent: group, namespace_settings: subgroup_settings) }
let_it_be(:group) { create(:group) }
subject { group }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
end
end

View file

@ -7,6 +7,7 @@ RSpec.describe Project, factory_default: :keep do
include GitHelpers
include ExternalAuthorizationServiceHelpers
include ReloadHelpers
include StubGitlabCalls
using RSpec::Parameterized::TableSyntax
let_it_be(:namespace) { create_default(:namespace).freeze }
@ -7455,6 +7456,258 @@ RSpec.describe Project, factory_default: :keep do
end
end
describe '#enforced_runner_token_expiration_interval and #effective_runner_token_expiration_interval' do
shared_examples 'no enforced expiration interval' do
it { expect(subject.enforced_runner_token_expiration_interval).to be_nil }
end
shared_examples 'enforced expiration interval' do |enforced_interval:|
it { expect(subject.enforced_runner_token_expiration_interval).to eq(enforced_interval) }
end
shared_examples 'no effective expiration interval' do
it { expect(subject.effective_runner_token_expiration_interval).to be_nil }
end
shared_examples 'effective expiration interval' do |effective_interval:|
it { expect(subject.effective_runner_token_expiration_interval).to eq(effective_interval) }
end
context 'when there is no interval' do
let_it_be(:project) { create(:project) }
subject { project }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
context 'when there is a project interval' do
let_it_be(:project) { create(:project, runner_token_expiration_interval: 3.days.to_i) }
subject { project }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'effective expiration interval', effective_interval: 3.days
end
# runner_token_expiration_interval should not affect the expiration interval, only
# project_runner_token_expiration_interval should.
context 'when there is a site-wide enforced shared interval' do
before do
stub_application_setting(runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:project) { create(:project) }
subject { project }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
# group_runner_token_expiration_interval should not affect the expiration interval, only
# project_runner_token_expiration_interval should.
context 'when there is a site-wide enforced group interval' do
before do
stub_application_setting(group_runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:project) { create(:project) }
subject { project }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
context 'when there is a site-wide enforced project interval' do
before do
stub_application_setting(project_runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:project) { create(:project) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 5.days
it_behaves_like 'effective expiration interval', effective_interval: 5.days
end
# runner_token_expiration_interval should not affect the expiration interval, only
# project_runner_token_expiration_interval should.
context 'when there is a group-enforced group interval' do
let_it_be(:group_settings) { create(:namespace_settings, runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:group) { create(:group, namespace_settings: group_settings) }
let_it_be(:project) { create(:project, group: group) }
subject { project }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
# subgroup_runner_token_expiration_interval should not affect the expiration interval, only
# project_runner_token_expiration_interval should.
context 'when there is a group-enforced subgroup interval' do
let_it_be(:group_settings) { create(:namespace_settings, subgroup_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:group) { create(:group, namespace_settings: group_settings) }
let_it_be(:project) { create(:project, group: group) }
subject { project }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
context 'when there is an owner group-enforced project interval' do
let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:group) { create(:group, namespace_settings: group_settings) }
let_it_be(:project) { create(:project, group: group) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 4.days
it_behaves_like 'effective expiration interval', effective_interval: 4.days
end
context 'when there is a grandparent group-enforced interval' do
let_it_be(:grandparent_group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 3.days.to_i) }
let_it_be(:grandparent_group) { create(:group, namespace_settings: grandparent_group_settings) }
let_it_be(:parent_group_settings) { create(:namespace_settings) }
let_it_be(:parent_group) { create(:group, parent: grandparent_group, namespace_settings: parent_group_settings) }
let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:group) { create(:group, parent: parent_group, namespace_settings: group_settings) }
let_it_be(:project) { create(:project, group: group) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 3.days
it_behaves_like 'effective expiration interval', effective_interval: 3.days
end
context 'when there is a parent group-enforced interval overridden by group-enforced interval' do
let_it_be(:parent_group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 5.days.to_i) }
let_it_be(:parent_group) { create(:group, namespace_settings: parent_group_settings) }
let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:group) { create(:group, parent: parent_group, namespace_settings: group_settings) }
let_it_be(:project) { create(:project, group: group) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 4.days
it_behaves_like 'effective expiration interval', effective_interval: 4.days
end
context 'when site-wide enforced interval overrides project interval' do
before do
stub_application_setting(project_runner_token_expiration_interval: 3.days.to_i)
end
let_it_be(:project) { create(:project, runner_token_expiration_interval: 4.days.to_i) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 3.days
it_behaves_like 'effective expiration interval', effective_interval: 3.days
end
context 'when project interval overrides site-wide enforced interval' do
before do
stub_application_setting(project_runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:project) { create(:project, runner_token_expiration_interval: 4.days.to_i) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 5.days
it_behaves_like 'effective expiration interval', effective_interval: 4.days
it 'has human-readable expiration intervals' do
expect(subject.enforced_runner_token_expiration_interval_human_readable).to eq('5d')
expect(subject.effective_runner_token_expiration_interval_human_readable).to eq('4d')
end
end
context 'when site-wide enforced interval overrides group-enforced interval' do
before do
stub_application_setting(project_runner_token_expiration_interval: 3.days.to_i)
end
let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:group) { create(:group, namespace_settings: group_settings) }
let_it_be(:project) { create(:project, group: group) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 3.days
it_behaves_like 'effective expiration interval', effective_interval: 3.days
end
context 'when group-enforced interval overrides site-wide enforced interval' do
before do
stub_application_setting(project_runner_token_expiration_interval: 5.days.to_i)
end
let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:group) { create(:group, namespace_settings: group_settings) }
let_it_be(:project) { create(:project, group: group) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 4.days
it_behaves_like 'effective expiration interval', effective_interval: 4.days
end
context 'when group-enforced interval overrides project interval' do
let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 3.days.to_i) }
let_it_be(:group) { create(:group, namespace_settings: group_settings) }
let_it_be(:project) { create(:project, group: group, runner_token_expiration_interval: 4.days.to_i) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 3.days
it_behaves_like 'effective expiration interval', effective_interval: 3.days
end
context 'when project interval overrides group-enforced interval' do
let_it_be(:group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 5.days.to_i) }
let_it_be(:group) { create(:group, namespace_settings: group_settings) }
let_it_be(:project) { create(:project, group: group, runner_token_expiration_interval: 4.days.to_i) }
subject { project }
it_behaves_like 'enforced expiration interval', enforced_interval: 5.days
it_behaves_like 'effective expiration interval', effective_interval: 4.days
end
# Unrelated groups should not affect the expiration interval.
context 'when there is an enforced project interval in an unrelated group' do
let_it_be(:unrelated_group_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:unrelated_group) { create(:group, namespace_settings: unrelated_group_settings) }
let_it_be(:project) { create(:project) }
subject { project }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
# Subgroups should not affect the parent group expiration interval.
context 'when there is an enforced project interval in a subgroup' do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup_settings) { create(:namespace_settings, project_runner_token_expiration_interval: 4.days.to_i) }
let_it_be(:subgroup) { create(:group, parent: group, namespace_settings: subgroup_settings) }
let_it_be(:project) { create(:project, group: group) }
subject { project }
it_behaves_like 'no enforced expiration interval'
it_behaves_like 'no effective expiration interval'
end
end
it_behaves_like 'it has loose foreign keys' do
let(:factory_name) { :project }
end

View file

@ -45,20 +45,6 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red
.once
end
shared_examples 'when the feature flag is disabled' do
it 'executes and logs no errors' do
stub_feature_flags(webhook_recursion_detection: false)
stub_requests
expect(Gitlab::AuthLogger).not_to receive(:error)
trigger_web_hooks
expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).once
expect(WebMock).to have_requested(:post, stubbed_hostname(system_hook.url)).once
end
end
context 'when one of the webhooks is recursive' do
before do
# Recreate the necessary state for the previous request to be
@ -87,8 +73,6 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red
expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url)).once
expect(WebMock).to have_requested(:post, stubbed_hostname(system_hook.url)).once
end
include_examples 'when the feature flag is disabled'
end
context 'when the count limit has been reached' do
@ -134,8 +118,6 @@ RSpec.describe 'Recursive webhook detection', :sidekiq_inline, :clean_gitlab_red
expect(WebMock).to have_requested(:post, stubbed_hostname(system_hook.url)).once
end
end
include_examples 'when the feature flag is disabled'
end
context 'when the recursive webhook detection header is absent' do

View file

@ -39,7 +39,7 @@ RSpec.describe IncidentManagement::Incidents::CreateService do
let(:issue) { new_issue }
include_examples 'has incident label'
include_examples 'does not have incident label'
end
context 'with default severity' do
@ -71,8 +71,8 @@ RSpec.describe IncidentManagement::Incidents::CreateService do
end
context 'when incident label does not exists' do
it 'creates incident label' do
expect { create_incident }.to change { project.labels.where(title: label_title).count }.by(1)
it 'does not create incident label' do
expect { create_incident }.to not_change { project.labels.where(title: label_title).count }
end
end

View file

@ -114,11 +114,11 @@ RSpec.describe Issues::CreateService do
end
it_behaves_like 'incident issue'
it_behaves_like 'has incident label'
it_behaves_like 'does not have incident label'
it 'does create an incident label' do
it 'does not create an incident label' do
expect { subject }
.to change { Label.where(incident_label_attributes).count }.by(1)
.to not_change { Label.where(incident_label_attributes).count }
end
it 'calls IncidentManagement::Incidents::CreateEscalationStatusService' do

View file

@ -191,11 +191,6 @@ RSpec.describe Issues::UpdateService, :mailer do
end
end
it 'adds a `incident` label if one does not exist' do
expect { update_issue(issue_type: 'incident') }.to change(issue.labels, :count).by(1)
expect(issue.labels.pluck(:title)).to eq(['incident'])
end
it 'creates system note about issue type' do
update_issue(issue_type: 'incident')
@ -222,18 +217,6 @@ RSpec.describe Issues::UpdateService, :mailer do
expect(issue.labels).to eq([label_1])
end
end
context 'filtering the incident label' do
let(:params) { { add_label_ids: [] } }
before do
update_issue(issue_type: 'incident')
end
it 'creates and add a incident label id to add_label_ids' do
expect(issue.label_ids).to contain_exactly(label_1.id)
end
end
end
context 'from incident to issue' do
@ -248,10 +231,8 @@ RSpec.describe Issues::UpdateService, :mailer do
context 'for an incident with multiple labels' do
let(:issue) { create(:incident, project: project, labels: [label_1, label_2]) }
it 'removes an `incident` label if one exists on the incident' do
expect { update_issue(issue_type: 'issue') }.to change(issue, :label_ids)
.from(containing_exactly(label_1.id, label_2.id))
.to([label_2.id])
it 'does not remove an `incident` label if one exists on the incident' do
expect { update_issue(issue_type: 'issue') }.to not_change(issue, :label_ids)
end
end
@ -259,10 +240,8 @@ RSpec.describe Issues::UpdateService, :mailer do
let(:issue) { create(:incident, project: project, labels: [label_1, label_2]) }
let(:params) { { label_ids: [label_1.id, label_2.id], remove_label_ids: [] } }
it 'adds an incident label id to remove_label_ids for it to be removed' do
expect { update_issue(issue_type: 'issue') }.to change(issue, :label_ids)
.from(containing_exactly(label_1.id, label_2.id))
.to([label_2.id])
it 'does not add an incident label id to remove_label_ids for it to be removed' do
expect { update_issue(issue_type: 'issue') }.to not_change(issue, :label_ids)
end
end
end

View file

@ -53,7 +53,6 @@ module SimpleCovEnv
track_files '{app,config/initializers,config/initializers_before_autoloader,db/post_migrate,haml_lint,lib,rubocop,tooling}/**/*.rb'
add_filter '/vendor/ruby/'
add_filter '/app/controllers/sherlock/' # Profiling tool used only in development
add_filter '/bin/'
add_filter 'db/fixtures/development/' # Matches EE files as well

View file

@ -47,6 +47,26 @@ RSpec.shared_examples 'labels sidebar widget' do
end
end
it 'adds first label by pressing enter when search' do
within(labels_widget) do
page.within('[data-testid="value-wrapper"]') do
expect(page).not_to have_content(development.name)
end
fill_in 'Search', with: 'Devel'
sleep 1
expect(page.all(:css, '[data-testid="dropdown-content"] .gl-new-dropdown-item').length).to eq(1)
find_field('Search').native.send_keys(:enter)
click_button 'Close'
wait_for_requests
page.within('[data-testid="value-wrapper"]') do
expect(page).to have_content(development.name)
end
end
end
it 'escapes XSS when viewing issuable labels' do
page.within(labels_widget) do
expect(page).to have_content '<script>alert("xss");</script>'

View file

@ -55,8 +55,13 @@ RSpec.shared_examples 'cleanup by a loose foreign key' do
end
def find_model
primary_key = model.class.primary_key.to_sym
model.class.find_by(primary_key => model.public_send(primary_key))
query = model.class
# handle composite primary keys
connection = model.class.connection
connection.primary_keys(model.class.table_name).each do |primary_key|
query = query.where(primary_key => model.public_send(primary_key))
end
query.first
end
it 'cleans up (delete or nullify) the model' do

View file

@ -17,16 +17,6 @@ RSpec.shared_examples 'incident issue' do
end
end
RSpec.shared_examples 'has incident label' do
let(:label_properties) { attributes_for(:label, :incident) }
it 'has exactly one incident label' do
expect(issue.labels).to be_one do |label|
label.slice(*label_properties.keys).symbolize_keys == label_properties
end
end
end
# This shared_example requires the following variables:
# - issue (required)
#
@ -45,6 +35,12 @@ RSpec.shared_examples 'not an incident issue' do
expect(issue.work_item_type.base_type).not_to eq('incident')
end
it_behaves_like 'does not have incident label'
end
RSpec.shared_examples 'does not have incident label' do
let(:label_properties) { attributes_for(:label, :incident) }
it 'has not an incident label' do
expect(issue.labels).not_to include(have_attributes(label_properties))
end

View file

@ -924,10 +924,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8"
integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw==
"@gitlab/ui@32.56.0":
version "32.56.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.56.0.tgz#7952a8d106565bcf43a111ff0389fc5a629b085a"
integrity sha512-MqoVw5x9nmYJBOP1gIKI/4hULMWlzdJ9eaUgNMQ0eA9WwBTDroNwR+YGuILZDdKhAfYSiqqClmjBT0Ddt4j64g==
"@gitlab/ui@32.67.0":
version "32.67.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-32.67.0.tgz#8a6dc9a0aa5fe05855d13251deeb16f6bf07ebd8"
integrity sha512-7sHVM1aQB+tMxlUCiOq8G0094nWJBhvtwJEeiw+U5+htR5+s5lYuFGPO8UYntjBlNgVSfHuDb5vY5M67W5HvJA==
dependencies:
"@babel/standalone" "^7.0.0"
bootstrap-vue "2.20.1"