Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
23634aa773
commit
99c1dfd5e3
|
|
@ -65,7 +65,6 @@ docs-lint markdown:
|
|||
- .default-retry
|
||||
- .docs:rules:docs-lint
|
||||
- .docs-markdown-lint-image
|
||||
- .yarn-cache
|
||||
stage: lint
|
||||
needs: []
|
||||
script:
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ workhorse:test fips:
|
|||
extends: .workhorse:test
|
||||
image: registry.gitlab.com/gitlab-org/gitlab-omnibus-builder/ubuntu_20.04_fips:4.0.0
|
||||
variables:
|
||||
WORKHORSE_TEST_FIPS_ENABLED: 1
|
||||
FIPS_MODE: 1
|
||||
|
||||
workhorse:test race:
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -84,7 +84,7 @@ gem 'gssapi', group: :kerberos
|
|||
gem 'timfel-krb5-auth', '~> 0.8', group: :kerberos
|
||||
|
||||
# Spam and anti-bot protection
|
||||
gem 'recaptcha', '~> 4.11', require: 'recaptcha/rails'
|
||||
gem 'recaptcha', '~> 5.12', require: 'recaptcha/rails'
|
||||
gem 'akismet', '~> 3.0'
|
||||
gem 'invisible_captcha', '~> 2.0.0'
|
||||
|
||||
|
|
|
|||
|
|
@ -464,7 +464,7 @@
|
|||
{"name":"rchardet","version":"1.8.0","platform":"ruby","checksum":"693acd5253d5ade81a51940697955f6dd4bb2f0d245bda76a8e23deec70a52c7"},
|
||||
{"name":"rdoc","version":"6.3.2","platform":"ruby","checksum":"def4a720235c27d56c176ae73555e647eb04ea58a8bbaa927f8f9f79de7805a6"},
|
||||
{"name":"re2","version":"1.6.0","platform":"ruby","checksum":"2e37f27971f6a76223eac688c04f3e48aea374f34b302ec22d75b4635cd64bc1"},
|
||||
{"name":"recaptcha","version":"4.13.1","platform":"ruby","checksum":"dc6c2cb78afa87034358b7ba1c6f7175972b5709fdf7500e2550623e119e3788"},
|
||||
{"name":"recaptcha","version":"5.12.3","platform":"ruby","checksum":"37d1894add9e70a54d0c6c7f0ecbeedffbfa7d075acfbd4c509818dfdebdb7ee"},
|
||||
{"name":"recursive-open-struct","version":"1.1.3","platform":"ruby","checksum":"a3538a72552fcebcd0ada657bdff313641a4a5fbc482c08cfb9a65acb1c9de5a"},
|
||||
{"name":"redcarpet","version":"3.5.1","platform":"ruby","checksum":"717f64cb6ec11c8d9ec9b521ed26ca2eeda68b4fe1fc3388a641176dbd47732f"},
|
||||
{"name":"redis","version":"4.8.0","platform":"ruby","checksum":"2000cf5014669c9dc821704b6d322a35a9a33852a95208911d9175d63b448a44"},
|
||||
|
|
|
|||
|
|
@ -1174,7 +1174,7 @@ GEM
|
|||
rchardet (1.8.0)
|
||||
rdoc (6.3.2)
|
||||
re2 (1.6.0)
|
||||
recaptcha (4.13.1)
|
||||
recaptcha (5.12.3)
|
||||
json
|
||||
recursive-open-struct (1.1.3)
|
||||
redcarpet (3.5.1)
|
||||
|
|
@ -1790,7 +1790,7 @@ DEPENDENCIES
|
|||
rbtrace (~> 0.4)
|
||||
rdoc (~> 6.3.2)
|
||||
re2 (~> 1.6.0)
|
||||
recaptcha (~> 4.11)
|
||||
recaptcha (~> 5.12)
|
||||
redis (~> 4.8.0)
|
||||
redis-actionpack (~> 5.3.0)
|
||||
redis-namespace (~> 1.9.0)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default {
|
|||
:href="`#${panel.name}`"
|
||||
data-qa-selector="panel_link"
|
||||
:data-qa-panel-name="panel.name"
|
||||
class="new-namespace-panel gl-display-flex gl-flex-shrink-0 gl-flex-direction-column gl-lg-flex-direction-row gl-align-items-center gl-rounded-base gl-border-gray-100 gl-border-solid gl-border-1 gl-w-full gl-py-6 gl-px-8 gl-hover-text-decoration-none!"
|
||||
class="new-namespace-panel gl-display-flex gl-flex-shrink-0 gl-flex-direction-column gl-lg-flex-direction-row gl-align-items-center gl-rounded-base gl-border-gray-100 gl-border-solid gl-border-1 gl-w-full gl-py-6 gl-px-3 gl-hover-text-decoration-none!"
|
||||
@click="track('click_tab', { label: panel.name })"
|
||||
>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -135,6 +135,14 @@
|
|||
@include gl-font-weight-normal;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
@include gl-mr-3;
|
||||
@include gl-p-3;
|
||||
background-color: $t-gray-a-08;
|
||||
border: 1px solid $t-gray-a-08;
|
||||
border-radius: $border-radius-default;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin top-level-item {
|
||||
|
|
@ -462,4 +470,3 @@
|
|||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module GitlabRecaptcha
|
||||
extend ActiveSupport::Concern
|
||||
include Recaptcha::Verify
|
||||
include Recaptcha::Adapters::ControllerMethods
|
||||
include RecaptchaHelper
|
||||
|
||||
def load_recaptcha
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ module SpammableActions::CaptchaCheck::HtmlFormatActionsSupport
|
|||
# Convert spam/CAPTCHA values from form field params to headers, because all spam-related services
|
||||
# expect these values to be passed as headers.
|
||||
#
|
||||
# The 'g-recaptcha-response' field name comes from `Recaptcha::ClientHelper#recaptcha_tags` in the
|
||||
# The 'g-recaptcha-response' field name comes from `Recaptcha::Adapters::ViewMethods#recaptcha_tags` in the
|
||||
# recaptcha gem. This is a field which is automatically included by calling the
|
||||
# `#recaptcha_tags` method within a HAML template's form.
|
||||
def convert_html_spam_params_to_headers
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class GroupsController < Groups::ApplicationController
|
|||
include RecordUserLastActivity
|
||||
include SendFileUpload
|
||||
include FiltersEvents
|
||||
include Recaptcha::Verify
|
||||
include Recaptcha::Adapters::ControllerMethods
|
||||
extend ::Gitlab::Utils::Override
|
||||
|
||||
respond_to :html
|
||||
|
|
|
|||
|
|
@ -388,7 +388,7 @@ class ProjectsController < Projects::ApplicationController
|
|||
|
||||
def determine_layout
|
||||
if [:new, :create].include?(action_name.to_sym)
|
||||
'application'
|
||||
'dashboard'
|
||||
elsif [:edit, :update].include?(action_name.to_sym)
|
||||
'project_settings'
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RegistrationsController < Devise::RegistrationsController
|
||||
include Recaptcha::Verify
|
||||
include Recaptcha::Adapters::ControllerMethods
|
||||
include AcceptsPendingInvitations
|
||||
include RecaptchaHelper
|
||||
include InvisibleCaptchaOnSignup
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ class SessionsController < Devise::SessionsController
|
|||
include InternalRedirect
|
||||
include AuthenticatesWithTwoFactor
|
||||
include Devise::Controllers::Rememberable
|
||||
include Recaptcha::ClientHelper
|
||||
include Recaptcha::Verify
|
||||
include Recaptcha::Adapters::ViewMethods
|
||||
include Recaptcha::Adapters::ControllerMethods
|
||||
include RendersLdapServers
|
||||
include KnownSignIn
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module Types
|
|||
|
||||
field :notes, Types::Notes::NoteType.connection_type, null: false, description: "All notes on this noteable."
|
||||
field :discussions, Types::Notes::DiscussionType.connection_type, null: false, description: "All discussions on this noteable."
|
||||
field :commenters, Types::UserType.connection_type, null: false, description: "All commenters on this noteable."
|
||||
|
||||
def self.resolve_type(object, context)
|
||||
case object
|
||||
|
|
@ -24,6 +25,10 @@ module Types
|
|||
raise "Unknown GraphQL type for #{object}"
|
||||
end
|
||||
end
|
||||
|
||||
def commenters
|
||||
object.commenters(user: current_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -88,12 +88,12 @@ module CounterAttribute
|
|||
end
|
||||
|
||||
def increment_counter(attribute, increment)
|
||||
return if increment == 0
|
||||
return if increment.amount == 0
|
||||
|
||||
run_after_commit_or_now do
|
||||
new_value = counter(attribute).increment(increment)
|
||||
|
||||
log_increment_counter(attribute, increment, new_value)
|
||||
log_increment_counter(attribute, increment.amount, new_value)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ module CounterAttribute
|
|||
run_after_commit_or_now do
|
||||
new_value = counter(attribute).bulk_increment(increments)
|
||||
|
||||
log_increment_counter(attribute, increments.sum, new_value)
|
||||
log_increment_counter(attribute, increments.sum(&:amount), new_value)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -24,10 +24,10 @@ module HasUserType
|
|||
included do
|
||||
scope :humans, -> { where(user_type: :human) }
|
||||
scope :bots, -> { where(user_type: BOT_USER_TYPES) }
|
||||
scope :without_bots, -> { humans.or(where.not(user_type: BOT_USER_TYPES)) }
|
||||
scope :without_bots, -> { humans.or(where(user_type: USER_TYPES.keys - BOT_USER_TYPES)) }
|
||||
scope :non_internal, -> { humans.or(where(user_type: NON_INTERNAL_USER_TYPES)) }
|
||||
scope :without_ghosts, -> { humans.or(where.not(user_type: :ghost)) }
|
||||
scope :without_project_bot, -> { humans.or(where.not(user_type: :project_bot)) }
|
||||
scope :without_ghosts, -> { humans.or(where(user_type: USER_TYPES.keys - ['ghost'])) }
|
||||
scope :without_project_bot, -> { humans.or(where(user_type: USER_TYPES.keys - ['project_bot'])) }
|
||||
scope :human_or_service_user, -> { humans.or(where(user_type: :service_user)) }
|
||||
|
||||
enum user_type: USER_TYPES
|
||||
|
|
|
|||
|
|
@ -205,6 +205,14 @@ module Noteable
|
|||
model_name.singular
|
||||
end
|
||||
|
||||
def commenters(user: nil)
|
||||
eligable_notes = notes.user
|
||||
|
||||
eligable_notes = eligable_notes.not_internal unless user&.can?(:read_internal_note, self)
|
||||
|
||||
User.where(id: eligable_notes.select(:author_id).distinct)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Synthetic system notes don't have discussion IDs because these are generated dynamically
|
||||
|
|
|
|||
|
|
@ -78,9 +78,10 @@ module UpdateProjectStatistics
|
|||
return if delta == 0
|
||||
return if project.nil?
|
||||
|
||||
increment = Gitlab::Counters::Increment.new(amount: delta, ref: id)
|
||||
|
||||
run_after_commit do
|
||||
ProjectStatistics.increment_statistic(
|
||||
project, self.class.project_statistics_name, delta)
|
||||
ProjectStatistics.increment_statistic(project, self.class.project_statistics_name, increment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ class Note < ApplicationRecord
|
|||
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
|
||||
scope :system, -> { where(system: true) }
|
||||
scope :user, -> { where(system: false) }
|
||||
scope :not_internal, -> { where(internal: false) }
|
||||
scope :common, -> { where(noteable_type: ["", nil]) }
|
||||
scope :fresh, -> { order_created_asc.with_order_id_asc }
|
||||
scope :updated_after, ->(time) { where('updated_at > ?', time) }
|
||||
|
|
|
|||
|
|
@ -123,30 +123,31 @@ class ProjectStatistics < ApplicationRecord
|
|||
# through counter_attribute_after_commit
|
||||
#
|
||||
# For non-counter attributes, storage_size is updated depending on key => [columns] in INCREMENTABLE_COLUMNS
|
||||
def self.increment_statistic(project, key, amount)
|
||||
def self.increment_statistic(project, key, increment)
|
||||
return if project.pending_delete?
|
||||
|
||||
project.statistics.try do |project_statistics|
|
||||
project_statistics.increment_statistic(key, amount)
|
||||
project_statistics.increment_statistic(key, increment)
|
||||
end
|
||||
end
|
||||
|
||||
def self.bulk_increment_statistic(project, key, amounts)
|
||||
def self.bulk_increment_statistic(project, key, increments)
|
||||
unless Feature.enabled?(:project_statistics_bulk_increment, type: :development)
|
||||
return increment_statistic(project, key, amounts.sum)
|
||||
total_amount = Gitlab::Counters::Increment.new(amount: increments.sum(&:amount))
|
||||
return increment_statistic(project, key, total_amount)
|
||||
end
|
||||
|
||||
return if project.pending_delete?
|
||||
|
||||
project.statistics.try do |project_statistics|
|
||||
project_statistics.bulk_increment_statistic(key, amounts)
|
||||
project_statistics.bulk_increment_statistic(key, increments)
|
||||
end
|
||||
end
|
||||
|
||||
def increment_statistic(key, amount)
|
||||
def increment_statistic(key, increment)
|
||||
raise ArgumentError, "Cannot increment attribute: #{key}" unless incrementable_attribute?(key)
|
||||
|
||||
increment_counter(key, amount)
|
||||
increment_counter(key, increment)
|
||||
end
|
||||
|
||||
def bulk_increment_statistic(key, increments)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ module Captcha
|
|||
# Encapsulates logic of checking captchas.
|
||||
#
|
||||
class CaptchaVerificationService
|
||||
include Recaptcha::Verify
|
||||
include Recaptcha::Adapters::ControllerMethods
|
||||
|
||||
# Currently the only value that is used out of the request by the reCAPTCHA library
|
||||
# is 'remote_ip'. Therefore, we just create a struct to avoid passing the full request
|
||||
|
|
@ -45,7 +45,7 @@ module Captcha
|
|||
|
||||
attr_reader :spam_params
|
||||
|
||||
# The recaptcha library's Recaptcha::Verify#verify_recaptcha method requires that
|
||||
# The recaptcha library's Recaptcha::Adapters::ControllerMethods#verify_recaptcha method requires that
|
||||
# 'request' be a readable attribute - it doesn't support passing it as an options argument.
|
||||
attr_reader :request
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ module Ci
|
|||
end
|
||||
|
||||
def update_statistics
|
||||
@statistics_updates.each do |project, changes|
|
||||
ProjectStatistics.bulk_increment_statistic(project, Ci::JobArtifact.project_statistics_name, changes)
|
||||
@statistics_updates.each do |project, increments|
|
||||
ProjectStatistics.bulk_increment_statistic(project, Ci::JobArtifact.project_statistics_name, increments)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -73,8 +73,8 @@ module Ci
|
|||
|
||||
# using ! here since this can't be called inside a transaction
|
||||
def update_project_statistics!
|
||||
statistics_updates_per_project.each do |project, updates|
|
||||
ProjectStatistics.bulk_increment_statistic(project, Ci::JobArtifact.project_statistics_name, updates)
|
||||
statistics_updates_per_project.each do |project, increments|
|
||||
ProjectStatistics.bulk_increment_statistic(project, Ci::JobArtifact.project_statistics_name, increments)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -83,7 +83,8 @@ module Ci
|
|||
result = Hash.new { |updates, project| updates[project] = [] }
|
||||
|
||||
@job_artifacts.each_with_object(result) do |job_artifact, result|
|
||||
result[job_artifact.project] << -job_artifact.size.to_i
|
||||
increment = Gitlab::Counters::Increment.new(amount: -job_artifact.size.to_i, ref: job_artifact.id)
|
||||
result[job_artifact.project] << increment
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,12 +13,13 @@ module Projects
|
|||
if batch.any?
|
||||
# We are doing the sum in ruby because the query takes too long when done in SQL
|
||||
total_artifacts_size = batch.sum { |artifact| artifact.size.to_i }
|
||||
increment = Gitlab::Counters::Increment.new(amount: total_artifacts_size)
|
||||
|
||||
Projects::BuildArtifactsSizeRefresh.transaction do
|
||||
# Mark the refresh ready for another worker to pick up and process the next batch
|
||||
refresh.requeue!(batch.last.id)
|
||||
|
||||
refresh.project.statistics.increment_counter(:build_artifacts_size, total_artifacts_size)
|
||||
refresh.project.statistics.increment_counter(:build_artifacts_size, increment)
|
||||
end
|
||||
else
|
||||
# Remove the refresh job from the table if there are no more
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ module ObjectStorage
|
|||
hash[:TempPath] = workhorse_local_upload_path
|
||||
end
|
||||
|
||||
hash[:UploadHashFunctions] = %w[sha1 sha256 sha512] if ::Gitlab::FIPS.enabled?
|
||||
hash[:MaximumSize] = maximum_size if maximum_size.present?
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
- page_title _("Dashboard")
|
||||
- header_title _("Dashboard"), root_path unless header_title
|
||||
- sidebar "dashboard"
|
||||
- @hide_breadcrumbs = true
|
||||
- if Feature.enabled?(:your_work_sidebar, current_user)
|
||||
- @left_sidebar = true
|
||||
- nav "your_work"
|
||||
- else
|
||||
- @hide_breadcrumbs = true
|
||||
|
||||
= render template: "layouts/application"
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
= render partial: 'shared/nav/sidebar', object: Sidebars::YourWork::Panel.new(Sidebars::Context.new(current_user: current_user, container: nil))
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
%li.context-header
|
||||
= link_to root_url do
|
||||
%span.icon-container
|
||||
= sprite_icon "work"
|
||||
%span.sidebar-context-title= _("Your work")
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: your_work_sidebar
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107345
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/385855
|
||||
milestone: '15.8'
|
||||
type: development
|
||||
group: group::foundations
|
||||
default_enabled: false
|
||||
|
|
@ -64,6 +64,7 @@ Rails.autoloaders.each do |autoloader|
|
|||
'svg' => 'SVG',
|
||||
'function_uri' => 'FunctionURI',
|
||||
'uuid' => 'UUID',
|
||||
'occurrence_uuid' => 'OccurrenceUUID',
|
||||
'vulnerability_uuid' => 'VulnerabilityUUID',
|
||||
'vs_code_extension_activity_unique_counter' => 'VSCodeExtensionActivityUniqueCounter'
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../tooling/danger/user_types'
|
||||
|
||||
module Danger
|
||||
class UserTypes < ::Danger::Plugin
|
||||
include Tooling::Danger::UserTypes
|
||||
end
|
||||
end
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
user_types.bot_user_types_change_warning
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
To support more developers where they're already working, we've adopted the open source project `glab`, which will form the foundation of GitLab's native CLI experience. The GitLab CLI brings GitLab together with Git and your code, with no application or tab switching required.
|
||||
|
||||
You can read about our adoption of `glab`, our partnership with 1Password, and how to contribute to the project in our [blog post](/blog/2022/12/07/introducing-the-gitlab-cli/).
|
||||
You can read about our adoption of `glab`, our partnership with 1Password, and how to contribute to the project in our [blog post](https://about.gitlab.com/blog/2022/12/07/introducing-the-gitlab-cli/).
|
||||
|
||||
A special thank you to [Clement Sam](https://gitlab.com/profclems) for creating `glab` and trusting us with its future.
|
||||
stage: create
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoveSbomOccurrencesUniqueIndex < Gitlab::Database::Migration[2.1]
|
||||
INDEX_NAME = 'index_sbom_occurrences_on_ingestion_attributes'
|
||||
ATTRIBUTES = %i[
|
||||
project_id
|
||||
component_id
|
||||
component_version_id
|
||||
source_id
|
||||
commit_sha
|
||||
].freeze
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
remove_concurrent_index_by_name :sbom_occurrences, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :sbom_occurrences, ATTRIBUTES, unique: true, name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TruncateSbomOccurrences < Gitlab::Database::Migration[2.1]
|
||||
def up
|
||||
# Because existing data in the table violates the new
|
||||
# uniqueness constraints, we need to remove the non-distinct rows.
|
||||
# Rather than do an expensive and error-prone batch migration
|
||||
# to find and remove the duplicates, we'll just remove all records
|
||||
# from the table.
|
||||
#
|
||||
# The `cyclonedx_sbom_ingestion` feature flag should
|
||||
# be OFF in all environments to avoid having more duplicate records
|
||||
# added between this migration and the one where the new unqiue index
|
||||
# is added.
|
||||
|
||||
# TRUNCATE is a DDL statement (it drops the table and re-creates it), so we want to run the
|
||||
# migration in DDL mode, but we also don't want to execute it against all schemas because
|
||||
# it's considered a write operation. So, we'll manually check and skip the migration if
|
||||
# it's on not `:gitlab_main`.
|
||||
return unless Gitlab::Database.gitlab_schemas_for_connection(connection).include?(:gitlab_main)
|
||||
|
||||
execute('TRUNCATE sbom_occurrences')
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddUuidColumnToSbomOccurrences < Gitlab::Database::Migration[2.1]
|
||||
def change
|
||||
add_column :sbom_occurrences, :uuid, :uuid, null: false # rubocop:disable Rails/NotNullColumn
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexSbomOccurrencesOnUuid < Gitlab::Database::Migration[2.1]
|
||||
INDEX_NAME = 'index_sbom_occurrences_on_uuid'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_concurrent_index :sbom_occurrences, :uuid, unique: true, name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :sbom_occurrences, name: INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UpdateBillableUsersIndex < Gitlab::Database::Migration[2.1]
|
||||
disable_ddl_transaction!
|
||||
|
||||
NEW_INDEX = 'index_users_for_billable_users'
|
||||
OLD_INDEX = 'index_users_for_active_billable'
|
||||
|
||||
OLD_INDEX_CONDITION = <<~QUERY
|
||||
((state)::text = 'active'::text) AND ((user_type IS NULL)
|
||||
OR (user_type = ANY (ARRAY[NULL::integer, 6, 4]))) AND ((user_type IS NULL)
|
||||
OR (user_type <> ALL ('{1,2,3,4,5,6,7,8,9,11}'::smallint[])))
|
||||
QUERY
|
||||
NEW_INDEX_CONDITION = <<~QUERY
|
||||
state = 'active' AND (user_type IS NULL OR user_type IN (6, 4)) AND (user_type IS NULL OR user_type IN (4, 5))
|
||||
QUERY
|
||||
|
||||
def up
|
||||
add_concurrent_index(:users, :id, where: NEW_INDEX_CONDITION, name: NEW_INDEX)
|
||||
remove_concurrent_index_by_name(:users, OLD_INDEX)
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index(:users, :id, where: OLD_INDEX_CONDITION, name: OLD_INDEX)
|
||||
remove_concurrent_index_by_name(:users, NEW_INDEX)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
5bc41c2430a033da7aa063e5646941428bb01cbf99aafed4acc80b4f9aa2f650
|
||||
|
|
@ -0,0 +1 @@
|
|||
5a7f509173cf10ab512935db0dd65ab9ed347539a6448e2922ea603db418b1df
|
||||
|
|
@ -0,0 +1 @@
|
|||
51f9c66f46063a9ad6979f2a50b0d963d93c007b25bde2dedf941317317ef077
|
||||
|
|
@ -0,0 +1 @@
|
|||
de8a5fae011e67ff3b8da9c73f0c19a93a2c534764d81bc72e3058627b5ab6b5
|
||||
|
|
@ -0,0 +1 @@
|
|||
a842c4aae88386fc5fdeb7f08c0a2ba14780b651801e7dae28c974af58aa946c
|
||||
|
|
@ -21380,7 +21380,8 @@ CREATE TABLE sbom_occurrences (
|
|||
pipeline_id bigint,
|
||||
source_id bigint,
|
||||
commit_sha bytea NOT NULL,
|
||||
component_id bigint NOT NULL
|
||||
component_id bigint NOT NULL,
|
||||
uuid uuid NOT NULL
|
||||
);
|
||||
|
||||
CREATE SEQUENCE sbom_occurrences_id_seq
|
||||
|
|
@ -30944,14 +30945,14 @@ CREATE INDEX index_sbom_occurrences_on_component_id ON sbom_occurrences USING bt
|
|||
|
||||
CREATE INDEX index_sbom_occurrences_on_component_version_id ON sbom_occurrences USING btree (component_version_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_sbom_occurrences_on_ingestion_attributes ON sbom_occurrences USING btree (project_id, component_id, component_version_id, source_id, commit_sha);
|
||||
|
||||
CREATE INDEX index_sbom_occurrences_on_pipeline_id ON sbom_occurrences USING btree (pipeline_id);
|
||||
|
||||
CREATE INDEX index_sbom_occurrences_on_project_id ON sbom_occurrences USING btree (project_id);
|
||||
|
||||
CREATE INDEX index_sbom_occurrences_on_source_id ON sbom_occurrences USING btree (source_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_sbom_occurrences_on_uuid ON sbom_occurrences USING btree (uuid);
|
||||
|
||||
CREATE UNIQUE INDEX index_sbom_sources_on_source_type_and_source ON sbom_sources USING btree (source_type, source);
|
||||
|
||||
CREATE INDEX index_scim_identities_on_group_id ON scim_identities USING btree (group_id);
|
||||
|
|
@ -31312,7 +31313,7 @@ CREATE INDEX index_user_statuses_on_user_id ON user_statuses USING btree (user_i
|
|||
|
||||
CREATE UNIQUE INDEX index_user_synced_attributes_metadata_on_user_id ON user_synced_attributes_metadata USING btree (user_id);
|
||||
|
||||
CREATE INDEX index_users_for_active_billable ON users USING btree (id) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[NULL::integer, 6, 4]))) AND ((user_type IS NULL) OR (user_type <> ALL ('{1,2,3,4,5,6,7,8,9,11}'::smallint[]))));
|
||||
CREATE INDEX index_users_for_billable_users ON users USING btree (id) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[6, 4]))) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[4, 5]))));
|
||||
|
||||
CREATE INDEX index_users_on_accepted_term_id ON users USING btree (accepted_term_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Look up the global node modules directory.
|
||||
*
|
||||
* Because we install markdownlint packages globally
|
||||
* in the Docker image where this runs, we need to
|
||||
* provide the path to the global install location
|
||||
* when referencing global functions from our own node
|
||||
* modules.
|
||||
*
|
||||
* Image:
|
||||
* https://gitlab.com/gitlab-org/gitlab-docs/-/blob/main/dockerfiles/gitlab-docs-lint-markdown.Dockerfile
|
||||
*/
|
||||
const { execSync } = require('child_process');
|
||||
module.exports.globalPath = execSync('yarn global dir').toString().trim() + '/node_modules/';
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
const { forEachLine, getLineMetadata, isBlankLine } = require(`markdownlint-rule-helpers`);
|
||||
const { globalPath } = require('../require_helper');
|
||||
const {
|
||||
forEachLine,
|
||||
getLineMetadata,
|
||||
isBlankLine,
|
||||
} = require(`${globalPath}/markdownlint-rule-helpers`);
|
||||
|
||||
module.exports = {
|
||||
names: ['tabs-blank-lines'],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const { forEachLine, getLineMetadata } = require(`markdownlint-rule-helpers`);
|
||||
const { globalPath } = require('../require_helper');
|
||||
const { forEachLine, getLineMetadata } = require(`${globalPath}/markdownlint-rule-helpers`);
|
||||
|
||||
module.exports = {
|
||||
names: ['tabs-title-markup'],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
const { forEachLine, getLineMetadata, isBlankLine } = require(`markdownlint-rule-helpers`);
|
||||
const { globalPath } = require('../require_helper');
|
||||
const {
|
||||
forEachLine,
|
||||
getLineMetadata,
|
||||
isBlankLine,
|
||||
} = require(`${globalPath}/markdownlint-rule-helpers`);
|
||||
|
||||
module.exports = {
|
||||
names: ['tabs-title-text'],
|
||||
|
|
|
|||
|
|
@ -10278,6 +10278,7 @@ Describes an alert from the project's Alert Management.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="alertmanagementalertassignees"></a>`assignees` | [`UserCoreConnection`](#usercoreconnection) | Assignees of the alert. (see [Connections](#connections)) |
|
||||
| <a id="alertmanagementalertcommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="alertmanagementalertcreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp the alert was created. |
|
||||
| <a id="alertmanagementalertdescription"></a>`description` | [`String`](#string) | Description of the alert. |
|
||||
| <a id="alertmanagementalertdetails"></a>`details` | [`JSON`](#json) | Alert details. |
|
||||
|
|
@ -10617,6 +10618,7 @@ Represents an epic on an issue board.
|
|||
| <a id="boardepicblockingcount"></a>`blockingCount` | [`Int`](#int) | Count of epics that this epic is blocking. |
|
||||
| <a id="boardepicclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. |
|
||||
| <a id="boardepiccolor"></a>`color` | [`String`](#string) | Color of the epic. Returns `null` if `epic_color_highlight` feature flag is disabled. |
|
||||
| <a id="boardepiccommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="boardepicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
|
||||
| <a id="boardepiccreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the epic was created. |
|
||||
| <a id="boardepicdefaultprojectforissuecreation"></a>`defaultProjectForIssueCreation` | [`Project`](#project) | Default Project for issue creation. Based on the project the user created the last issue in. |
|
||||
|
|
@ -12118,6 +12120,7 @@ A single design.
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="designcommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="designdiffrefs"></a>`diffRefs` | [`DiffRefs!`](#diffrefs) | Diff refs for this design. |
|
||||
| <a id="designdiscussions"></a>`discussions` | [`DiscussionConnection!`](#discussionconnection) | All discussions on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="designevent"></a>`event` | [`DesignVersionEvent!`](#designversionevent) | How this design was changed in the current version. |
|
||||
|
|
@ -12619,6 +12622,7 @@ Represents an epic.
|
|||
| <a id="epicblockingcount"></a>`blockingCount` | [`Int`](#int) | Count of epics that this epic is blocking. |
|
||||
| <a id="epicclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the epic was closed. |
|
||||
| <a id="epiccolor"></a>`color` | [`String`](#string) | Color of the epic. Returns `null` if `epic_color_highlight` feature flag is disabled. |
|
||||
| <a id="epiccommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="epicconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if the epic is confidential. |
|
||||
| <a id="epiccreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of when the epic was created. |
|
||||
| <a id="epicdefaultprojectforissuecreation"></a>`defaultProjectForIssueCreation` | [`Project`](#project) | Default Project for issue creation. Based on the project the user created the last issue in. |
|
||||
|
|
@ -12860,6 +12864,7 @@ Relationship between an epic and an issue.
|
|||
| <a id="epicissueblockingcount"></a>`blockingCount` | [`Int!`](#int) | Count of issues this issue is blocking. |
|
||||
| <a id="epicissueclosedasduplicateof"></a>`closedAsDuplicateOf` | [`Issue`](#issue) | Issue this issue was closed as a duplicate of. |
|
||||
| <a id="epicissueclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the issue was closed. |
|
||||
| <a id="epicissuecommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="epicissueconfidential"></a>`confidential` | [`Boolean!`](#boolean) | Indicates the issue is confidential. |
|
||||
| <a id="epicissuecreatenoteemail"></a>`createNoteEmail` | [`String`](#string) | User specific email address for the issue. |
|
||||
| <a id="epicissuecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the issue was created. |
|
||||
|
|
@ -14574,6 +14579,7 @@ Describes an issuable resource link for incident issues.
|
|||
| <a id="issueblockingcount"></a>`blockingCount` | [`Int!`](#int) | Count of issues this issue is blocking. |
|
||||
| <a id="issueclosedasduplicateof"></a>`closedAsDuplicateOf` | [`Issue`](#issue) | Issue this issue was closed as a duplicate of. |
|
||||
| <a id="issueclosedat"></a>`closedAt` | [`Time`](#time) | Timestamp of when the issue was closed. |
|
||||
| <a id="issuecommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="issueconfidential"></a>`confidential` | [`Boolean!`](#boolean) | Indicates the issue is confidential. |
|
||||
| <a id="issuecreatenoteemail"></a>`createNoteEmail` | [`String`](#string) | User specific email address for the issue. |
|
||||
| <a id="issuecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the issue was created. |
|
||||
|
|
@ -15002,6 +15008,7 @@ Defines which user roles, users, or groups can merge into a protected branch.
|
|||
| <a id="mergerequestautomergeenabled"></a>`autoMergeEnabled` | [`Boolean!`](#boolean) | Indicates if auto merge is enabled for the merge request. |
|
||||
| <a id="mergerequestautomergestrategy"></a>`autoMergeStrategy` | [`String`](#string) | Selected auto merge strategy. |
|
||||
| <a id="mergerequestavailableautomergestrategies"></a>`availableAutoMergeStrategies` | [`[String!]`](#string) | Array of available auto merge strategies. |
|
||||
| <a id="mergerequestcommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="mergerequestcommitcount"></a>`commitCount` | [`Int`](#int) | Number of commits in the merge request. |
|
||||
| <a id="mergerequestcommits"></a>`commits` | [`CommitConnection`](#commitconnection) | Merge request commits. (see [Connections](#connections)) |
|
||||
| <a id="mergerequestcommitswithoutmergecommits"></a>`commitsWithoutMergeCommits` | [`CommitConnection`](#commitconnection) | Merge request commits excluding merge commits. (see [Connections](#connections)) |
|
||||
|
|
@ -19428,6 +19435,7 @@ Represents a snippet entry.
|
|||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="snippetauthor"></a>`author` | [`UserCore`](#usercore) | Owner of the snippet. |
|
||||
| <a id="snippetcommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="snippetcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp this snippet was created. |
|
||||
| <a id="snippetdescription"></a>`description` | [`String`](#string) | Description of the snippet. |
|
||||
| <a id="snippetdescriptionhtml"></a>`descriptionHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `description`. |
|
||||
|
|
@ -20298,6 +20306,7 @@ Represents a vulnerability.
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="vulnerabilitycommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="vulnerabilityconfirmedat"></a>`confirmedAt` | [`Time`](#time) | Timestamp of when the vulnerability state was changed to confirmed. |
|
||||
| <a id="vulnerabilityconfirmedby"></a>`confirmedBy` | [`UserCore`](#usercore) | User that confirmed the vulnerability. |
|
||||
| <a id="vulnerabilitydescription"></a>`description` | [`String`](#string) | Description of the vulnerability. |
|
||||
|
|
@ -24189,6 +24198,7 @@ Implementations:
|
|||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="noteableinterfacecommenters"></a>`commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="noteableinterfacediscussions"></a>`discussions` | [`DiscussionConnection!`](#discussionconnection) | All discussions on this noteable. (see [Connections](#connections)) |
|
||||
| <a id="noteableinterfacenotes"></a>`notes` | [`NoteConnection!`](#noteconnection) | All notes on this noteable. (see [Connections](#connections)) |
|
||||
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ README.md @docs
|
|||
|
||||
A Code Owner approval rule is optional if any of these conditions are true:
|
||||
|
||||
- The user or group are not a member of the project or parent group.
|
||||
- The user or group are not a member of the project. Code Owners [cannot inherit from parent groups](https://gitlab.com/gitlab-org/gitlab/-/issues/288851/).
|
||||
- [Code Owner approval on a protected branch](protected_branches.md#require-code-owner-approval-on-a-protected-branch) has not been set up.
|
||||
- The section is [marked as optional](#make-a-code-owners-section-optional).
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ module Gitlab
|
|||
secret_detection: %w[14.0.0 14.0.1 14.0.2 14.0.3 14.0.4 14.0.5 14.0.6 14.1.0 14.1.1 14.1.2 14.1.3 15.0.0 15.0.1 15.0.2 15.0.4]
|
||||
}.freeze
|
||||
|
||||
VERSIONS_TO_REMOVE_IN_16_0 = [].freeze
|
||||
VERSIONS_TO_REMOVE_IN_16_0 = %w[14.0.0 14.0.1 14.0.2 14.0.3 14.0.4 14.0.5 14.0.6 14.1.0 14.1.1 14.1.2 14.1.3].freeze
|
||||
|
||||
DEPRECATED_VERSIONS = {
|
||||
cluster_image_scanning: VERSIONS_TO_REMOVE_IN_16_0,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Counters
|
||||
Increment = Struct.new(:amount, :ref, keyword_init: true)
|
||||
end
|
||||
end
|
||||
|
|
@ -31,9 +31,9 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def increment(amount)
|
||||
def increment(increment)
|
||||
result = redis_state do |redis|
|
||||
redis.incrby(key, amount)
|
||||
redis.incrby(key, increment.amount)
|
||||
end
|
||||
|
||||
FlushCounterIncrementsWorker.perform_in(WORKER_DELAY, counter_record.class.name, counter_record.id, attribute)
|
||||
|
|
@ -45,7 +45,7 @@ module Gitlab
|
|||
result = redis_state do |redis|
|
||||
redis.pipelined do |pipeline|
|
||||
increments.each do |increment|
|
||||
pipeline.incrby(key, increment)
|
||||
pipeline.incrby(key, increment.amount)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,19 +11,19 @@ module Gitlab
|
|||
@current_value = counter_record.method(attribute).call
|
||||
end
|
||||
|
||||
def increment(amount)
|
||||
updated = update_counter_record_attribute(amount)
|
||||
def increment(increment)
|
||||
updated = update_counter_record_attribute(increment.amount)
|
||||
|
||||
if updated == 1
|
||||
counter_record.execute_after_commit_callbacks
|
||||
@current_value += amount
|
||||
@current_value += increment.amount
|
||||
end
|
||||
|
||||
@current_value
|
||||
end
|
||||
|
||||
def bulk_increment(increments)
|
||||
total_increment = increments.sum
|
||||
total_increment = increments.sum(&:amount)
|
||||
|
||||
updated = update_counter_record_attribute(total_increment)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module YourWork
|
||||
module Menus
|
||||
class ProjectsMenu < ::Sidebars::Menu
|
||||
override :link
|
||||
def link
|
||||
dashboard_projects_path
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
_('Projects')
|
||||
end
|
||||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'project'
|
||||
end
|
||||
|
||||
override :render?
|
||||
def render?
|
||||
!!context.current_user
|
||||
end
|
||||
|
||||
override :active_routes
|
||||
def active_routes
|
||||
{ controller: ['root', 'projects', 'dashboard/projects'] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module YourWork
|
||||
class Panel < ::Sidebars::Panel
|
||||
override :configure_menus
|
||||
def configure_menus
|
||||
add_menus
|
||||
end
|
||||
|
||||
override :aria_label
|
||||
def aria_label
|
||||
_('Your work')
|
||||
end
|
||||
|
||||
override :render_raw_scope_menu_partial
|
||||
def render_raw_scope_menu_partial
|
||||
"shared/nav/your_work_scope_header"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_menus
|
||||
add_menu(Sidebars::YourWork::Menus::ProjectsMenu.new(context))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14,7 +14,7 @@ namespace :contracts do
|
|||
pact_helper_location = "pact_helpers/project/merge_requests/show/get_diffs_batch_helper.rb"
|
||||
|
||||
pact.uri(
|
||||
Provider::ContractSourceHelper.contract_location(:rake, pact_helper_location),
|
||||
Provider::ContractSourceHelper.contract_location(requester: :rake, file_path: pact_helper_location),
|
||||
pact_helper: "#{provider}/#{pact_helper_location}"
|
||||
)
|
||||
end
|
||||
|
|
@ -23,7 +23,7 @@ namespace :contracts do
|
|||
pact_helper_location = "pact_helpers/project/merge_requests/show/get_diffs_metadata_helper.rb"
|
||||
|
||||
pact.uri(
|
||||
Provider::ContractSourceHelper.contract_location(:rake, pact_helper_location),
|
||||
Provider::ContractSourceHelper.contract_location(requester: :rake, file_path: pact_helper_location),
|
||||
pact_helper: "#{provider}/#{pact_helper_location}"
|
||||
)
|
||||
end
|
||||
|
|
@ -32,7 +32,7 @@ namespace :contracts do
|
|||
pact_helper_location = "pact_helpers/project/merge_requests/show/get_discussions_helper.rb"
|
||||
|
||||
pact.uri(
|
||||
Provider::ContractSourceHelper.contract_location(:rake, pact_helper_location),
|
||||
Provider::ContractSourceHelper.contract_location(requester: :rake, file_path: pact_helper_location),
|
||||
pact_helper: "#{provider}/#{pact_helper_location}"
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace :contracts do
|
|||
pact_helper_location = "pact_helpers/project/pipeline_schedules/edit/put_edit_a_pipeline_schedule_helper.rb"
|
||||
|
||||
pact.uri(
|
||||
Provider::ContractSourceHelper.contract_location(:rake, pact_helper_location),
|
||||
Provider::ContractSourceHelper.contract_location(requester: :rake, file_path: pact_helper_location),
|
||||
pact_helper: "#{provider}/#{pact_helper_location}"
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace :contracts do
|
|||
pact_helper_location = "pact_helpers/project/pipelines/new/post_create_a_new_pipeline_helper.rb"
|
||||
|
||||
pact.uri(
|
||||
Provider::ContractSourceHelper.contract_location(:rake, pact_helper_location),
|
||||
Provider::ContractSourceHelper.contract_location(requester: :rake, file_path: pact_helper_location),
|
||||
pact_helper: "#{provider}/#{pact_helper_location}"
|
||||
)
|
||||
end
|
||||
|
|
@ -23,7 +23,7 @@ namespace :contracts do
|
|||
pact_helper_location = "pact_helpers/project/pipelines/index/get_list_project_pipelines_helper.rb"
|
||||
|
||||
pact.uri(
|
||||
Provider::ContractSourceHelper.contract_location(:rake, pact_helper_location),
|
||||
Provider::ContractSourceHelper.contract_location(requester: :rake, file_path: pact_helper_location),
|
||||
pact_helper: "#{provider}/#{pact_helper_location}"
|
||||
)
|
||||
end
|
||||
|
|
@ -32,7 +32,7 @@ namespace :contracts do
|
|||
pact_helper_location = "pact_helpers/project/pipelines/show/get_pipeline_header_data_helper.rb"
|
||||
|
||||
pact.uri(
|
||||
Provider::ContractSourceHelper.contract_location(:rake, pact_helper_location),
|
||||
Provider::ContractSourceHelper.contract_location(requester: :rake, file_path: pact_helper_location),
|
||||
pact_helper: "#{provider}/#{pact_helper_location}"
|
||||
)
|
||||
end
|
||||
|
|
@ -41,7 +41,7 @@ namespace :contracts do
|
|||
pact_helper_location = "pact_helpers/project/pipelines/show/delete_pipeline_helper.rb"
|
||||
|
||||
pact.uri(
|
||||
Provider::ContractSourceHelper.contract_location(:rake, pact_helper_location),
|
||||
Provider::ContractSourceHelper.contract_location(requester: :rake, file_path: pact_helper_location),
|
||||
pact_helper: "#{provider}/#{pact_helper_location}"
|
||||
)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -34,11 +34,6 @@ unless Rails.env.production?
|
|||
exit(1)
|
||||
end
|
||||
|
||||
desc "GitLab | Lint | Lint docs Markdown files"
|
||||
task :markdown do
|
||||
sh "./scripts/lint-doc.sh"
|
||||
end
|
||||
|
||||
desc "GitLab | Lint | Run several lint checks"
|
||||
task :all do
|
||||
status = 0
|
||||
|
|
|
|||
|
|
@ -48431,6 +48431,9 @@ msgstr ""
|
|||
msgid "Your username is %{username}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Your work"
|
||||
msgstr ""
|
||||
|
||||
msgid "You’re about to permanently delete the %{issuableType} ‘%{strongOpen}%{issuableTitle}%{strongClose}’. To avoid data loss, consider %{strongOpen}closing this %{issuableType}%{strongClose} instead. Once deleted, it cannot be undone or recovered."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,6 @@ module QA
|
|||
@provider.validate_dependencies
|
||||
@provider.setup
|
||||
|
||||
@api_url = fetch_api_url
|
||||
|
||||
credentials = @provider.filter_credentials(fetch_credentials)
|
||||
@ca_certificate = Base64.decode64(credentials.dig('data', 'ca.crt'))
|
||||
@token = Base64.decode64(credentials.dig('data', 'token'))
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
module QA
|
||||
RSpec.describe 'Configure',
|
||||
quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/381454', type: :flaky },
|
||||
only: { subdomain: %i[staging staging-canary] }, product_group: :configure do
|
||||
only: { subdomain: %i[staging staging-canary] }, product_group: :configure do
|
||||
describe 'Auto DevOps with a Kubernetes Agent' do
|
||||
let!(:app_project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
|
|
|
|||
|
|
@ -119,24 +119,19 @@ else
|
|||
fi
|
||||
fi
|
||||
|
||||
function run_locally_or_in_container() {
|
||||
function run_locally_or_in_docker() {
|
||||
local cmd=$1
|
||||
local args=$2
|
||||
|
||||
if hash ${cmd} 2>/dev/null
|
||||
then
|
||||
$cmd $args
|
||||
# When using software like Rancher Desktop, both nerdctl and docker binaries are available
|
||||
# but only one is configured. To check which one to use, we need to probe each runtime
|
||||
elif (hash nerdctl 2>/dev/null) && (nerdctl info 2>&1 1>/dev/null)
|
||||
elif hash docker 2>/dev/null
|
||||
then
|
||||
nerdctl run -t -v "${PWD}:/gitlab" -w /gitlab --rm registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.16-vale-2.20.1-markdownlint-0.32.2 ${cmd} ${args}
|
||||
elif (hash docker 2>/dev/null) && (docker info 2>&1 1>/dev/null)
|
||||
then
|
||||
docker run -t -v "${PWD}:/gitlab" -w /gitlab --rm registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.16-vale-2.20.1-markdownlint-0.32.2 ${cmd} ${args}
|
||||
docker run -t -v ${PWD}:/gitlab -w /gitlab --rm registry.gitlab.com/gitlab-org/gitlab-docs/lint-markdown:alpine-3.16-vale-2.20.1-markdownlint-0.32.2 ${cmd} ${args}
|
||||
else
|
||||
echo
|
||||
echo " ✖ ERROR: '${cmd}' not found. Install '${cmd}' or a container runtime (Docker/Nerdctl) to proceed." >&2
|
||||
echo " ✖ ERROR: '${cmd}' not found. Install '${cmd}' or Docker to proceed." >&2
|
||||
echo
|
||||
((ERRORCODE++))
|
||||
fi
|
||||
|
|
@ -156,19 +151,11 @@ if [ -z "${MD_DOC_PATH}" ]
|
|||
then
|
||||
echo "Merged results pipeline detected, but no markdown files found. Skipping."
|
||||
else
|
||||
yarn markdownlint --config .markdownlint.yml ${MD_DOC_PATH} --rules doc/.markdownlint/rules
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
echo
|
||||
echo '✖ ERROR: Markdownlint failed with errors.' >&2
|
||||
echo
|
||||
((ERRORCODE++))
|
||||
fi
|
||||
run_locally_or_in_docker 'markdownlint' "--config .markdownlint.yml ${MD_DOC_PATH} --rules doc/.markdownlint/rules"
|
||||
fi
|
||||
|
||||
echo '=> Linting prose...'
|
||||
run_locally_or_in_container 'vale' "--minAlertLevel error --output=doc/.vale/vale.tmpl ${MD_DOC_PATH}"
|
||||
run_locally_or_in_docker 'vale' "--minAlertLevel error --output=doc/.vale/vale.tmpl ${MD_DOC_PATH}"
|
||||
|
||||
if [ $ERRORCODE -ne 0 ]
|
||||
then
|
||||
|
|
|
|||
|
|
@ -4,18 +4,22 @@ module Provider
|
|||
module ContractSourceHelper
|
||||
QA_PACT_BROKER_HOST = "http://localhost:9292/pacts"
|
||||
PREFIX_PATHS = {
|
||||
rake: "../../../contracts/contracts/project",
|
||||
rake: {
|
||||
ce: "../../contracts/project",
|
||||
ee: "../../../../ee/spec/contracts/contracts/project"
|
||||
},
|
||||
spec: "../contracts/project"
|
||||
}.freeze
|
||||
SUB_PATH_REGEX = %r{project/(?<file_path>.*?)_helper.rb}.freeze
|
||||
|
||||
class << self
|
||||
def contract_location(requester, file_path)
|
||||
def contract_location(requester:, file_path:, edition: :ce)
|
||||
raise ArgumentError, 'requester must be :rake or :spec' unless [:rake, :spec].include? requester
|
||||
raise ArgumentError, 'edition must be :ce or :ee' unless [:ce, :ee].include? edition
|
||||
|
||||
relevant_path = file_path.match(SUB_PATH_REGEX)[:file_path].split('/')
|
||||
|
||||
ENV["PACT_BROKER"] ? pact_broker_url(relevant_path) : local_contract_location(requester, relevant_path)
|
||||
ENV["PACT_BROKER"] ? pact_broker_url(relevant_path) : local_contract_location(requester, relevant_path, edition)
|
||||
end
|
||||
|
||||
def pact_broker_url(file_path)
|
||||
|
|
@ -36,9 +40,10 @@ module Provider
|
|||
"#{file_path[0].split('_').map(&:capitalize).join}%23#{file_path[1]}"
|
||||
end
|
||||
|
||||
def local_contract_location(requester, file_path)
|
||||
def local_contract_location(requester, file_path, edition)
|
||||
contract_path = construct_local_contract_path(file_path)
|
||||
prefix_path = requester == :rake ? File.expand_path(PREFIX_PATHS[requester], __dir__) : PREFIX_PATHS[requester]
|
||||
prefix_path = PREFIX_PATHS[requester]
|
||||
prefix_path = File.expand_path(prefix_path[edition], __dir__) if requester == :rake
|
||||
|
||||
"#{prefix_path}#{contract_path}"
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Provider
|
|||
app { Environments::Test.app }
|
||||
|
||||
honours_pact_with "MergeRequests#show" do
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(:spec, __FILE__)
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(requester: :spec, file_path: __FILE__)
|
||||
end
|
||||
|
||||
Provider::PublishContractHelper.publish_contract_setup.call(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Provider
|
|||
app { Environments::Test.app }
|
||||
|
||||
honours_pact_with "MergeRequests#show" do
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(:spec, __FILE__)
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(requester: :spec, file_path: __FILE__)
|
||||
end
|
||||
|
||||
Provider::PublishContractHelper.publish_contract_setup.call(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Provider
|
|||
app { Environments::Test.app }
|
||||
|
||||
honours_pact_with "MergeRequests#show" do
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(:spec, __FILE__)
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(requester: :spec, file_path: __FILE__)
|
||||
end
|
||||
|
||||
Provider::PublishContractHelper.publish_contract_setup.call(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Provider
|
|||
app { Environments::Test.app }
|
||||
|
||||
honours_pact_with "PipelineSchedules#edit" do
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(:spec, __FILE__)
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(requester: :spec, file_path: __FILE__)
|
||||
end
|
||||
|
||||
Provider::PublishContractHelper.publish_contract_setup.call(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Provider
|
|||
app { Environments::Test.app }
|
||||
|
||||
honours_pact_with "Pipelines#index" do
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(:spec, __FILE__)
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(requester: :spec, file_path: __FILE__)
|
||||
end
|
||||
|
||||
Provider::PublishContractHelper.publish_contract_setup.call(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Provider
|
|||
app { Environments::Test.app }
|
||||
|
||||
honours_pact_with "Pipelines#new" do
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(:spec, __FILE__)
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(requester: :spec, file_path: __FILE__)
|
||||
end
|
||||
|
||||
Provider::PublishContractHelper.publish_contract_setup.call(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Provider
|
|||
app { Environments::Test.app }
|
||||
|
||||
honours_pact_with "Pipelines#show" do
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(:spec, __FILE__)
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(requester: :spec, file_path: __FILE__)
|
||||
end
|
||||
|
||||
Provider::PublishContractHelper.publish_contract_setup.call(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module Provider
|
|||
app { Environments::Test.app }
|
||||
|
||||
honours_pact_with "Pipelines#show" do
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(:spec, __FILE__)
|
||||
pact_uri Provider::ContractSourceHelper.contract_location(requester: :spec, file_path: __FILE__)
|
||||
end
|
||||
|
||||
Provider::PublishContractHelper.publish_contract_setup.call(
|
||||
|
|
|
|||
|
|
@ -11,21 +11,26 @@ RSpec.describe Provider::ContractSourceHelper, feature_category: :not_owned do
|
|||
|
||||
describe '#contract_location' do
|
||||
it 'raises an error when an invalid requester is given' do
|
||||
expect { subject.contract_location(:foo, pact_helper_path) }
|
||||
expect { subject.contract_location(requester: :foo, file_path: pact_helper_path) }
|
||||
.to raise_error(ArgumentError, 'requester must be :rake or :spec')
|
||||
end
|
||||
|
||||
it 'raises an error when an invalid edition is given' do
|
||||
expect { subject.contract_location(requester: :spec, file_path: pact_helper_path, edition: :zz) }
|
||||
.to raise_error(ArgumentError, 'edition must be :ce or :ee')
|
||||
end
|
||||
|
||||
context 'when the PACT_BROKER environment variable is not set' do
|
||||
it 'extracts the relevant path from the pact_helper path' do
|
||||
expect(subject).to receive(:local_contract_location).with(:rake, split_pact_helper_path)
|
||||
expect(subject).to receive(:local_contract_location).with(:rake, split_pact_helper_path, :ce)
|
||||
|
||||
subject.contract_location(:rake, pact_helper_path)
|
||||
subject.contract_location(requester: :rake, file_path: pact_helper_path)
|
||||
end
|
||||
|
||||
it 'does not construct the pact broker url' do
|
||||
expect(subject).not_to receive(:pact_broker_url)
|
||||
|
||||
subject.contract_location(:rake, pact_helper_path)
|
||||
subject.contract_location(requester: :rake, file_path: pact_helper_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -37,13 +42,13 @@ RSpec.describe Provider::ContractSourceHelper, feature_category: :not_owned do
|
|||
it 'extracts the relevant path from the pact_helper path' do
|
||||
expect(subject).to receive(:pact_broker_url).with(split_pact_helper_path)
|
||||
|
||||
subject.contract_location(:spec, pact_helper_path)
|
||||
subject.contract_location(requester: :spec, file_path: pact_helper_path)
|
||||
end
|
||||
|
||||
it 'does not construct the pact broker url' do
|
||||
expect(subject).not_to receive(:local_contract_location)
|
||||
|
||||
subject.contract_location(:spec, pact_helper_path)
|
||||
subject.contract_location(requester: :spec, file_path: pact_helper_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -51,7 +56,7 @@ RSpec.describe Provider::ContractSourceHelper, feature_category: :not_owned do
|
|||
describe '#pact_broker_url' do
|
||||
it 'returns the full url to the contract that the provider test is verifying' do
|
||||
contract_url_path = "http://localhost:9292/pacts/provider/" \
|
||||
"#{provider_url_path}/consumer/#{consumer_url_path}/latest"
|
||||
"#{provider_url_path}/consumer/#{consumer_url_path}/latest"
|
||||
|
||||
expect(subject.pact_broker_url(split_pact_helper_path)).to eq(contract_url_path)
|
||||
end
|
||||
|
|
@ -73,7 +78,7 @@ RSpec.describe Provider::ContractSourceHelper, feature_category: :not_owned do
|
|||
it 'returns the contract file path with the prefix path for a rake task' do
|
||||
rake_task_relative_path = '/spec/contracts/contracts/project'
|
||||
|
||||
rake_task_path = subject.local_contract_location(:rake, split_pact_helper_path)
|
||||
rake_task_path = subject.local_contract_location(:rake, split_pact_helper_path, :ce)
|
||||
|
||||
expect(rake_task_path).to include(rake_task_relative_path)
|
||||
expect(rake_task_path).not_to include('../')
|
||||
|
|
@ -82,7 +87,7 @@ RSpec.describe Provider::ContractSourceHelper, feature_category: :not_owned do
|
|||
it 'returns the contract file path with the prefix path for a spec' do
|
||||
spec_relative_path = '../contracts/project'
|
||||
|
||||
expect(subject.local_contract_location(:spec, split_pact_helper_path)).to include(spec_relative_path)
|
||||
expect(subject.local_contract_location(:spec, split_pact_helper_path, :ce)).to include(spec_relative_path)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe '"Your work" navbar', feature_category: :navigation do
|
||||
include_context 'dashboard navbar structure'
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
it_behaves_like 'verified navigation bar' do
|
||||
before do
|
||||
sign_in(user)
|
||||
visit root_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -18,6 +18,8 @@ RSpec.describe 'Dashboard Projects', feature_category: :projects do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like "a dashboard page with sidebar", :dashboard_projects_path, :projects
|
||||
|
||||
context 'when user has access to the project' do
|
||||
it 'shows role badge' do
|
||||
visit dashboard_projects_path
|
||||
|
|
|
|||
|
|
@ -72,10 +72,6 @@ RSpec.describe 'Global search', :js, feature_category: :global_search do
|
|||
# TODO: Remove this along with feature flag #339348
|
||||
stub_feature_flags(new_header_search: true)
|
||||
visit dashboard_projects_path
|
||||
|
||||
# initialize javascript loaded input search input field
|
||||
find('#search').click
|
||||
find('body').click
|
||||
end
|
||||
|
||||
it 'renders updated search bar' do
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ RSpec.describe GitlabSchema.types['AlertManagementAlert'], feature_category: :in
|
|||
prometheus_alert
|
||||
environment
|
||||
web_url
|
||||
commenters
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ RSpec.describe GitlabSchema.types['Design'] do
|
|||
specify { expect(described_class.interfaces).to include(Types::TodoableInterface) }
|
||||
|
||||
it_behaves_like 'a GraphQL type with design fields' do
|
||||
let(:extra_design_fields) { %i[notes current_user_todos discussions versions web_url] }
|
||||
let(:extra_design_fields) { %i[notes current_user_todos discussions versions web_url commenters] }
|
||||
let_it_be(:design) { create(:design, :with_versions) }
|
||||
let(:object_id) { GitlabSchema.id_from_object(design) }
|
||||
let_it_be(:object_id_b) { GitlabSchema.id_from_object(create(:design, :with_versions)) }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ RSpec.describe Types::Notes::NoteableInterface do
|
|||
expected_fields = %i[
|
||||
discussions
|
||||
notes
|
||||
commenters
|
||||
]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ RSpec.describe GitlabSchema.types['Snippet'] do
|
|||
:visibility_level, :created_at, :updated_at,
|
||||
:web_url, :raw_url, :ssh_url_to_repo, :http_url_to_repo,
|
||||
:notes, :discussions, :user_permissions,
|
||||
:description_html, :blobs]
|
||||
:description_html, :blobs, :commenters]
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator do
|
||||
RSpec.describe Gitlab::Ci::Parsers::Security::Validators::SchemaValidator, feature_category: :vulnerability_management do
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
let(:supported_dast_versions) { described_class::SUPPORTED_VERSIONS[:dast].join(', ') }
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ RSpec.describe Gitlab::Counters::BufferedCounter, :clean_gitlab_redis_shared_sta
|
|||
|
||||
subject(:counter) { described_class.new(counter_record, attribute) }
|
||||
|
||||
let(:counter_record) { create(:project_statistics) }
|
||||
let_it_be(:counter_record) { create(:project_statistics) }
|
||||
let(:attribute) { :build_artifacts_size }
|
||||
|
||||
describe '#get' do
|
||||
|
|
@ -25,48 +25,53 @@ RSpec.describe Gitlab::Counters::BufferedCounter, :clean_gitlab_redis_shared_sta
|
|||
end
|
||||
|
||||
describe '#increment' do
|
||||
it 'sets a new key by the given value' do
|
||||
counter.increment(123)
|
||||
let(:increment) { Gitlab::Counters::Increment.new(amount: 123) }
|
||||
let(:other_increment) { Gitlab::Counters::Increment.new(amount: 100) }
|
||||
|
||||
expect(counter.get).to eq(123)
|
||||
it 'sets a new key by the given value' do
|
||||
counter.increment(increment)
|
||||
|
||||
expect(counter.get).to eq(increment.amount)
|
||||
end
|
||||
|
||||
it 'increments an existing key by the given value' do
|
||||
counter.increment(100)
|
||||
counter.increment(123)
|
||||
counter.increment(other_increment)
|
||||
counter.increment(increment)
|
||||
|
||||
expect(counter.get).to eq(100 + 123)
|
||||
expect(counter.get).to eq(other_increment.amount + increment.amount)
|
||||
end
|
||||
|
||||
it 'returns the new value' do
|
||||
counter.increment(123)
|
||||
it 'returns the value of the key after the increment' do
|
||||
counter.increment(increment)
|
||||
result = counter.increment(other_increment)
|
||||
|
||||
expect(counter.increment(23)).to eq(146)
|
||||
expect(result).to eq(increment.amount + other_increment.amount)
|
||||
end
|
||||
|
||||
it 'schedules a worker to commit the counter into database' do
|
||||
expect(FlushCounterIncrementsWorker).to receive(:perform_in)
|
||||
.with(described_class::WORKER_DELAY, counter_record.class.to_s, counter_record.id, attribute)
|
||||
|
||||
counter.increment(123)
|
||||
counter.increment(increment)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bulk_increment' do
|
||||
let(:increments) { [123, 456] }
|
||||
let(:other_increment) { Gitlab::Counters::Increment.new(amount: 1) }
|
||||
let(:increments) { [Gitlab::Counters::Increment.new(amount: 123), Gitlab::Counters::Increment.new(amount: 456)] }
|
||||
|
||||
it 'increments the key by the given values' do
|
||||
counter.bulk_increment(increments)
|
||||
|
||||
expect(counter.get).to eq(increments.sum)
|
||||
expect(counter.get).to eq(increments.sum(&:amount))
|
||||
end
|
||||
|
||||
it 'returns the value of the key after the increment' do
|
||||
counter.increment(100)
|
||||
counter.increment(other_increment)
|
||||
|
||||
result = counter.bulk_increment(increments)
|
||||
|
||||
expect(result).to eq(100 + increments.sum)
|
||||
expect(result).to eq(other_increment.amount + increments.sum(&:amount))
|
||||
end
|
||||
|
||||
it 'schedules a worker to commit the counter into database' do
|
||||
|
|
@ -78,10 +83,12 @@ RSpec.describe Gitlab::Counters::BufferedCounter, :clean_gitlab_redis_shared_sta
|
|||
end
|
||||
|
||||
describe '#reset!' do
|
||||
let(:increment) { Gitlab::Counters::Increment.new(amount: 123) }
|
||||
|
||||
before do
|
||||
allow(counter_record).to receive(:update!)
|
||||
|
||||
counter.increment(123)
|
||||
counter.increment(increment)
|
||||
end
|
||||
|
||||
it 'removes the key from Redis' do
|
||||
|
|
@ -113,7 +120,7 @@ RSpec.describe Gitlab::Counters::BufferedCounter, :clean_gitlab_redis_shared_sta
|
|||
end
|
||||
|
||||
context 'when there is an amount to commit' do
|
||||
let(:increments) { [10, -3] }
|
||||
let(:increments) { [10, -3].map { |amt| Gitlab::Counters::Increment.new(amount: amt) } }
|
||||
|
||||
before do
|
||||
increments.each { |i| counter.increment(i) }
|
||||
|
|
@ -121,7 +128,7 @@ RSpec.describe Gitlab::Counters::BufferedCounter, :clean_gitlab_redis_shared_sta
|
|||
|
||||
it 'commits the increment into the database' do
|
||||
expect { counter.commit_increment! }
|
||||
.to change { counter_record.reset.read_attribute(attribute) }.by(increments.sum)
|
||||
.to change { counter_record.reset.read_attribute(attribute) }.by(increments.sum(&:amount))
|
||||
end
|
||||
|
||||
it 'removes the increment entry from Redis' do
|
||||
|
|
@ -196,7 +203,7 @@ RSpec.describe Gitlab::Counters::BufferedCounter, :clean_gitlab_redis_shared_sta
|
|||
|
||||
context 'when there are increments to flush' do
|
||||
before do
|
||||
counter.increment(10)
|
||||
counter.increment(Gitlab::Counters::Increment.new(amount: 10))
|
||||
end
|
||||
|
||||
it 'executes the callbacks' do
|
||||
|
|
|
|||
|
|
@ -7,38 +7,41 @@ RSpec.describe Gitlab::Counters::LegacyCounter do
|
|||
|
||||
let(:counter_record) { create(:project_statistics) }
|
||||
let(:attribute) { :snippets_size }
|
||||
let(:amount) { 123 }
|
||||
|
||||
let(:increment) { Gitlab::Counters::Increment.new(amount: 123) }
|
||||
let(:other_increment) { Gitlab::Counters::Increment.new(amount: 100) }
|
||||
|
||||
describe '#increment' do
|
||||
it 'increments the attribute in the counter record' do
|
||||
expect { counter.increment(amount) }.to change { counter_record.reload.method(attribute).call }.by(amount)
|
||||
expect { counter.increment(increment) }
|
||||
.to change { counter_record.reload.method(attribute).call }.by(increment.amount)
|
||||
end
|
||||
|
||||
it 'returns the value after the increment' do
|
||||
counter.increment(100)
|
||||
counter.increment(other_increment)
|
||||
|
||||
expect(counter.increment(amount)).to eq(100 + amount)
|
||||
expect(counter.increment(increment)).to eq(other_increment.amount + increment.amount)
|
||||
end
|
||||
|
||||
it 'executes after counter_record after commit callback' do
|
||||
expect(counter_record).to receive(:execute_after_commit_callbacks).and_call_original
|
||||
|
||||
counter.increment(amount)
|
||||
counter.increment(increment)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#bulk_increment' do
|
||||
let(:increments) { [123, 456] }
|
||||
let(:increments) { [Gitlab::Counters::Increment.new(amount: 123), Gitlab::Counters::Increment.new(amount: 456)] }
|
||||
|
||||
it 'increments the attribute in the counter record' do
|
||||
expect { counter.bulk_increment(increments) }
|
||||
.to change { counter_record.reload.method(attribute).call }.by(increments.sum)
|
||||
.to change { counter_record.reload.method(attribute).call }.by(increments.sum(&:amount))
|
||||
end
|
||||
|
||||
it 'returns the value after the increment' do
|
||||
counter.increment(100)
|
||||
counter.increment(other_increment)
|
||||
|
||||
expect(counter.bulk_increment(increments)).to eq(100 + increments.sum)
|
||||
expect(counter.bulk_increment(increments)).to eq(other_increment.amount + increments.sum(&:amount))
|
||||
end
|
||||
|
||||
it 'executes after counter_record after commit callback' do
|
||||
|
|
|
|||
|
|
@ -664,6 +664,7 @@ project:
|
|||
- pipeline_metadata
|
||||
- disable_download_button
|
||||
- dependency_list_exports
|
||||
- sbom_occurrences
|
||||
award_emoji:
|
||||
- awardable
|
||||
- user
|
||||
|
|
|
|||
|
|
@ -723,7 +723,7 @@ RSpec.describe Ci::JobArtifact do
|
|||
|
||||
it 'updates project statistics' do
|
||||
expect(ProjectStatistics).to receive(:bulk_increment_statistic).once
|
||||
.with(project, :build_artifacts_size, [-job_artifact.file.size])
|
||||
.with(project, :build_artifacts_size, [have_attributes(amount: -job_artifact.file.size)])
|
||||
|
||||
pipeline.destroy!
|
||||
end
|
||||
|
|
|
|||
|
|
@ -63,6 +63,82 @@ RSpec.describe Noteable do
|
|||
end
|
||||
end
|
||||
|
||||
# rubocop:disable RSpec/MultipleMemoizedHelpers
|
||||
describe '#commenters' do
|
||||
shared_examples 'commenters' do
|
||||
it 'does not automatically include the noteable author' do
|
||||
expect(commenters).not_to include(noteable.author)
|
||||
end
|
||||
|
||||
context 'with no user' do
|
||||
it 'contains a distinct list of non-internal note authors' do
|
||||
expect(commenters).to contain_exactly(commenter, another_commenter)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non project member' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
it 'contains a distinct list of non-internal note authors' do
|
||||
expect(commenters).to contain_exactly(commenter, another_commenter)
|
||||
end
|
||||
|
||||
it 'does not include a commenter from another noteable' do
|
||||
expect(commenters).not_to include(other_noteable_commenter)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let_it_be(:commenter) { create(:user) }
|
||||
let_it_be(:another_commenter) { create(:user) }
|
||||
let_it_be(:internal_commenter) { create(:user) }
|
||||
let_it_be(:other_noteable_commenter) { create(:user) }
|
||||
|
||||
let(:current_user) {}
|
||||
let(:commenters) { noteable.commenters(user: current_user) }
|
||||
|
||||
let!(:comments) { create_list(:note, 2, author: commenter, noteable: noteable, project: noteable.project) }
|
||||
let!(:more_comments) { create_list(:note, 2, author: another_commenter, noteable: noteable, project: noteable.project) }
|
||||
|
||||
context 'when noteable is an issue' do
|
||||
let(:noteable) { create(:issue) }
|
||||
|
||||
let!(:internal_comments) { create_list(:note, 2, author: internal_commenter, noteable: noteable, project: noteable.project, internal: true) }
|
||||
let!(:other_noteable_comments) { create_list(:note, 2, author: other_noteable_commenter, noteable: create(:issue, project: noteable.project), project: noteable.project) }
|
||||
|
||||
it_behaves_like 'commenters'
|
||||
|
||||
context 'with reporter' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
before do
|
||||
noteable.project.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it 'contains a distinct list of non-internal note authors' do
|
||||
expect(commenters).to contain_exactly(commenter, another_commenter, internal_commenter)
|
||||
end
|
||||
|
||||
context 'with noteable author' do
|
||||
let(:current_user) { noteable.author }
|
||||
|
||||
it 'contains a distinct list of non-internal note authors' do
|
||||
expect(commenters).to contain_exactly(commenter, another_commenter, internal_commenter)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when noteable is a merge request' do
|
||||
let(:noteable) { create(:merge_request) }
|
||||
|
||||
let!(:other_noteable_comments) { create_list(:note, 2, author: other_noteable_commenter, noteable: create(:merge_request, source_project: noteable.project, source_branch: 'feat123'), project: noteable.project) }
|
||||
|
||||
it_behaves_like 'commenters'
|
||||
end
|
||||
end
|
||||
# rubocop:enable RSpec/MultipleMemoizedHelpers
|
||||
|
||||
describe '#discussion_ids_relation' do
|
||||
it 'returns ordered discussion_ids' do
|
||||
discussion_ids = subject.discussion_ids_relation.pluck(:discussion_id)
|
||||
|
|
|
|||
|
|
@ -713,7 +713,7 @@ RSpec.describe Packages::Package, type: :model do
|
|||
subject(:destroy!) { package.destroy! }
|
||||
|
||||
it 'updates the project statistics' do
|
||||
expect(project_statistics).to receive(:increment_counter).with(:packages_size, -package_file.size)
|
||||
expect(project_statistics).to receive(:increment_counter).with(:packages_size, have_attributes(amount: -package_file.size))
|
||||
|
||||
destroy!
|
||||
end
|
||||
|
|
|
|||
|
|
@ -456,23 +456,25 @@ RSpec.describe ProjectStatistics do
|
|||
|
||||
describe '.increment_statistic' do
|
||||
shared_examples 'a statistic that increases storage_size synchronously' do
|
||||
let(:increment) { Gitlab::Counters::Increment.new(amount: 13) }
|
||||
|
||||
it 'increases the statistic by that amount' do
|
||||
expect { described_class.increment_statistic(project, stat, 13) }
|
||||
expect { described_class.increment_statistic(project, stat, increment) }
|
||||
.to change { statistics.reload.send(stat) || 0 }
|
||||
.by(13)
|
||||
.by(increment.amount)
|
||||
end
|
||||
|
||||
it 'increases also storage size by that amount' do
|
||||
expect { described_class.increment_statistic(project, stat, 20) }
|
||||
expect { described_class.increment_statistic(project, stat, increment) }
|
||||
.to change { statistics.reload.storage_size }
|
||||
.by(20)
|
||||
.by(increment.amount)
|
||||
end
|
||||
|
||||
it 'schedules a namespace aggregation worker' do
|
||||
expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async)
|
||||
.with(statistics.project.namespace.id)
|
||||
|
||||
described_class.increment_statistic(project, stat, 20)
|
||||
described_class.increment_statistic(project, stat, increment)
|
||||
end
|
||||
|
||||
context 'when the project is pending delete' do
|
||||
|
|
@ -481,20 +483,22 @@ RSpec.describe ProjectStatistics do
|
|||
end
|
||||
|
||||
it 'does not change the statistics' do
|
||||
expect { described_class.increment_statistic(project, stat, 13) }
|
||||
expect { described_class.increment_statistic(project, stat, increment) }
|
||||
.not_to change { statistics.reload.send(stat) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a statistic that increases storage_size asynchronously' do
|
||||
let(:increment) { Gitlab::Counters::Increment.new(amount: 13) }
|
||||
|
||||
it 'stores the increment temporarily in Redis', :clean_gitlab_redis_shared_state do
|
||||
described_class.increment_statistic(project, stat, 13)
|
||||
described_class.increment_statistic(project, stat, increment)
|
||||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
key = statistics.counter(stat).key
|
||||
increment = redis.get(key)
|
||||
expect(increment.to_i).to eq(13)
|
||||
value = redis.get(key)
|
||||
expect(value.to_i).to eq(increment.amount)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -504,9 +508,9 @@ RSpec.describe ProjectStatistics do
|
|||
.with(Gitlab::Counters::BufferedCounter::WORKER_DELAY, described_class.name, statistics.id, stat)
|
||||
.and_call_original
|
||||
|
||||
expect { described_class.increment_statistic(project, stat, 20) }
|
||||
.to change { statistics.reload.send(stat) }.by(20)
|
||||
.and change { statistics.reload.send(:storage_size) }.by(20)
|
||||
expect { described_class.increment_statistic(project, stat, increment) }
|
||||
.to change { statistics.reload.send(stat) }.by(increment.amount)
|
||||
.and change { statistics.reload.send(:storage_size) }.by(increment.amount)
|
||||
end
|
||||
|
||||
context 'when the project is pending delete' do
|
||||
|
|
@ -515,7 +519,7 @@ RSpec.describe ProjectStatistics do
|
|||
end
|
||||
|
||||
it 'does not change the statistics' do
|
||||
expect { described_class.increment_statistic(project, stat, 13) }
|
||||
expect { described_class.increment_statistic(project, stat, increment) }
|
||||
.not_to change { [statistics.reload.send(stat), statistics.reload.send(:storage_size)] }
|
||||
end
|
||||
end
|
||||
|
|
@ -540,9 +544,11 @@ RSpec.describe ProjectStatistics do
|
|||
end
|
||||
|
||||
context 'when the amount is 0' do
|
||||
let(:increment) { Gitlab::Counters::Increment.new(amount: 0) }
|
||||
|
||||
it 'does not execute a query' do
|
||||
project
|
||||
expect { described_class.increment_statistic(project, :build_artifacts_size, 0) }
|
||||
expect { described_class.increment_statistic(project, :build_artifacts_size, increment) }
|
||||
.not_to exceed_query_limit(0)
|
||||
end
|
||||
end
|
||||
|
|
@ -556,8 +562,8 @@ RSpec.describe ProjectStatistics do
|
|||
end
|
||||
|
||||
describe '.bulk_increment_statistic' do
|
||||
let(:increments) { [10, 3] }
|
||||
let(:total_amount) { increments.sum }
|
||||
let(:increments) { [10, 3].map { |amount| Gitlab::Counters::Increment.new(amount: amount) } }
|
||||
let(:total_amount) { increments.sum(&:amount) }
|
||||
|
||||
shared_examples 'a statistic that increases storage_size synchronously' do
|
||||
it 'increases the statistic by that amount' do
|
||||
|
|
@ -636,7 +642,9 @@ RSpec.describe ProjectStatistics do
|
|||
end
|
||||
|
||||
it 'calls increment_statistic on once with the sum of the increments' do
|
||||
expect(statistics).to receive(:increment_statistic).with(stat, increments.sum).and_call_original
|
||||
total_amount = increments.sum(&:amount)
|
||||
expect(statistics)
|
||||
.to receive(:increment_statistic).with(stat, have_attributes(amount: total_amount)).and_call_original
|
||||
|
||||
described_class.bulk_increment_statistic(project, stat, increments)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ RSpec.describe Projects::BuildArtifactsSizeRefresh, type: :model do
|
|||
|
||||
before do
|
||||
stats = create(:project_statistics, project: refresh.project, build_artifacts_size: 120)
|
||||
stats.increment_counter(:build_artifacts_size, 30)
|
||||
stats.increment_counter(:build_artifacts_size, Gitlab::Counters::Increment.new(amount: 30))
|
||||
end
|
||||
|
||||
it 'transitions the state to running' do
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Ci::JobArtifacts::DestroyAssociationsService do
|
||||
let_it_be(:artifact_1, refind: true) { create(:ci_job_artifact, :zip) }
|
||||
let_it_be(:artifact_2, refind: true) { create(:ci_job_artifact, :zip) }
|
||||
let_it_be(:artifact_3, refind: true) { create(:ci_job_artifact, :zip, project: artifact_1.project) }
|
||||
let_it_be(:project_1) { create(:project) }
|
||||
let_it_be(:project_2) { create(:project) }
|
||||
|
||||
let_it_be(:artifact_1, refind: true) { create(:ci_job_artifact, :zip, project: project_1) }
|
||||
let_it_be(:artifact_2, refind: true) { create(:ci_job_artifact, :zip, project: project_2) }
|
||||
let_it_be(:artifact_3, refind: true) { create(:ci_job_artifact, :zip, project: project_1) }
|
||||
|
||||
let(:artifacts) { Ci::JobArtifact.where(id: [artifact_1.id, artifact_2.id, artifact_3.id]) }
|
||||
let(:service) { described_class.new(artifacts) }
|
||||
|
|
@ -35,10 +38,16 @@ RSpec.describe Ci::JobArtifacts::DestroyAssociationsService do
|
|||
end
|
||||
|
||||
it 'updates project statistics' do
|
||||
project1_increments = [
|
||||
have_attributes(amount: -artifact_1.size, ref: artifact_1.id),
|
||||
have_attributes(amount: -artifact_3.size, ref: artifact_3.id)
|
||||
]
|
||||
project2_increments = [have_attributes(amount: -artifact_2.size, ref: artifact_2.id)]
|
||||
|
||||
expect(ProjectStatistics).to receive(:bulk_increment_statistic).once
|
||||
.with(artifact_1.project, :build_artifacts_size, match_array([-artifact_1.size, -artifact_3.size]))
|
||||
.with(project_1, :build_artifacts_size, match_array(project1_increments))
|
||||
expect(ProjectStatistics).to receive(:bulk_increment_statistic).once
|
||||
.with(artifact_2.project, :build_artifacts_size, match_array([-artifact_2.size]))
|
||||
.with(project_2, :build_artifacts_size, match_array(project2_increments))
|
||||
|
||||
service.update_statistics
|
||||
end
|
||||
|
|
|
|||
|
|
@ -208,33 +208,53 @@ RSpec.describe Ci::JobArtifacts::DestroyBatchService do
|
|||
end
|
||||
|
||||
context 'ProjectStatistics', :sidekiq_inline do
|
||||
let(:artifact_with_file) { create(:ci_job_artifact, :zip) }
|
||||
let(:artifact_with_file_2) { create(:ci_job_artifact, :zip, project: artifact_with_file.project) }
|
||||
let(:artifact_without_file) { create(:ci_job_artifact) }
|
||||
let(:affected_statistics) { artifact_with_file.project.statistics }
|
||||
let(:unaffected_statistics) { artifact_without_file.project.statistics }
|
||||
let_it_be(:project_1) { create(:project) }
|
||||
let_it_be(:project_2) { create(:project) }
|
||||
|
||||
let(:artifact_with_file) { create(:ci_job_artifact, :zip, project: project_1) }
|
||||
let(:artifact_with_file_2) { create(:ci_job_artifact, :zip, project: project_1) }
|
||||
let(:artifact_without_file) { create(:ci_job_artifact, project: project_2) }
|
||||
let!(:artifacts) { Ci::JobArtifact.where(id: [artifact_with_file.id, artifact_without_file.id, artifact_with_file_2.id]) }
|
||||
|
||||
it 'updates project statistics by the relevant amount' do
|
||||
expected_amount = -(artifact_with_file.size + artifact_with_file_2.size)
|
||||
|
||||
expect { execute }
|
||||
.to change { affected_statistics.reload.build_artifacts_size }.by(expected_amount)
|
||||
.and change { unaffected_statistics.reload.build_artifacts_size }.by(0)
|
||||
.to change { project_1.statistics.reload.build_artifacts_size }.by(expected_amount)
|
||||
.and change { project_2.statistics.reload.build_artifacts_size }.by(0)
|
||||
end
|
||||
|
||||
it 'increments project statistics with artifact size as amount and job artifact id as ref' do
|
||||
project_1_increments = [
|
||||
have_attributes(amount: -artifact_with_file.size, ref: artifact_with_file.id),
|
||||
have_attributes(amount: -artifact_with_file_2.file.size, ref: artifact_with_file_2.id)
|
||||
]
|
||||
project_2_increments = [have_attributes(amount: 0, ref: artifact_without_file.id)]
|
||||
|
||||
expect(ProjectStatistics).to receive(:bulk_increment_statistic).with(project_1, :build_artifacts_size, match_array(project_1_increments))
|
||||
expect(ProjectStatistics).to receive(:bulk_increment_statistic).with(project_2, :build_artifacts_size, match_array(project_2_increments))
|
||||
|
||||
execute
|
||||
end
|
||||
|
||||
context 'with update_stats: false' do
|
||||
subject(:execute) { service.execute(update_stats: false) }
|
||||
|
||||
it 'does not update project statistics' do
|
||||
expect { execute }.not_to change { [affected_statistics.reload.build_artifacts_size, unaffected_statistics.reload.build_artifacts_size] }
|
||||
expect { execute }.not_to change { [project_1.statistics.reload.build_artifacts_size, project_2.statistics.reload.build_artifacts_size] }
|
||||
end
|
||||
|
||||
it 'returns statistic updates per project' do
|
||||
project_1_updates = [
|
||||
have_attributes(amount: -artifact_with_file.size, ref: artifact_with_file.id),
|
||||
have_attributes(amount: -artifact_with_file_2.file.size, ref: artifact_with_file_2.id)
|
||||
]
|
||||
project_2_updates = [have_attributes(amount: 0, ref: artifact_without_file.id)]
|
||||
|
||||
expected_updates = {
|
||||
statistics_updates: {
|
||||
artifact_with_file.project => match_array([-artifact_with_file.file.size, -artifact_with_file_2.file.size]),
|
||||
artifact_without_file.project => [0]
|
||||
project_1 => match_array(project_1_updates),
|
||||
project_2 => project_2_updates
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ RSpec.describe Projects::RefreshBuildArtifactsSizeStatisticsService, :clean_gitl
|
|||
|
||||
let(:now) { Time.zone.now }
|
||||
let(:statistics) { project.statistics }
|
||||
let(:increment) { Gitlab::Counters::Increment.new(amount: 30) }
|
||||
|
||||
around do |example|
|
||||
freeze_time { example.run }
|
||||
|
|
@ -38,7 +39,7 @@ RSpec.describe Projects::RefreshBuildArtifactsSizeStatisticsService, :clean_gitl
|
|||
stub_const("#{described_class}::BATCH_SIZE", 3)
|
||||
|
||||
stats = create(:project_statistics, project: project, build_artifacts_size: 120)
|
||||
stats.increment_counter(:build_artifacts_size, 30)
|
||||
stats.increment_counter(:build_artifacts_size, increment)
|
||||
end
|
||||
|
||||
it 'resets the build artifacts size stats' do
|
||||
|
|
|
|||
|
|
@ -221,3 +221,18 @@ RSpec.shared_context 'group navbar structure' do
|
|||
]
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.shared_context 'dashboard navbar structure' do
|
||||
let(:structure) do
|
||||
[
|
||||
{
|
||||
nav_item: "Your work",
|
||||
nav_sub_items: []
|
||||
},
|
||||
{
|
||||
nav_item: _("Projects"),
|
||||
nav_sub_items: []
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.shared_examples "a dashboard page with sidebar" do |page_path, menu_label|
|
||||
before do
|
||||
sign_in(user)
|
||||
visit send(page_path)
|
||||
end
|
||||
|
||||
let(:sidebar_css) { "aside.nav-sidebar[aria-label=\"Your work\"]" }
|
||||
let(:active_menu_item_css) { "li.active[data-track-label=\"#{menu_label}_menu\"]" }
|
||||
|
||||
it "shows the \"Your work\" sidebar" do
|
||||
expect(page).to have_css(sidebar_css)
|
||||
end
|
||||
|
||||
it "shows the correct sidebar menu item as active" do
|
||||
within(sidebar_css) do
|
||||
expect(page).to have_css(active_menu_item_css)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -14,13 +14,14 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
|
|||
counter_attributes.each do |attribute|
|
||||
describe attribute do
|
||||
describe '#increment_counter', :redis do
|
||||
let(:increment) { 10 }
|
||||
let(:amount) { 10 }
|
||||
let(:increment) { Gitlab::Counters::Increment.new(amount: amount) }
|
||||
let(:counter_key) { model.counter(attribute).key }
|
||||
|
||||
subject { model.increment_counter(attribute, increment) }
|
||||
|
||||
context 'when attribute is a counter attribute' do
|
||||
where(:increment) { [10, -3] }
|
||||
where(:amount) { [10, -3] }
|
||||
|
||||
with_them do
|
||||
it 'increments the counter in Redis and logs it' do
|
||||
|
|
@ -29,8 +30,8 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
|
|||
message: 'Increment counter attribute',
|
||||
attribute: attribute,
|
||||
project_id: model.project_id,
|
||||
increment: increment,
|
||||
new_counter_value: 0 + increment,
|
||||
increment: amount,
|
||||
new_counter_value: 0 + amount,
|
||||
current_db_value: model.read_attribute(attribute),
|
||||
'correlation_id' => an_instance_of(String),
|
||||
'meta.feature_category' => 'test',
|
||||
|
|
@ -42,7 +43,7 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
|
|||
|
||||
Gitlab::Redis::SharedState.with do |redis|
|
||||
counter = redis.get(counter_key)
|
||||
expect(counter).to eq(increment.to_s)
|
||||
expect(counter).to eq(amount.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -59,8 +60,8 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
|
|||
end
|
||||
end
|
||||
|
||||
context 'when increment is 0' do
|
||||
let(:increment) { 0 }
|
||||
context 'when increment amount is 0' do
|
||||
let(:amount) { 0 }
|
||||
|
||||
it 'does nothing' do
|
||||
expect(FlushCounterIncrementsWorker).not_to receive(:perform_in)
|
||||
|
|
@ -73,8 +74,8 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
|
|||
end
|
||||
|
||||
describe '#bulk_increment_counter', :redis do
|
||||
let(:increments) { [10, 5] }
|
||||
let(:total_amount) { increments.sum }
|
||||
let(:increments) { [Gitlab::Counters::Increment.new(amount: 10), Gitlab::Counters::Increment.new(amount: 5)] }
|
||||
let(:total_amount) { increments.sum(&:amount) }
|
||||
let(:counter_key) { model.counter(attribute).key }
|
||||
|
||||
subject { model.bulk_increment_counter(attribute, increments) }
|
||||
|
|
@ -122,10 +123,11 @@ RSpec.shared_examples_for CounterAttribute do |counter_attributes|
|
|||
describe '#reset_counter!' do
|
||||
let(:attribute) { counter_attributes.first }
|
||||
let(:counter_key) { model.counter(attribute).key }
|
||||
let(:increment) { Gitlab::Counters::Increment.new(amount: 10) }
|
||||
|
||||
before do
|
||||
model.update!(attribute => 123)
|
||||
model.increment_counter(attribute, 10)
|
||||
model.increment_counter(attribute, increment)
|
||||
end
|
||||
|
||||
subject { model.reset_counter!(attribute) }
|
||||
|
|
|
|||
|
|
@ -60,8 +60,11 @@ RSpec.shared_examples 'UpdateProjectStatistics' do |with_counter_attribute|
|
|||
end
|
||||
|
||||
it 'stores pending increments for async update' do
|
||||
expected_increment = have_attributes(amount: delta, ref: subject.id)
|
||||
|
||||
expect(ProjectStatistics)
|
||||
.to receive(:increment_statistic)
|
||||
.with(project, project_statistics_name, expected_increment)
|
||||
.and_call_original
|
||||
|
||||
subject.write_attribute(statistic_attribute, read_attribute + delta)
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'gitlab-dangerfiles'
|
||||
require 'gitlab/dangerfiles/spec_helper'
|
||||
require_relative '../../../tooling/danger/user_types'
|
||||
|
||||
RSpec.describe Tooling::Danger::UserTypes, feature_category: :subscription_cost_management do
|
||||
include_context 'with dangerfile'
|
||||
|
||||
let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
|
||||
let(:user_types) { fake_danger.new(helper: fake_helper) }
|
||||
|
||||
describe 'changed files' do
|
||||
subject(:bot_user_types_change_warning) { user_types.bot_user_types_change_warning }
|
||||
|
||||
before do
|
||||
allow(fake_helper).to receive(:modified_files).and_return(modified_files)
|
||||
allow(fake_helper).to receive(:changed_lines).and_return(changed_lines)
|
||||
end
|
||||
|
||||
context 'when has_user_type.rb file is not impacted' do
|
||||
let(:modified_files) { ['app/models/concerns/importable.rb'] }
|
||||
let(:changed_lines) { ['+ANY_CHANGES'] }
|
||||
|
||||
it "doesn't add any warnings" do
|
||||
expect(user_types).not_to receive(:warn)
|
||||
|
||||
bot_user_types_change_warning
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the has_user_type.rb file is impacted' do
|
||||
let(:modified_files) { ['app/models/concerns/has_user_type.rb'] }
|
||||
|
||||
context 'with BOT_USER_TYPES changes' do
|
||||
let(:changed_lines) { ['+BOT_USER_TYPES'] }
|
||||
|
||||
it 'adds warning' do
|
||||
expect(user_types).to receive(:warn).with(described_class::BOT_USER_TYPES_CHANGED_WARNING)
|
||||
|
||||
bot_user_types_change_warning
|
||||
end
|
||||
end
|
||||
|
||||
context 'without BOT_USER_TYPES changes' do
|
||||
let(:changed_lines) { ['+OTHER_CHANGES'] }
|
||||
|
||||
it "doesn't add any warnings" do
|
||||
expect(user_types).not_to receive(:warn)
|
||||
|
||||
bot_user_types_change_warning
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -497,6 +497,18 @@ RSpec.describe ObjectStorage do
|
|||
|
||||
subject { uploader_class.workhorse_authorize(has_length: has_length, maximum_size: maximum_size) }
|
||||
|
||||
context 'when FIPS is enabled', :fips_mode do
|
||||
it 'response enables FIPS' do
|
||||
expect(subject[:UploadHashFunctions]).to match_array(%w[sha1 sha256 sha512])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when FIPS is disabled' do
|
||||
it 'response disables FIPS' do
|
||||
expect(subject[:UploadHashFunctions]).to be nil
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'returns the maximum size given' do
|
||||
it "returns temporary path" do
|
||||
expect(subject[:MaximumSize]).to eq(maximum_size)
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Tooling
|
||||
module Danger
|
||||
module UserTypes
|
||||
FILE_PATH = "app/models/concerns/has_user_type.rb"
|
||||
BOT_USER_TYPES_CHANGE_INDICATOR_REGEX = %r{BOT_USER_TYPES}.freeze
|
||||
BOT_USER_TYPES_CHANGED_WARNING = <<~MSG
|
||||
You are changing BOT_USER_TYPES in `app/models/concerns/has_user_type.rb`.
|
||||
If you are adding or removing new bots, remember to update the `active_billable_users` index with the new value.
|
||||
If the bot is not billable, remember to make sure that it's not counted as a billable user.
|
||||
MSG
|
||||
|
||||
def bot_user_types_change_warning
|
||||
return unless impacted?
|
||||
|
||||
warn BOT_USER_TYPES_CHANGED_WARNING if bot_user_types_impacted?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def impacted?
|
||||
helper.modified_files.include?(FILE_PATH)
|
||||
end
|
||||
|
||||
def bot_user_types_impacted?
|
||||
helper.changed_lines(FILE_PATH).any? { |change| change =~ BOT_USER_TYPES_CHANGE_INDICATOR_REGEX }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue