Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-12-23 15:09:45 +00:00
parent 23634aa773
commit 99c1dfd5e3
109 changed files with 813 additions and 421 deletions

View File

@ -65,7 +65,6 @@ docs-lint markdown:
- .default-retry
- .docs:rules:docs-lint
- .docs-markdown-lint-image
- .yarn-cache
stage: lint
needs: []
script:

View File

@ -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:

View File

@ -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'

View File

@ -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"},

View File

@ -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)

View File

@ -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

View File

@ -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;
}
}

View File

@ -2,7 +2,7 @@
module GitlabRecaptcha
extend ActiveSupport::Concern
include Recaptcha::Verify
include Recaptcha::Adapters::ControllerMethods
include RecaptchaHelper
def load_recaptcha

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -0,0 +1 @@
= render partial: 'shared/nav/sidebar', object: Sidebars::YourWork::Panel.new(Sidebars::Context.new(current_user: current_user, container: nil))

View File

@ -0,0 +1,5 @@
%li.context-header
= link_to root_url do
%span.icon-container
= sprite_icon "work"
%span.sidebar-context-title= _("Your work")

View File

@ -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

View File

@ -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'
)

View File

@ -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

View File

@ -1,3 +0,0 @@
# frozen_string_literal: true
user_types.bot_user_types_change_warning

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
5bc41c2430a033da7aa063e5646941428bb01cbf99aafed4acc80b4f9aa2f650

View File

@ -0,0 +1 @@
5a7f509173cf10ab512935db0dd65ab9ed347539a6448e2922ea603db418b1df

View File

@ -0,0 +1 @@
51f9c66f46063a9ad6979f2a50b0d963d93c007b25bde2dedf941317317ef077

View File

@ -0,0 +1 @@
de8a5fae011e67ff3b8da9c73f0c19a93a2c534764d81bc72e3058627b5ab6b5

View File

@ -0,0 +1 @@
a842c4aae88386fc5fdeb7f08c0a2ba14780b651801e7dae28c974af58aa946c

View File

@ -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);

View File

@ -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/';

View File

@ -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'],

View File

@ -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'],

View File

@ -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'],

View File

@ -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)) |

View File

@ -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).

View File

@ -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,

7
lib/gitlab/counters.rb Normal file
View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module Gitlab
module Counters
Increment = Struct.new(:amount, :ref, keyword_init: true)
end
end

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -48431,6 +48431,9 @@ msgstr ""
msgid "Your username is %{username}."
msgstr ""
msgid "Your work"
msgstr ""
msgid "Youre 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 ""

View File

@ -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

View File

@ -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|

View File

@ -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

View File

@ -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

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)) }

View File

@ -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)

View File

@ -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

View File

@ -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(', ') }

View File

@ -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

View File

@ -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

View File

@ -664,6 +664,7 @@ project:
- pipeline_metadata
- disable_download_button
- dependency_list_exports
- sbom_occurrences
award_emoji:
- awardable
- user

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) }

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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