Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a87e8cad90
commit
e67f3f55d2
90 changed files with 711 additions and 1991 deletions
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)"
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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') ||
|
||||
|
|
|
@ -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
|
||||
|
|
22
app/models/concerns/runner_token_expiration_interval.rb
Normal file
22
app/models/concerns/runner_token_expiration_interval.rb
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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')
|
|
@ -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')
|
|
@ -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
|
|
@ -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')
|
|
@ -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')
|
|
@ -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')
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
1
db/schema_migrations/20220111002756
Normal file
1
db/schema_migrations/20220111002756
Normal file
|
@ -0,0 +1 @@
|
|||
34759cbf09171f6057b87af791f5e9f3045ac5e06558147436ba32e276f40a19
|
1
db/schema_migrations/20220118155846
Normal file
1
db/schema_migrations/20220118155846
Normal file
|
@ -0,0 +1 @@
|
|||
88935eee0781ee1faaf52e3bc89dc5c490c2095d6ccf83e4fd4186641507c173
|
1
db/schema_migrations/20220118155847
Normal file
1
db/schema_migrations/20220118155847
Normal file
|
@ -0,0 +1 @@
|
|||
aa73b04aa355111564cdc7adb036a2030a28fbb3b524c3b3dbb8248e27b845d7
|
1
db/schema_migrations/20220118155848
Normal file
1
db/schema_migrations/20220118155848
Normal file
|
@ -0,0 +1 @@
|
|||
4d5adffe1a3e835d6d23f6bd9634993c778fef23d134552d54b003af2a3ff4da
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -120,6 +120,7 @@ included_attributes:
|
|||
- :name
|
||||
ci_cd_settings:
|
||||
- :group_runners_enabled
|
||||
- :runner_token_expiration_interval
|
||||
metrics_setting:
|
||||
- :dashboard_timezone
|
||||
- :external_dashboard_url
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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:
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
`;
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -692,6 +692,7 @@ Badge:
|
|||
- type
|
||||
ProjectCiCdSetting:
|
||||
- group_runners_enabled
|
||||
- runner_token_expiration_interval
|
||||
ProjectSetting:
|
||||
- allow_merge_on_skipped_pipeline
|
||||
- has_confluence
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue