Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b8bf288b92
commit
1bb7fc2fd6
|
|
@ -67,7 +67,7 @@
|
|||
top: $calc-system-headers-height;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: $brand-charcoal;
|
||||
background-color: var(--gl-color-brand-charcoal);
|
||||
|
||||
@media (forced-colors: active) {
|
||||
border-bottom: 1px solid;
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: $brand-gray-04;
|
||||
background-color: var(--gl-color-brand-gray-04);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
}
|
||||
|
||||
&:active {
|
||||
background-color: $brand-gray-03;
|
||||
background-color: var(--gl-color-brand-gray-03);
|
||||
}
|
||||
|
||||
img {
|
||||
|
|
@ -132,19 +132,19 @@
|
|||
padding: 6px 8px;
|
||||
height: 32px;
|
||||
border-radius: $gl-border-radius-base;
|
||||
color: $gray-100;
|
||||
color: var(--gl-color-neutral-100);
|
||||
font-weight: $gl-font-weight-normal;
|
||||
font-size: $gl-font-size;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: $white;
|
||||
color: var(--gl-color-neutral-0);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: $brand-gray-04;
|
||||
background-color: var(--gl-color-brand-gray-04);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +154,7 @@
|
|||
}
|
||||
|
||||
&:active {
|
||||
background-color: $brand-gray-03;
|
||||
background-color: var(--gl-color-brand-gray-03);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class LabelNote < SyntheticNote
|
|||
resource ||= events.first.issuable
|
||||
|
||||
label_note = from_event(events.first, resource: resource, resource_parent: resource_parent)
|
||||
label_note.events = events.sort_by(&:id)
|
||||
label_note.events = events.sort_by { |e| e.label&.name.to_s }
|
||||
|
||||
label_note
|
||||
end
|
||||
|
|
@ -59,7 +59,7 @@ class LabelNote < SyntheticNote
|
|||
# added 3 deleted labels
|
||||
# added ~1 ~2 labels
|
||||
def labels_str(label_refs, prefix: '')
|
||||
existing_refs = label_refs.select { |ref| ref.present? }.sort
|
||||
existing_refs = label_refs.select(&:present?)
|
||||
refs_str = existing_refs.empty? ? nil : existing_refs.join(' ')
|
||||
|
||||
deleted = label_refs.count - existing_refs.count
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
#
|
||||
class Namespace
|
||||
class TraversalHierarchy
|
||||
include Transactions
|
||||
|
||||
LOCK_TIMEOUT = '500ms'
|
||||
|
||||
attr_accessor :root
|
||||
|
||||
def self.for_namespace(namespace)
|
||||
|
|
@ -26,6 +30,7 @@ class Namespace
|
|||
end
|
||||
|
||||
# Update all traversal_ids in the current namespace hierarchy.
|
||||
# rubocop:disable Database/RescueQueryCanceled -- Measuring specific query timeouts
|
||||
def sync_traversal_ids!
|
||||
# An issue in Rails since 2013 prevents this kind of join based update in
|
||||
# ActiveRecord. https://github.com/rails/rails/issues/13496
|
||||
|
|
@ -46,17 +51,20 @@ class Namespace
|
|||
%w[namespaces], url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/424279'
|
||||
) do
|
||||
Namespace.transaction do
|
||||
lock_options = 'FOR NO KEY UPDATE'
|
||||
lock_options += ' NOWAIT' if Feature.enabled?(:sync_traversal_ids_nowait, Feature.current_request)
|
||||
|
||||
@root.lock!(lock_options)
|
||||
if sync_traversal_ids_nowait?
|
||||
Gitlab::Database::Transaction::Settings.with('LOCK_TIMEOUT', LOCK_TIMEOUT) do
|
||||
@root.lock!('FOR NO KEY UPDATE')
|
||||
end
|
||||
else
|
||||
@root.lock!('FOR NO KEY UPDATE')
|
||||
end
|
||||
|
||||
Namespace.connection.exec_query(sql)
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::LockWaitTimeout => e
|
||||
if e.message.starts_with? 'PG::LockNotAvailable'
|
||||
db_nowait_counter.increment(source: 'Namespace#sync_traversal_ids!')
|
||||
rescue ActiveRecord::QueryCanceled => e
|
||||
if e.message.include?("canceling statement due to statement timeout")
|
||||
db_query_timeout_counter.increment(source: 'Namespace#sync_traversal_ids!')
|
||||
end
|
||||
|
||||
raise
|
||||
|
|
@ -71,6 +79,7 @@ class Namespace
|
|||
.joins("INNER JOIN (#{recursive_traversal_ids}) as cte ON namespaces.id = cte.id")
|
||||
.where('namespaces.traversal_ids::bigint[] <> cte.traversal_ids')
|
||||
end
|
||||
# rubocop:enable Database/RescueQueryCanceled
|
||||
|
||||
private
|
||||
|
||||
|
|
@ -105,12 +114,16 @@ class Namespace
|
|||
.find_by(parent_id: nil)
|
||||
end
|
||||
|
||||
def db_nowait_counter
|
||||
Gitlab::Metrics.counter(:db_nowait, 'Counts the times we triggered NOWAIT on a database lock operation')
|
||||
def db_query_timeout_counter
|
||||
Gitlab::Metrics.counter(:db_query_timeout, 'Counts the times the query timed out')
|
||||
end
|
||||
|
||||
def db_deadlock_counter
|
||||
Gitlab::Metrics.counter(:db_deadlock, 'Counts the times we have deadlocked in the database')
|
||||
end
|
||||
|
||||
def sync_traversal_ids_nowait?
|
||||
Feature.enabled?(:sync_traversal_ids_nowait, Feature.current_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
# ----- DELETE EVERYTHING ABOVE THIS LINE -----
|
||||
|
||||
- title: "`ciJobTokenScopeAddProject` GraphQL mutation is deprecated"
|
||||
# The milestones for the deprecation announcement, and the removal.
|
||||
removal_milestone: "18.0"
|
||||
announcement_milestone: "17.5"
|
||||
# Change breaking_change to false if needed.
|
||||
breaking_change: true
|
||||
# The stage and GitLab username of the person reporting the change,
|
||||
# and a link to the deprecation issue
|
||||
reporter: jocelynjane
|
||||
stage: Govern
|
||||
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/474175
|
||||
impact: low # Can be one of: [critical, high, medium, low]
|
||||
scope: project # Can be one or a combination of: [instance, group, project]
|
||||
resolution_role: Developer # Can be one of: [Admin, Owner, Maintainer, Developer]
|
||||
manual_task: true # Can be true or false. Use this to denote whether a resolution action must be performed manually (true), or if it can be automated by using the API or other automation (false).
|
||||
body: | # (required) Don't change this line.
|
||||
With the [upcoming default behavior change to the CI/CD job token](https://docs.gitlab.com/ee/update/deprecations.html#default-cicd-job-token-ci_job_token-scope-changed) in GitLab 18.0, we are also deprecating the associated `ciJobTokenScopeAddProject` GraphQL mutation as the associated feature will be no longer be available.
|
||||
|
||||
# ==============================
|
||||
# OPTIONAL END-OF-SUPPORT FIELDS
|
||||
# ==============================
|
||||
#
|
||||
# If an End of Support period applies:
|
||||
# 1) Share this announcement in the `#spt_managers` Support channel in Slack
|
||||
# 2) Mention `@gitlab-com/support` in this merge request.
|
||||
#
|
||||
# When support for this feature ends, in XX.YY milestone format.
|
||||
end_of_support_milestone:
|
||||
# Array of tiers the feature is currently available to,
|
||||
# like [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
tiers:
|
||||
# Links to documentation and thumbnail image
|
||||
documentation_url:
|
||||
image_url:
|
||||
# Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
|
||||
video_url:
|
||||
|
|
@ -396,7 +396,7 @@ Example response:
|
|||
"access_level": null,
|
||||
"user_id": null,
|
||||
"group_id": null,
|
||||
"deploy_key_id: 1,
|
||||
"deploy_key_id": 1,
|
||||
"access_level_description": "Deploy"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ To set the maximum job timeout:
|
|||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/4335) in GitLab Runner 16.4.
|
||||
|
||||
To control the amount of time `script` and `after_script` runs before it terminates, you can set specify a timeout.
|
||||
To control the amount of time `script` and `after_script` runs before it terminates, specify a timeout value in the `.gitlab-ci.yml` file.
|
||||
|
||||
For example, you can specify a timeout to terminate a long-running `script` early, so that artifacts and caches can still be uploaded
|
||||
before the job timeout is exceeded.
|
||||
|
|
|
|||
|
|
@ -909,6 +909,22 @@ The [`GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN`](https://docs.gitlab.com/ee/admi
|
|||
|
||||
<div class="deprecation breaking-change" data-milestone="18.0">
|
||||
|
||||
### `ciJobTokenScopeAddProject` GraphQL mutation is deprecated
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
||||
- Announced in GitLab <span class="milestone">17.5</span>
|
||||
- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
|
||||
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/474175).
|
||||
|
||||
</div>
|
||||
|
||||
With the [upcoming default behavior change to the CI/CD job token](https://docs.gitlab.com/ee/update/deprecations.html#default-cicd-job-token-ci_job_token-scope-changed) in GitLab 18.0, we are also deprecating the associated `ciJobTokenScopeAddProject` GraphQL mutation as the associated feature will be no longer be available.
|
||||
|
||||
</div>
|
||||
|
||||
<div class="deprecation breaking-change" data-milestone="18.0">
|
||||
|
||||
### `require_password_to_approve` field
|
||||
|
||||
<div class="deprecation-notes">
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ Audit event types belong to the following product categories.
|
|||
| [`audit_events_streaming_headers_update`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92068) | Triggered when a streaming header for audit events is updated. | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.3](https://gitlab.com/gitlab-org/gitlab/-/issues/366350) | Group |
|
||||
| [`compliance_framework_added`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/157893) | Triggered when a compliance framework is applied to a project | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.2](https://gitlab.com/gitlab-org/gitlab/-/issues/464160) | Project |
|
||||
| [`compliance_framework_deleted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65343) | Triggered when a compliance framework is removed from a project | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.1](https://gitlab.com/gitlab-org/gitlab/-/issues/329362) | Project |
|
||||
| [`compliance_framework_id_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94711) | Triggered when a compliance framework ID is updated | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/369310) | Project |
|
||||
| [`compliance_framework_id_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94711) | Triggered when a compliance framework is updated for a project | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/369310) | Project |
|
||||
| [`compliance_framework_removed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/157893) | Triggered when a compliance framework is removed from a project | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.2](https://gitlab.com/gitlab-org/gitlab/-/issues/464160) | Project |
|
||||
| [`create_compliance_framework`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74292) | Triggered on when a compliance framework is successfully created | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [14.6](https://gitlab.com/gitlab-org/gitlab/-/issues/340649) | Group |
|
||||
| [`create_status_check`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84624) | Triggered when an external status check is created | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/issues/355805) | Project |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Database
|
||||
module Transaction
|
||||
class Settings
|
||||
ALLOWED_CONFIGS = %w[
|
||||
LOCK_TIMEOUT
|
||||
].freeze
|
||||
|
||||
InvalidConfigError = Class.new(StandardError)
|
||||
|
||||
class << self
|
||||
def with(config_name, value, connection = ApplicationRecord.connection)
|
||||
old_value = get(config_name, connection)
|
||||
|
||||
set(config_name, value, connection)
|
||||
|
||||
yield
|
||||
|
||||
set(config_name, old_value, connection)
|
||||
end
|
||||
|
||||
def set(config_name, value, connection = ApplicationRecord.connection)
|
||||
check_allowed!(config_name)
|
||||
|
||||
quoted_value = connection.quote(value)
|
||||
query = "SET LOCAL #{config_name} = #{quoted_value}"
|
||||
|
||||
connection.exec_query(query)
|
||||
end
|
||||
|
||||
def get(config_name, connection = ApplicationRecord.connection)
|
||||
check_allowed!(config_name)
|
||||
|
||||
connection.select_all("SHOW #{config_name}").rows[0][0]
|
||||
end
|
||||
|
||||
def check_allowed!(config_name)
|
||||
return if ALLOWED_CONFIGS.include?(config_name)
|
||||
|
||||
raise InvalidConfigError, "Config #{config_name} is not allowed"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -73,6 +73,8 @@ module Gitlab
|
|||
LICENSES_LOAD = 'SELECT "licenses".* FROM "licenses" ORDER BY "licenses"."id"'
|
||||
SCHEMA_INTROSPECTION = %r{SELECT.*(FROM|JOIN) (pg_attribute|pg_class)}m
|
||||
SAVEPOINT = %r{(RELEASE )?SAVEPOINT}m
|
||||
SET = %r{^SET\s}m
|
||||
SHOW = %r{^SHOW\s}m
|
||||
|
||||
# queries can be safely ignored if they are amoritized in regular usage
|
||||
# (i.e. only requested occasionally and otherwise cached).
|
||||
|
|
@ -81,6 +83,8 @@ module Gitlab
|
|||
return true if sql&.include?(LICENSES_LOAD)
|
||||
return true if SCHEMA_INTROSPECTION.match?(sql)
|
||||
return true if SAVEPOINT.match?(sql)
|
||||
return true if SET.match?(sql)
|
||||
return true if SHOW.match?(sql)
|
||||
|
||||
false
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,111 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Database::Transaction::Settings, feature_category: :database do
|
||||
shared_context 'with a CI transaction' do
|
||||
before do
|
||||
skip_if_shared_database(:ci)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
Ci::ApplicationRecord.transaction do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get' do
|
||||
before do
|
||||
described_class.set('LOCK_TIMEOUT', '200ms')
|
||||
end
|
||||
|
||||
it 'gets the value of a configuration' do
|
||||
expect(described_class.get('LOCK_TIMEOUT')).to eq('200ms')
|
||||
end
|
||||
|
||||
it 'raises an error when config is not allowed' do
|
||||
expect { described_class.get('some_config') }.to raise_error(described_class::InvalidConfigError)
|
||||
end
|
||||
|
||||
context 'with connection parameter' do
|
||||
include_context 'with a CI transaction'
|
||||
|
||||
before do
|
||||
described_class.set('LOCK_TIMEOUT', '300ms', Ci::Tag.connection)
|
||||
end
|
||||
|
||||
it 'gets the value of a configuration' do
|
||||
expect(described_class.get('LOCK_TIMEOUT', Ci::Tag.connection)).to eq('300ms')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set' do
|
||||
it 'sets a configuration value' do
|
||||
described_class.set('LOCK_TIMEOUT', '300ms')
|
||||
|
||||
expect(described_class.get('LOCK_TIMEOUT')).to eq('300ms')
|
||||
end
|
||||
|
||||
it 'raises an error when config is not allowed' do
|
||||
expect { described_class.set('some_config', 10) }.to raise_error(described_class::InvalidConfigError)
|
||||
end
|
||||
|
||||
context 'with connection parameter' do
|
||||
include_context 'with a CI transaction'
|
||||
|
||||
it 'sets a configuration value' do
|
||||
described_class.set('LOCK_TIMEOUT', '300ms')
|
||||
described_class.set('LOCK_TIMEOUT', '400ms', Ci::Tag.connection)
|
||||
|
||||
expect(described_class.get('LOCK_TIMEOUT')).to eq('300ms')
|
||||
expect(described_class.get('LOCK_TIMEOUT', Ci::Tag.connection)).to eq('400ms')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#with' do
|
||||
it 'sets a configuration value' do
|
||||
described_class.with('LOCK_TIMEOUT', '400ms') do
|
||||
expect(described_class.get('LOCK_TIMEOUT')).to eq('400ms')
|
||||
end
|
||||
end
|
||||
|
||||
it 'restores original configuration value' do
|
||||
described_class.set('LOCK_TIMEOUT', '200ms')
|
||||
|
||||
described_class.with('LOCK_TIMEOUT', '500ms') do
|
||||
expect(described_class.get('LOCK_TIMEOUT')).to eq('500ms')
|
||||
end
|
||||
|
||||
expect(described_class.get('LOCK_TIMEOUT')).to eq('200ms')
|
||||
end
|
||||
|
||||
it 'raises an error when config is not allowed' do
|
||||
expect do
|
||||
described_class.with('some_config', 10) { 1 + 1 }
|
||||
end.to raise_error(described_class::InvalidConfigError)
|
||||
end
|
||||
|
||||
context 'with connection parameter' do
|
||||
include_context 'with a CI transaction'
|
||||
|
||||
it 'sets and restores a configuration value' do
|
||||
described_class.set('LOCK_TIMEOUT', '200ms', Ci::Tag.connection)
|
||||
|
||||
described_class.with('LOCK_TIMEOUT', '400ms', Ci::Tag.connection) do
|
||||
expect(described_class.get('LOCK_TIMEOUT', Ci::Tag.connection)).to eq('400ms')
|
||||
end
|
||||
|
||||
expect(described_class.get('LOCK_TIMEOUT', Ci::Tag.connection)).to eq('200ms')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#check_allowed!' do
|
||||
it 'raises an error when config is not allowed' do
|
||||
expect { described_class.check_allowed!('some_config') }.to raise_error(described_class::InvalidConfigError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -131,6 +131,8 @@ RSpec.describe Gitlab::QueryLimiting::Transaction, feature_category: :database d
|
|||
transaction.increment('SELECT x.foo, a.attname FROM some_table x JOIN pg_attribute a')
|
||||
transaction.increment('SAVEPOINT active_record_2')
|
||||
transaction.increment('RELEASE SAVEPOINT active_record_2')
|
||||
transaction.increment('SET LOCK_TIMEOUT = 500')
|
||||
transaction.increment('SHOW LOCK_TIMEOUT')
|
||||
transaction.increment(<<-SQL)
|
||||
SELECT a.attname, a.other_column
|
||||
FROM pg_attribute a
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model, feature_category: :g
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'locked row', nowait: true do
|
||||
it_behaves_like 'locked row', timeout: true do
|
||||
let(:recorded_queries) { ActiveRecord::QueryRecorder.new }
|
||||
let(:row) { root }
|
||||
|
||||
|
|
@ -78,19 +78,20 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model, feature_category: :g
|
|||
end
|
||||
|
||||
context 'when record is already locked' do
|
||||
let(:msg) { 'PG::QueryCanceled: ERROR: canceling statement due to statement timeout' }
|
||||
|
||||
before do
|
||||
msg = %(PG::LockNotAvailable: ERROR: could not obtain lock on row in relation "namespaces"\n)
|
||||
allow(root).to receive(:lock!).and_raise(ActiveRecord::LockWaitTimeout.new(msg))
|
||||
allow(root).to receive(:lock!).and_raise(ActiveRecord::QueryCanceled.new(msg))
|
||||
end
|
||||
|
||||
it { expect { subject }.to raise_error(ActiveRecord::LockWaitTimeout) }
|
||||
it { expect { subject }.to raise_error(ActiveRecord::QueryCanceled, msg) }
|
||||
|
||||
it 'increment db_nowait counter' do
|
||||
it 'increment db_query_timeout counter' do
|
||||
expect do
|
||||
subject
|
||||
rescue StandardError
|
||||
nil
|
||||
end.to change { db_nowait_total('Namespace#sync_traversal_ids!') }.by(1)
|
||||
end.to change { db_query_timeout_total('Namespace#sync_traversal_ids!') }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -99,7 +100,7 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model, feature_category: :g
|
|||
stub_feature_flags(sync_traversal_ids_nowait: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'locked row', nowait: false do
|
||||
it_behaves_like 'locked row', timeout: false do
|
||||
let(:recorded_queries) { ActiveRecord::QueryRecorder.new }
|
||||
let(:row) { root }
|
||||
|
||||
|
|
@ -126,9 +127,9 @@ RSpec.describe Namespace::TraversalHierarchy, type: :model, feature_category: :g
|
|||
end
|
||||
end
|
||||
|
||||
def db_nowait_total(source)
|
||||
def db_query_timeout_total(source)
|
||||
Gitlab::Metrics
|
||||
.counter(:db_nowait, 'Counts the times we triggered NOWAIT on a database lock operation')
|
||||
.counter(:db_query_timeout, 'Counts the times the query timed out')
|
||||
.get(source: source)
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -6,19 +6,13 @@ RSpec.shared_examples 'label note created from events' do
|
|||
resource_key = resource.class.name.underscore.to_s
|
||||
event_params[resource_key] = resource
|
||||
|
||||
if params[:label].nil?
|
||||
build(:resource_label_event, event_params.merge(params)).tap do |e|
|
||||
e.save!(validate: false)
|
||||
end
|
||||
else
|
||||
create(:resource_label_event, event_params.merge(params))
|
||||
end
|
||||
build(:resource_label_event, event_params.merge(params))
|
||||
end
|
||||
|
||||
def label_refs(events)
|
||||
labels = events.map(&:label).compact
|
||||
|
||||
labels.map { |l| l.to_reference }.sort.join(' ')
|
||||
labels.map { |l| l.to_reference }.join(' ')
|
||||
end
|
||||
|
||||
let(:time) { Time.now }
|
||||
|
|
@ -36,8 +30,8 @@ RSpec.shared_examples 'label note created from events' do
|
|||
expect(note.noteable).to eq event.issuable
|
||||
expect(note.note).to be_present
|
||||
expect(note.note_html).to be_present
|
||||
expect(note.created_at).to be_like_time create_event.created_at
|
||||
expect(note.updated_at).to be_like_time create_event.created_at
|
||||
expect(note.created_at).to eq create_event.created_at
|
||||
expect(note.updated_at).to eq create_event.created_at
|
||||
end
|
||||
|
||||
it 'updates markdown cache if reference is not set yet' do
|
||||
|
|
@ -74,15 +68,22 @@ RSpec.shared_examples 'label note created from events' do
|
|||
expect(note.note).to eq "added #{label_refs(events)} + 1 deleted label"
|
||||
end
|
||||
|
||||
it 'orders label events by id' do
|
||||
it 'orders label events by label name' do
|
||||
foo_label = label.dup.tap do |l|
|
||||
l.update_attribute(:title, 'foo')
|
||||
end
|
||||
bar_label = label2.dup.tap do |l|
|
||||
l.update_attribute(:title, 'bar')
|
||||
end
|
||||
|
||||
events = [
|
||||
create_event(created_at: time, label: label),
|
||||
create_event(created_at: time, label: label2)
|
||||
create_event(created_at: time, label: foo_label),
|
||||
create_event(created_at: time, label: bar_label)
|
||||
]
|
||||
|
||||
note = described_class.from_events(events.reverse)
|
||||
note = described_class.from_events(events)
|
||||
|
||||
expect(note.note).to eq "added #{label_refs(events)} labels"
|
||||
expect(note.note).to eq "added #{label_refs(events.reverse)} labels"
|
||||
end
|
||||
|
||||
it 'returns text note for removed labels' do
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
# Ensure a transaction also occurred.
|
||||
# Be careful! This form of spec is not foolproof, but better than nothing.
|
||||
|
||||
RSpec.shared_examples 'locked row' do |nowait: false|
|
||||
RSpec.shared_examples 'locked row' do |timeout: false|
|
||||
it "has locked row" do
|
||||
table_name = row.class.table_name
|
||||
ids_regex = /SELECT.*FROM.*#{table_name}.*"#{table_name}"."id" = #{row.id}.+FOR NO KEY UPDATE/m
|
||||
|
|
@ -12,11 +12,11 @@ RSpec.shared_examples 'locked row' do |nowait: false|
|
|||
expect(recorded_queries.log).to include a_string_matching 'SAVEPOINT'
|
||||
expect(recorded_queries.log).to include a_string_matching ids_regex
|
||||
|
||||
expect(recorded_queries.log).to include a_string_matching 'NOWAIT' if nowait
|
||||
expect(recorded_queries.log).to include a_string_matching 'LOCK_TIMEOUT' if timeout
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'locked rows' do |nowait: false|
|
||||
RSpec.shared_examples 'locked rows' do |timeout: false|
|
||||
it "has locked rows" do
|
||||
table_name = rows.first.class.table_name
|
||||
|
||||
|
|
@ -26,6 +26,6 @@ RSpec.shared_examples 'locked rows' do |nowait: false|
|
|||
expect(recorded_queries.log).to include a_string_matching 'SAVEPOINT'
|
||||
expect(recorded_queries.log).to include a_string_matching ids_regex
|
||||
|
||||
expect(recorded_queries.log).to include a_string_matching 'NOWAIT' if nowait
|
||||
expect(recorded_queries.log).to include a_string_matching 'LOCK_TIMEOUT' if timeout
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue