Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-04-15 15:09:11 +00:00
parent 69b0ff9002
commit 10130901f1
119 changed files with 2232 additions and 645 deletions

View File

@ -1 +1 @@
e505e98295e765f945719f289125a98e671a7e19
6904387a86815c80988d87f23af9d3fe1e2d4c85

View File

@ -1 +1 @@
1.37.0
1.38.0

View File

@ -44,6 +44,7 @@ module ServiceParams
# make those event names plural as special case.
:issues_events,
:issues_url,
:jenkins_url,
:jira_issue_transition_automatic,
:jira_issue_transition_id,
:manual_configuration,
@ -56,6 +57,7 @@ module ServiceParams
:password,
:priority,
:project_key,
:project_name,
:project_url,
:recipients,
:restrict_to_branch,

View File

@ -12,9 +12,6 @@ class Projects::BranchesController < Projects::ApplicationController
# Support legacy URLs
before_action :redirect_for_legacy_index_sort_or_search, only: [:index]
before_action :limit_diverging_commit_counts!, only: [:diverging_commit_counts]
before_action do
push_frontend_feature_flag(:gldropdown_branches, default_enabled: :yaml)
end
feature_category :source_code_management

View File

@ -21,7 +21,7 @@ module Resolvers
# We fetch blobs from Gitaly efficiently but it still scales O(N) with the
# number of paths being fetched, so apply a scaling limit to that.
def self.resolver_complexity(args, child_complexity:)
super + args.fetch(:paths, []).size
super + (args[:paths] || []).size
end
def resolve(paths:, ref:)

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
module Resolvers
module Ci
class TestSuiteResolver < BaseResolver
include Gitlab::Graphql::Authorize::AuthorizeResource
type ::Types::Ci::TestSuiteType, null: true
authorize :read_build
authorizes_object!
alias_method :pipeline, :object
argument :build_ids, [GraphQL::ID_TYPE],
required: true,
description: 'IDs of the builds used to run the test suite.'
def resolve(build_ids:)
builds = pipeline.latest_builds.id_in(build_ids).presence
return unless builds
TestSuiteSerializer
.new(project: pipeline.project, current_user: @current_user)
.represent(load_test_suite_data(builds), details: true)
end
private
def load_test_suite_data(builds)
suite = builds.sum do |build|
build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
end
Gitlab::Ci::Reports::TestFailureHistory.new(suite.failed.values, pipeline.project).load!
suite
end
end
end
end

View File

@ -128,6 +128,12 @@ module Types
description: 'Summary of the test report generated by the pipeline.',
resolver: Resolvers::Ci::TestReportSummaryResolver
field :test_suite,
Types::Ci::TestSuiteType,
null: true,
description: 'A specific test suite in a pipeline test report.',
resolver: Resolvers::Ci::TestSuiteResolver
def detailed_status
object.detailed_status(current_user)
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class RecentFailuresType < BaseObject
graphql_name 'RecentFailures'
description 'Recent failure history of a test case.'
connection_type_class(Types::CountableConnectionType)
field :count, GraphQL::INT_TYPE, null: true,
description: 'Number of times the test case has failed in the past 14 days.'
field :base_branch, GraphQL::STRING_TYPE, null: true,
description: 'Name of the base branch of the project.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Types
module Ci
class TestCaseStatusEnum < BaseEnum
graphql_name 'TestCaseStatus'
::Gitlab::Ci::Reports::TestCase::STATUS_TYPES.each do |status|
value status,
description: "Test case that has a status of #{status}.",
value: status
end
end
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class TestCaseType < BaseObject
graphql_name 'TestCase'
description 'Test case in pipeline test report.'
connection_type_class(Types::CountableConnectionType)
field :status, Types::Ci::TestCaseStatusEnum, null: true,
description: "Status of the test case (#{::Gitlab::Ci::Reports::TestCase::STATUS_TYPES.join(', ')})."
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the test case.'
field :classname, GraphQL::STRING_TYPE, null: true,
description: 'Classname of the test case.'
field :execution_time, GraphQL::FLOAT_TYPE, null: true,
description: 'Test case execution time in seconds.'
field :file, GraphQL::STRING_TYPE, null: true,
description: 'Path to the file of the test case.'
field :attachment_url, GraphQL::STRING_TYPE, null: true,
description: 'URL of the test case attachment file.'
field :system_output, GraphQL::STRING_TYPE, null: true,
description: 'System output of the test case.'
field :stack_trace, GraphQL::STRING_TYPE, null: true,
description: 'Stack trace of the test case.'
field :recent_failures, Types::Ci::RecentFailuresType, null: true,
description: 'Recent failure history of the test case on the base branch.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module Types
module Ci
# rubocop: disable Graphql/AuthorizeTypes
class TestSuiteType < BaseObject
graphql_name 'TestSuite'
description 'Test suite in a pipeline test report.'
connection_type_class(Types::CountableConnectionType)
field :name, GraphQL::STRING_TYPE, null: true,
description: 'Name of the test suite.'
field :total_time, GraphQL::FLOAT_TYPE, null: true,
description: 'Total duration of the tests in the test suite.'
field :total_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of the test cases in the test suite.'
field :success_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of test cases that succeeded in the test suite.'
field :failed_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of test cases that failed in the test suite.'
field :skipped_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of test cases that were skipped in the test suite.'
field :error_count, GraphQL::INT_TYPE, null: true,
description: 'Total number of test cases that had an error.'
field :suite_error, GraphQL::STRING_TYPE, null: true,
description: 'Test suite error message.'
field :test_cases, Types::Ci::TestCaseType.connection_type, null: true,
description: 'Test cases in the test suite.'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
module Types
module Repository
# rubocop: disable Graphql/AuthorizeTypes
# This is presented through `Repository` that has its own authorization
class BlobType < BaseObject
present_using BlobPresenter
graphql_name 'RepositoryBlob'
field :id, GraphQL::ID_TYPE, null: false,
description: 'ID of the blob.'
field :oid, GraphQL::STRING_TYPE, null: false, method: :id,
description: 'OID of the blob.'
field :path, GraphQL::STRING_TYPE, null: false,
description: 'Path of the blob.'
field :name, GraphQL::STRING_TYPE,
description: 'Blob name.',
null: true
field :mode, type: GraphQL::STRING_TYPE,
description: 'Blob mode.',
null: true
field :lfs_oid, GraphQL::STRING_TYPE, null: true,
calls_gitaly: true,
description: 'LFS OID of the blob.'
field :web_path, GraphQL::STRING_TYPE, null: true,
description: 'Web path of the blob.'
def lfs_oid
Gitlab::Graphql::Loaders::BatchLfsOidLoader.new(object.repository, object.id).find
end
end
end
end

View File

@ -14,7 +14,7 @@ module Types
description: 'Indicates a corresponding Git repository exists on disk.'
field :tree, Types::Tree::TreeType, null: true, resolver: Resolvers::TreeResolver, calls_gitaly: true,
description: 'Tree of the repository.'
field :blobs, Types::Tree::BlobType.connection_type, null: true, resolver: Resolvers::BlobsResolver, calls_gitaly: true,
field :blobs, Types::Repository::BlobType.connection_type, null: true, resolver: Resolvers::BlobsResolver, calls_gitaly: true,
description: 'Blobs contained within the repository'
field :branch_names, [GraphQL::STRING_TYPE], null: true, calls_gitaly: true,
complexity: 170, description: 'Names of branches available in this repository that match the search pattern.',

View File

@ -20,10 +20,6 @@ module BranchesHelper
end
end
end
def gldropdrown_branches_enabled?
Feature.enabled?(:gldropdown_branches, default_enabled: :yaml)
end
end
BranchesHelper.prepend_if_ee('EE::BranchesHelper')

View File

@ -34,39 +34,39 @@ module Ci
# and will be cleaned up with https://gitlab.com/gitlab-org/gitlab/-/issues/326299
def experiment_suggested_ci_templates
[
{ name: 'Android', logo: image_path('/assets/illustrations/logos/android.svg') },
{ name: 'Bash', logo: image_path('/assets/illustrations/logos/bash.svg') },
{ name: 'C++', logo: image_path('/assets/illustrations/logos/c_plus_plus.svg') },
{ name: 'Clojure', logo: image_path('/assets/illustrations/logos/clojure.svg') },
{ name: 'Composer', logo: image_path('/assets/illustrations/logos/composer.svg') },
{ name: 'Crystal', logo: image_path('/assets/illustrations/logos/crystal.svg') },
{ name: 'Dart', logo: image_path('/assets/illustrations/logos/dart.svg') },
{ name: 'Django', logo: image_path('/assets/illustrations/logos/django.svg') },
{ name: 'Docker', logo: image_path('/assets/illustrations/logos/docker.svg') },
{ name: 'Elixir', logo: image_path('/assets/illustrations/logos/elixir.svg') },
{ name: 'iOS-Fastlane', logo: image_path('/assets/illustrations/logos/fastlane.svg') },
{ name: 'Flutter', logo: image_path('/assets/illustrations/logos/flutter.svg') },
{ name: 'Go', logo: image_path('/assets/illustrations/logos/go_logo.svg') },
{ name: 'Gradle', logo: image_path('/assets/illustrations/logos/gradle.svg') },
{ name: 'Grails', logo: image_path('/assets/illustrations/logos/grails.svg') },
{ name: 'dotNET', logo: image_path('/assets/illustrations/logos/dotnet.svg') },
{ name: 'Rails', logo: image_path('/assets/illustrations/logos/rails.svg') },
{ name: 'Julia', logo: image_path('/assets/illustrations/logos/julia.svg') },
{ name: 'Laravel', logo: image_path('/assets/illustrations/logos/laravel.svg') },
{ name: 'Latex', logo: image_path('/assets/illustrations/logos/latex.svg') },
{ name: 'Maven', logo: image_path('/assets/illustrations/logos/maven.svg') },
{ name: 'Mono', logo: image_path('/assets/illustrations/logos/mono.svg') },
{ name: 'Nodejs', logo: image_path('/assets/illustrations/logos/node_js.svg') },
{ name: 'npm', logo: image_path('/assets/illustrations/logos/npm.svg') },
{ name: 'OpenShift', logo: image_path('/assets/illustrations/logos/openshift.svg') },
{ name: 'Packer', logo: image_path('/assets/illustrations/logos/packer.svg') },
{ name: 'PHP', logo: image_path('/assets/illustrations/logos/php.svg') },
{ name: 'Python', logo: image_path('/assets/illustrations/logos/python.svg') },
{ name: 'Ruby', logo: image_path('/assets/illustrations/logos/ruby.svg') },
{ name: 'Rust', logo: image_path('/assets/illustrations/logos/rust.svg') },
{ name: 'Scala', logo: image_path('/assets/illustrations/logos/scala.svg') },
{ name: 'Swift', logo: image_path('/assets/illustrations/logos/swift.svg') },
{ name: 'Terraform', logo: image_path('/assets/illustrations/logos/terraform.svg') }
{ name: 'Android', logo: image_path('illustrations/logos/android.svg') },
{ name: 'Bash', logo: image_path('illustrations/logos/bash.svg') },
{ name: 'C++', logo: image_path('illustrations/logos/c_plus_plus.svg') },
{ name: 'Clojure', logo: image_path('illustrations/logos/clojure.svg') },
{ name: 'Composer', logo: image_path('illustrations/logos/composer.svg') },
{ name: 'Crystal', logo: image_path('illustrations/logos/crystal.svg') },
{ name: 'Dart', logo: image_path('illustrations/logos/dart.svg') },
{ name: 'Django', logo: image_path('illustrations/logos/django.svg') },
{ name: 'Docker', logo: image_path('illustrations/logos/docker.svg') },
{ name: 'Elixir', logo: image_path('illustrations/logos/elixir.svg') },
{ name: 'iOS-Fastlane', logo: image_path('illustrations/logos/fastlane.svg') },
{ name: 'Flutter', logo: image_path('illustrations/logos/flutter.svg') },
{ name: 'Go', logo: image_path('illustrations/logos/go_logo.svg') },
{ name: 'Gradle', logo: image_path('illustrations/logos/gradle.svg') },
{ name: 'Grails', logo: image_path('illustrations/logos/grails.svg') },
{ name: 'dotNET', logo: image_path('illustrations/logos/dotnet.svg') },
{ name: 'Rails', logo: image_path('illustrations/logos/rails.svg') },
{ name: 'Julia', logo: image_path('illustrations/logos/julia.svg') },
{ name: 'Laravel', logo: image_path('illustrations/logos/laravel.svg') },
{ name: 'Latex', logo: image_path('illustrations/logos/latex.svg') },
{ name: 'Maven', logo: image_path('illustrations/logos/maven.svg') },
{ name: 'Mono', logo: image_path('illustrations/logos/mono.svg') },
{ name: 'Nodejs', logo: image_path('illustrations/logos/node_js.svg') },
{ name: 'npm', logo: image_path('illustrations/logos/npm.svg') },
{ name: 'OpenShift', logo: image_path('illustrations/logos/openshift.svg') },
{ name: 'Packer', logo: image_path('illustrations/logos/packer.svg') },
{ name: 'PHP', logo: image_path('illustrations/logos/php.svg') },
{ name: 'Python', logo: image_path('illustrations/logos/python.svg') },
{ name: 'Ruby', logo: image_path('illustrations/logos/ruby.svg') },
{ name: 'Rust', logo: image_path('illustrations/logos/rust.svg') },
{ name: 'Scala', logo: image_path('illustrations/logos/scala.svg') },
{ name: 'Swift', logo: image_path('illustrations/logos/swift.svg') },
{ name: 'Terraform', logo: image_path('illustrations/logos/terraform.svg') }
]
end

View File

@ -729,14 +729,6 @@ module ProjectsHelper
]
end
def sidebar_projects_paths
%w[
projects#show
projects#activity
releases#index
]
end
def sidebar_settings_paths
%w[
projects#edit

View File

@ -8,4 +8,8 @@ module WhatsNewHelper
def whats_new_version_digest
ReleaseHighlight.most_recent_version_digest
end
def display_whats_new?
Gitlab.dev_env_org_or_com? || user_signed_in?
end
end

View File

@ -286,9 +286,11 @@ module Ci
end
after_transition any => [:failed] do |pipeline|
next unless pipeline.auto_devops_source?
pipeline.run_after_commit do
::Gitlab::Ci::Pipeline::Metrics.pipeline_failure_reason_counter.increment(reason: pipeline.failure_reason)
pipeline.run_after_commit { AutoDevops::DisableWorker.perform_async(pipeline.id) }
AutoDevops::DisableWorker.perform_async(pipeline.id) if pipeline.auto_devops_source?
end
end
end

View File

@ -179,6 +179,12 @@ class CommitStatus < ApplicationRecord
ExpireJobCacheWorker.perform_async(id)
end
end
after_transition any => :failed do |commit_status|
commit_status.run_after_commit do
::Gitlab::Ci::Pipeline::Metrics.job_failure_reason_counter.increment(reason: commit_status.failure_reason)
end
end
end
def self.names

View File

@ -137,6 +137,14 @@ module Issuable
scope :references_project, -> { references(:project) }
scope :non_archived, -> { join_project.where(projects: { archived: false }) }
scope :includes_for_bulk_update, -> do
associations = %i[author assignees epic group labels metrics project source_project target_project].select do |association|
reflect_on_association(association)
end
includes(*associations)
end
attr_mentionable :title, pipeline: :single_line
attr_mentionable :description

View File

@ -39,11 +39,13 @@ module Milestoneable
private
def milestone_is_valid
errors.add(:milestone_id, 'is invalid') if respond_to?(:milestone_id) && milestone_id.present? && !milestone_available?
errors.add(:milestone_id, 'is invalid') if respond_to?(:milestone_id) && !milestone_available?
end
end
def milestone_available?
return true if milestone_id.blank?
project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group)
end

View File

@ -17,6 +17,13 @@ module Sidebars
{}
end
# Attributes to pass to the html_options attribute
# in the helper method that sets the active class
# on each element.
def nav_link_html_options
{}
end
def title
raise NotImplementedError
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module VulnerabilityFindingHelpers
extend ActiveSupport::Concern
end
VulnerabilityFindingHelpers.prepend_if_ee('EE::VulnerabilityFindingHelpers')

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module VulnerabilityFindingSignatureHelpers
extend ActiveSupport::Concern
end
VulnerabilityFindingSignatureHelpers.prepend_if_ee('EE::VulnerabilityFindingSignatureHelpers')

View File

@ -14,6 +14,8 @@ class PagesDeployment < ApplicationRecord
scope :older_than, -> (id) { where('id < ?', id) }
scope :migrated_from_legacy_storage, -> { where(file: MIGRATED_FILE_NAME) }
scope :with_files_stored_locally, -> { where(file_store: ::ObjectStorage::Store::LOCAL) }
scope :with_files_stored_remotely, -> { where(file_store: ::ObjectStorage::Store::REMOTE) }
validates :file, presence: true
validates :file_store, presence: true, inclusion: { in: ObjectStorage::SUPPORTED_STORES }

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
module ProjectOverview
class Menu < ::Sidebars::Menu
override :configure_menu_items
def configure_menu_items
add_item(MenuItems::Details.new(context))
add_item(MenuItems::Activity.new(context))
add_item(MenuItems::Releases.new(context))
end
override :link
def link
project_path(context.project)
end
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-project rspec-project-link'
}
end
override :extra_container_html_options
def nav_link_html_options
{ class: 'home' }
end
override :title
def title
_('Project overview')
end
override :sprite_icon
def sprite_icon
'home'
end
end
end
end
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
module ProjectOverview
module MenuItems
class Activity < ::Sidebars::MenuItem
override :link
def link
activity_project_path(context.project)
end
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-project-activity'
}
end
override :active_routes
def active_routes
{ path: 'projects#activity' }
end
override :title
def title
_('Activity')
end
end
end
end
end
end
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
module ProjectOverview
module MenuItems
class Details < ::Sidebars::MenuItem
override :link
def link
project_path(context.project)
end
override :extra_container_html_options
def extra_container_html_options
{
title: _('Project details'),
class: 'shortcuts-project'
}
end
override :active_routes
def active_routes
{ path: 'projects#show' }
end
override :title
def title
_('Details')
end
end
end
end
end
end
end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
module Sidebars
module Projects
module Menus
module ProjectOverview
module MenuItems
class Releases < ::Sidebars::MenuItem
override :link
def link
project_releases_path(context.project)
end
override :extra_container_html_options
def extra_container_html_options
{
class: 'shortcuts-project-releases'
}
end
override :render?
def render?
can?(context.current_user, :read_release, context.project) && !context.project.empty_repo?
end
override :active_routes
def active_routes
{ controller: :releases }
end
override :title
def title
_('Releases')
end
end
end
end
end
end
end

View File

@ -6,6 +6,8 @@ module Sidebars
override :configure_menus
def configure_menus
set_scope_menu(Sidebars::Projects::Menus::Scope::Menu.new(context))
add_menu(Sidebars::Projects::Menus::ProjectOverview::Menu.new(context))
end
override :render_raw_menus_partial

View File

@ -19,7 +19,7 @@ module Ci
end
def metrics
@metrics ||= ::Gitlab::Ci::Pipeline::Metrics.new
@metrics ||= ::Gitlab::Ci::Pipeline::Metrics
end
private

View File

@ -15,7 +15,7 @@ module Issuable
set_update_params(type)
items = update_issuables(type, ids)
response_success(payload: { count: items.count })
response_success(payload: { count: items.size })
rescue ArgumentError => e
response_error(e.message, 422)
end
@ -59,10 +59,17 @@ module Issuable
def find_issuables(parent, model_class, ids)
if parent.is_a?(Project)
model_class.id_in(ids).of_projects(parent)
projects = parent
elsif parent.is_a?(Group)
model_class.id_in(ids).of_projects(parent.all_projects)
projects = parent.all_projects
else
return
end
model_class
.id_in(ids)
.of_projects(projects)
.includes_for_bulk_update
end
def response_success(message: nil, payload: nil)

View File

@ -11,18 +11,7 @@ module MergeRequests
end
def execute(merge_request)
# We don't allow change of source/target projects and source branch
# after merge request was created
params.delete(:source_project_id)
params.delete(:target_project_id)
params.delete(:source_branch)
if merge_request.closed_or_merged_without_fork?
params.delete(:target_branch)
params.delete(:force_remove_source_branch)
end
update_task_event(merge_request) || update(merge_request)
update_merge_request_with_specialized_service(merge_request) || general_fallback(merge_request)
end
def handle_changes(merge_request, options)
@ -86,6 +75,21 @@ module MergeRequests
attr_reader :target_branch_was_deleted
def general_fallback(merge_request)
# We don't allow change of source/target projects and source branch
# after merge request was created
params.delete(:source_project_id)
params.delete(:target_project_id)
params.delete(:source_branch)
if merge_request.closed_or_merged_without_fork?
params.delete(:target_branch)
params.delete(:force_remove_source_branch)
end
update_task_event(merge_request) || update(merge_request)
end
def track_title_and_desc_edits(changed_fields)
tracked_fields = %w(title description)
@ -272,6 +276,34 @@ module MergeRequests
def quick_action_options
{ merge_request_diff_head_sha: params.delete(:merge_request_diff_head_sha) }
end
def update_merge_request_with_specialized_service(merge_request)
return unless params.delete(:use_specialized_service)
# If we're attempting to modify only a single attribute, look up whether
# we have a specialized, targeted service we should use instead. We may
# in the future extend this to include specialized services that operate
# on multiple attributes, but for now limit to only single attribute
# updates.
#
return unless params.one?
attempt_specialized_update_services(merge_request, params.each_key.first.to_sym)
end
def attempt_specialized_update_services(merge_request, attribute)
case attribute
when :assignee_ids
assignees_service.execute(merge_request)
else
nil
end
end
def assignees_service
@assignees_service ||= ::MergeRequests::UpdateAssigneesService
.new(project, current_user, params)
end
end
end

View File

@ -41,9 +41,11 @@ module Namespaces
attr_reader :track, :interval, :in_product_marketing_email_records
def send_email_for_group(group)
experiment_enabled_for_group = experiment_enabled_for_group?(group)
experiment_add_group(group, experiment_enabled_for_group)
return unless experiment_enabled_for_group
if Gitlab.com?
experiment_enabled_for_group = experiment_enabled_for_group?(group)
experiment_add_group(group, experiment_enabled_for_group)
return unless experiment_enabled_for_group
end
users_for_group(group).each do |user|
if can_perform_action?(user, group)

View File

@ -2,10 +2,8 @@
module Pages
class MigrateFromLegacyStorageService
def initialize(logger, migration_threads:, batch_size:, ignore_invalid_entries:, mark_projects_as_not_deployed:)
def initialize(logger, ignore_invalid_entries:, mark_projects_as_not_deployed:)
@logger = logger
@migration_threads = migration_threads
@batch_size = batch_size
@ignore_invalid_entries = ignore_invalid_entries
@mark_projects_as_not_deployed = mark_projects_as_not_deployed
@ -14,25 +12,35 @@ module Pages
@counters_lock = Mutex.new
end
def execute
def execute_with_threads(threads:, batch_size:)
@queue = SizedQueue.new(1)
threads = start_migration_threads
migration_threads = start_migration_threads(threads)
ProjectPagesMetadatum.only_on_legacy_storage.each_batch(of: @batch_size) do |batch|
ProjectPagesMetadatum.only_on_legacy_storage.each_batch(of: batch_size) do |batch|
@queue.push(batch)
end
@queue.close
@logger.info("Waiting for threads to finish...")
threads.each(&:join)
@logger.info(message: "Pages legacy storage migration: Waiting for threads to finish...")
migration_threads.each(&:join)
{ migrated: @migrated, errored: @errored }
end
def start_migration_threads
Array.new(@migration_threads) do
def execute_for_batch(project_ids)
batch = ProjectPagesMetadatum.only_on_legacy_storage.where(project_id: project_ids) # rubocop: disable CodeReuse/ActiveRecord
process_batch(batch)
{ migrated: @migrated, errored: @errored }
end
private
def start_migration_threads(count)
Array.new(count) do
Thread.new do
while batch = @queue.pop
Rails.application.executor.wrap do
@ -50,12 +58,12 @@ module Pages
migrate_project(project)
end
@logger.info("#{@migrated} projects are migrated successfully, #{@errored} projects failed to be migrated")
@logger.info(message: "Pages legacy storage migration: batch processed", migrated: @migrated, errored: @errored)
rescue => e
# This method should never raise exception otherwise all threads might be killed
# and this will result in queue starving (and deadlock)
Gitlab::ErrorTracking.track_exception(e)
@logger.error("failed processing a batch: #{e.message}")
@logger.error(message: "Pages legacy storage migration: failed processing a batch: #{e.message}")
end
def migrate_project(project)
@ -67,15 +75,15 @@ module Pages
end
if result[:status] == :success
@logger.info("project_id: #{project.id} #{project.pages_path} has been migrated in #{time.round(2)} seconds: #{result[:message]}")
@logger.info(message: "Pages legacy storage migration: project migrated: #{result[:message]}", project_id: project.id, pages_path: project.pages_path, duration: time.round(2))
@counters_lock.synchronize { @migrated += 1 }
else
@logger.error("project_id: #{project.id} #{project.pages_path} failed to be migrated in #{time.round(2)} seconds: #{result[:message]}")
@logger.error(message: "Pages legacy storage migration: project failed to be migrated: #{result[:message]}", project_id: project.id, pages_path: project.pages_path, duration: time.round(2))
@counters_lock.synchronize { @errored += 1 }
end
rescue => e
@counters_lock.synchronize { @errored += 1 }
@logger.error("project_id: #{project&.id} #{project&.pages_path} failed to be migrated: #{e.message}")
@logger.error(message: "Pages legacy storage migration: project failed to be migrated: #{result[:message]}", project_id: project&.id, pages_path: project&.pages_path)
Gitlab::ErrorTracking.track_exception(e, project_id: project&.id)
end
end

View File

@ -120,7 +120,8 @@
= sprite_icon('ellipsis_h', size: 12, css_class: 'more-icon js-navbar-toggle-right')
= sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left')
#whats-new-app{ data: { version_digest: whats_new_version_digest } }
- if display_whats_new?
#whats-new-app{ data: { version_digest: whats_new_version_digest } }
- if can?(current_user, :update_user_status, current_user)
.js-set-status-modal-wrapper{ data: user_status_data }

View File

@ -1,5 +1,6 @@
%li
%button.gl-justify-content-space-between.gl-align-items-center.js-whats-new-trigger{ type: 'button', class: 'gl-display-flex!' }
= _("What's new")
%span.js-whats-new-notification-count.whats-new-notification-count
= whats_new_most_recent_release_items_count
- if display_whats_new?
%li
%button.gl-justify-content-space-between.gl-align-items-center.js-whats-new-trigger{ type: 'button', class: 'gl-display-flex!' }
= _("What's new")
%span.js-whats-new-notification-count.whats-new-notification-count
= whats_new_most_recent_release_items_count

View File

@ -1,29 +1,3 @@
= nav_link(path: sidebar_projects_paths, html_options: { class: 'home' }) do
= link_to project_path(@project), class: 'shortcuts-project rspec-project-link', data: { qa_selector: 'project_link' } do
.nav-icon-container
= sprite_icon('home')
%span.nav-item-name
= _('Project overview')
%ul.sidebar-sub-level-items
= nav_link(path: 'projects#show', html_options: { class: "fly-out-top-item" } ) do
= link_to project_path(@project) do
%strong.fly-out-top-item-name
= _('Project overview')
%li.divider.fly-out-top-item
= nav_link(path: 'projects#show') do
= link_to project_path(@project), title: _('Project details'), class: 'shortcuts-project' do
%span= _('Details')
= nav_link(path: 'projects#activity') do
= link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity', data: { qa_selector: 'activity_link' } do
%span= _('Activity')
- if project_nav_tab?(:releases)
= nav_link(controller: :releases) do
= link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do
%span= _('Releases')
- if project_nav_tab? :learn_gitlab
= nav_link(controller: :learn_gitlab, html_options: { class: 'home' }) do
= link_to project_learn_gitlab_path(@project) do

View File

@ -16,25 +16,7 @@
= link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches')
.nav-controls
- if !gldropdrown_branches_enabled?
= form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do
= search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false }
- unless @mode == 'overview'
.dropdown.inline>
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.light
= branches_sort_options_hash[@sort]
= sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%li.dropdown-header
= s_('Branches|Sort by')
- branches_sort_options_hash.each do |value, title|
%li
= link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value)
- else
#js-branches-sort-dropdown{ data: { project_branches_filtered_path: project_branches_path(@project, state: 'all'), sort_options: branches_sort_options_hash.to_json, mode: @mode } }
#js-branches-sort-dropdown{ data: { project_branches_filtered_path: project_branches_path(@project, state: 'all'), sort_options: branches_sort_options_hash.to_json, mode: @mode } }
- if can? current_user, :push_code, @project
= link_to project_merged_branches_path(@project),

View File

@ -6,6 +6,8 @@
= render sidebar.render_raw_scope_menu_partial
%ul.sidebar-top-level-items.qa-project-sidebar
- if sidebar.renderable_menus.any?
= render partial: 'shared/nav/sidebar_menu', collection: sidebar.renderable_menus
- if sidebar.render_raw_menus_partial
= render sidebar.render_raw_menus_partial

View File

@ -0,0 +1,27 @@
= nav_link(**sidebar_menu.all_active_routes, html_options: sidebar_menu.nav_link_html_options) do
= link_to sidebar_menu.link, **sidebar_menu.container_html_options, data: { qa_selector: 'sidebar_menu_link', qa_menu_item: sidebar_menu.title } do
- if sidebar_menu.icon_or_image?
.nav-icon-container
- if sidebar_menu.image_path
= image_tag(sidebar_menu.image_path, **sidebar_menu.image_html_options)
- elsif sidebar_menu.sprite_icon
= sprite_icon(sidebar_menu.sprite_icon, **sidebar_menu.sprite_icon_html_options)
%span.nav-item-name{ **sidebar_menu.title_html_options }
= sidebar_menu.title
- if sidebar_menu.has_pill?
%span.badge.badge-pill.count{ **sidebar_menu.pill_html_options }
= number_with_delimiter(sidebar_menu.pill_count)
%ul.sidebar-sub-level-items{ class: ('is-fly-out-only' unless sidebar_menu.has_items?) }
= nav_link(**sidebar_menu.all_active_routes, html_options: { class: 'fly-out-top-item' } ) do
= link_to sidebar_menu.link, title: sidebar_menu.title do
%strong.fly-out-top-item-name
= sidebar_menu.title
- if sidebar_menu.has_pill?
%span.badge.badge-pill.count.fly-out-badge{ **sidebar_menu.pill_html_options }
= number_with_delimiter(sidebar_menu.pill_count)
- if sidebar_menu.has_renderable_items?
%li.divider.fly-out-top-item
= render partial: 'shared/nav/sidebar_menu_item', collection: sidebar_menu.renderable_items

View File

@ -0,0 +1,8 @@
= nav_link(**sidebar_menu_item.active_routes) do
= link_to sidebar_menu_item.link, **sidebar_menu_item.container_html_options, data: { qa_selector: 'sidebar_menu_item_link', qa_menu_item: sidebar_menu_item.title } do
%span
= sidebar_menu_item.title
- if sidebar_menu_item.sprite_icon
= sprite_icon(sidebar_menu_item.sprite_icon, **sidebar_menu_item.sprite_icon_html_options)
- if sidebar_menu_item.show_hint?
.js-feature-highlight{ **sidebar_menu_item.hint_html_options }

View File

@ -0,0 +1,5 @@
---
title: Optimize issuable updates
merge_request: 59468
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Fix Jenkins integration for GitLab FOSS
merge_request: 59476
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Automatically try to migrate gitlab pages to zip storage
merge_request: 54578
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add rake tasks for Pages deployment migration
merge_request: 57120
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Add GraphQL endpoint for a specific test suite in pipelines
merge_request: 58924
author:
type: added

View File

@ -0,0 +1,5 @@
---
title: Remove gldropdown_branches feature flag
merge_request: 59179
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Hide What's New for unauthenticated users
merge_request: 59330
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Add framework for using specialized services to improve performance of MergeRequests::UpdateService
merge_request: 58836
author:
type: performance

View File

@ -0,0 +1,5 @@
---
title: Add queue label to metrics dispatched by background transaction
merge_request: 59344
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Upgrade GitLab Pages to 1.38.0
merge_request: 59464
author:
type: added

View File

@ -1,8 +1,8 @@
---
name: gldropdown_branches
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57760
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326549
name: load_balancing_atomic_replica
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49294
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/291193
milestone: '13.11'
type: development
group: group::continuous integration
default_enabled: true
group:
default_enabled: false

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
class ScheduleMigratePagesToZipStorage < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
MIGRATION = 'MigratePagesToZipStorage'
BATCH_SIZE = 10
BATCH_TIME = 5.minutes
disable_ddl_transaction!
class ProjectPagesMetadatum < ActiveRecord::Base
extend SuppressCompositePrimaryKeyWarning
include EachBatch
self.primary_key = :project_id
self.table_name = 'project_pages_metadata'
self.inheritance_column = :_type_disabled
scope :deployed, -> { where(deployed: true) }
scope :only_on_legacy_storage, -> { deployed.where(pages_deployment_id: nil) }
end
def up
queue_background_migration_jobs_by_range_at_intervals(
ProjectPagesMetadatum.only_on_legacy_storage,
MIGRATION,
BATCH_TIME,
batch_size: BATCH_SIZE,
primary_column_name: :project_id
)
end
end

View File

@ -0,0 +1 @@
7c562d43801c18af48dc526dc6574aebd11689b62bad864b107580d341ba64a1

View File

@ -0,0 +1,29 @@
---
stage: Growth
group: Adoption
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# What's new **(FREE)**
With each monthly release, GitLab includes some of the highlights from the last 10
GitLab versions in the **What's new** feature. To access it:
1. In the top navigation bar, select the **{question}** icon.
1. Select **What's new** from the menu.
The **What's new** describes new features available in multiple
[GitLab tiers](https://about.gitlab.com/pricing). While all users can see the
feature list, the feature list is tailored to your subscription type:
- Features only available to self-managed installations are not shown on GitLab.com.
- Features only available on GitLab.com are not shown to self-managed installations.
The **What's new** feature cannot be disabled, but
[is planned](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59011) for a future release.
## Self-managed installations
Due to our release post process, the content for **What's new** is not yet finalized
when a new version (`.0` release) is cut. The updated **What's new** is included
in the first patch release, such as `13.10.1`.

View File

@ -167,7 +167,8 @@ The following API resources are available outside of project and group contexts
| [Sidekiq metrics](sidekiq_metrics.md) **(FREE SELF)** | `/sidekiq` |
| [Suggestions](suggestions.md) | `/suggestions` |
| [System hooks](system_hooks.md) | `/hooks` |
| [To-dos](todos.md) | `/todos` |
| [To-dos](todos.md) | `/todos` |
| [Usage data](usage_data.md) | `/usage_data` (For GitLab instance [Administrator](../user/permissions.md) users only) |
| [Users](users.md) | `/users` |
| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` |
| [Version](version.md) | `/version` |

View File

@ -631,7 +631,7 @@ Parsed field from an alert used for custom mappings.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `label` | [`String`](#string) | Human-readable label of the payload path. |
| `path` | [`[String!]`](#string) | Path to value inside payload JSON. |
| `path` | [`[PayloadAlertFieldPathSegment!]`](#payloadalertfieldpathsegment) | Path to value inside payload JSON. |
| `type` | [`AlertManagementPayloadAlertFieldType`](#alertmanagementpayloadalertfieldtype) | Type of the parsed value. |
### `AlertManagementPayloadAlertMappingField`
@ -642,7 +642,7 @@ Parsed field (with its name) from an alert used for custom mappings.
| ----- | ---- | ----------- |
| `fieldName` | [`AlertManagementPayloadAlertFieldName`](#alertmanagementpayloadalertfieldname) | A GitLab alert field name. |
| `label` | [`String`](#string) | Human-readable label of the payload path. |
| `path` | [`[String!]`](#string) | Path to value inside payload JSON. |
| `path` | [`[PayloadAlertFieldPathSegment!]`](#payloadalertfieldpathsegment) | Path to value inside payload JSON. |
| `type` | [`AlertManagementPayloadAlertFieldType`](#alertmanagementpayloadalertfieldtype) | Type of the parsed value. |
### `AlertManagementPrometheusIntegration`
@ -4827,6 +4827,7 @@ Information about pagination in a connection.
| `startedAt` | [`Time`](#time) | Timestamp when the pipeline was started. |
| `status` | [`PipelineStatusEnum!`](#pipelinestatusenum) | Status of the pipeline (CREATED, WAITING_FOR_RESOURCE, PREPARING, PENDING, RUNNING, FAILED, SUCCESS, CANCELED, SKIPPED, MANUAL, SCHEDULED). |
| `testReportSummary` | [`TestReportSummary!`](#testreportsummary) | Summary of the test report generated by the pipeline. |
| `testSuite` | [`TestSuite`](#testsuite) | A specific test suite in a pipeline test report. |
| `updatedAt` | [`Time!`](#time) | Timestamp of the pipeline's last activity. |
| `upstream` | [`Pipeline`](#pipeline) | Pipeline that triggered the pipeline. |
| `user` | [`User`](#user) | Pipeline user. |
@ -5276,6 +5277,15 @@ Represents rules that commit pushes must follow.
| ----- | ---- | ----------- |
| `rejectUnsignedCommits` | [`Boolean!`](#boolean) | Indicates whether commits not signed through GPG will be rejected. |
### `RecentFailures`
Recent failure history of a test case.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `baseBranch` | [`String`](#string) | Name of the base branch of the project. |
| `count` | [`Int`](#int) | Number of times the test case has failed in the past 14 days. |
### `Release`
Represents a release.
@ -5522,13 +5532,44 @@ Autogenerated return type of RepositionImageDiffNote.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `blobs` | [`BlobConnection`](#blobconnection) | Blobs contained within the repository. |
| `blobs` | [`RepositoryBlobConnection`](#repositoryblobconnection) | Blobs contained within the repository. |
| `branchNames` | [`[String!]`](#string) | Names of branches available in this repository that match the search pattern. |
| `empty` | [`Boolean!`](#boolean) | Indicates repository has no visible content. |
| `exists` | [`Boolean!`](#boolean) | Indicates a corresponding Git repository exists on disk. |
| `rootRef` | [`String`](#string) | Default branch of the repository. |
| `tree` | [`Tree`](#tree) | Tree of the repository. |
### `RepositoryBlob`
| Field | Type | Description |
| ----- | ---- | ----------- |
| `id` | [`ID!`](#id) | ID of the blob. |
| `lfsOid` | [`String`](#string) | LFS OID of the blob. |
| `mode` | [`String`](#string) | Blob mode. |
| `name` | [`String`](#string) | Blob name. |
| `oid` | [`String!`](#string) | OID of the blob. |
| `path` | [`String!`](#string) | Path of the blob. |
| `webPath` | [`String`](#string) | Web path of the blob. |
### `RepositoryBlobConnection`
The connection type for RepositoryBlob.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `edges` | [`[RepositoryBlobEdge]`](#repositoryblobedge) | A list of edges. |
| `nodes` | [`[RepositoryBlob]`](#repositoryblob) | A list of nodes. |
| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
### `RepositoryBlobEdge`
An edge in a connection.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `cursor` | [`String!`](#string) | A cursor for use in pagination. |
| `node` | [`RepositoryBlob`](#repositoryblob) | The item at the end of the edge. |
### `Requirement`
Represents a requirement.
@ -6333,6 +6374,42 @@ An edge in a connection.
| `cursor` | [`String!`](#string) | A cursor for use in pagination. |
| `node` | [`TerraformStateVersionRegistry`](#terraformstateversionregistry) | The item at the end of the edge. |
### `TestCase`
Test case in pipeline test report.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `attachmentUrl` | [`String`](#string) | URL of the test case attachment file. |
| `classname` | [`String`](#string) | Classname of the test case. |
| `executionTime` | [`Float`](#float) | Test case execution time in seconds. |
| `file` | [`String`](#string) | Path to the file of the test case. |
| `name` | [`String`](#string) | Name of the test case. |
| `recentFailures` | [`RecentFailures`](#recentfailures) | Recent failure history of the test case on the base branch. |
| `stackTrace` | [`String`](#string) | Stack trace of the test case. |
| `status` | [`TestCaseStatus`](#testcasestatus) | Status of the test case (error, failed, success, skipped). |
| `systemOutput` | [`String`](#string) | System output of the test case. |
### `TestCaseConnection`
The connection type for TestCase.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `count` | [`Int!`](#int) | Total count of collection. |
| `edges` | [`[TestCaseEdge]`](#testcaseedge) | A list of edges. |
| `nodes` | [`[TestCase]`](#testcase) | A list of nodes. |
| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
### `TestCaseEdge`
An edge in a connection.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `cursor` | [`String!`](#string) | A cursor for use in pagination. |
| `node` | [`TestCase`](#testcase) | The item at the end of the edge. |
### `TestReport`
Represents a requirement test report.
@ -6386,6 +6463,22 @@ Total test report statistics.
| `suiteError` | [`String`](#string) | Test suite error message. |
| `time` | [`Float`](#float) | Total duration of the tests. |
### `TestSuite`
Test suite in a pipeline test report.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `errorCount` | [`Int`](#int) | Total number of test cases that had an error. |
| `failedCount` | [`Int`](#int) | Total number of test cases that failed in the test suite. |
| `name` | [`String`](#string) | Name of the test suite. |
| `skippedCount` | [`Int`](#int) | Total number of test cases that were skipped in the test suite. |
| `successCount` | [`Int`](#int) | Total number of test cases that succeeded in the test suite. |
| `suiteError` | [`String`](#string) | Test suite error message. |
| `testCases` | [`TestCaseConnection`](#testcaseconnection) | Test cases in the test suite. |
| `totalCount` | [`Int`](#int) | Total number of the test cases in the test suite. |
| `totalTime` | [`Float`](#float) | Total duration of the tests in the test suite. |
### `TestSuiteSummary`
Test suite summary in a pipeline test report.
@ -8424,6 +8517,15 @@ Common sort values.
| `updated_asc` **{warning-solid}** | **Deprecated:** This was renamed. Please use `UPDATED_ASC`. Deprecated in 13.5. |
| `updated_desc` **{warning-solid}** | **Deprecated:** This was renamed. Please use `UPDATED_DESC`. Deprecated in 13.5. |
### `TestCaseStatus`
| Value | Description |
| ----- | ----------- |
| `error` | Test case that has a status of error. |
| `failed` | Test case that has a status of failed. |
| `skipped` | Test case that has a status of skipped. |
| `success` | Test case that has a status of success. |
### `TestReportState`
State of a test report.
@ -8967,6 +9069,10 @@ A `PackagesPackageID` is a global ID. It is encoded as a string.
An example `PackagesPackageID` is: `"gid://gitlab/Packages::Package/1"`.
### `PayloadAlertFieldPathSegment`
String or integer.
### `ProjectID`
A `ProjectID` is a global ID. It is encoded as a string.

View File

@ -5,17 +5,61 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: reference, api
---
# UsageData API **(FREE SELF)**
# Usage Data API **(FREE SELF)**
The UsageData API, associated with [Usage Ping](../development/usage_ping/index.md), is available only for
the use of GitLab instance [Administrator](../user/permissions.md) users.
The Usage Data API is associated with [Usage Ping](../development/usage_ping/index.md).
## UsageDataQueries API
## Export metric definitions as a single YAML file
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57270) in GitLab 13.11.
Export all metric definitions as a single YAML file, similar to the [Metrics Dictionary](../development/usage_ping/dictionary.md), for easier importing.
```plaintext
GET /usage_data/metric_definitions
```
Example request:
```shell
curl "https://gitlab.example.com/api/v4/usage_data/metric_definitions"
```
Example response:
```yaml
---
- key_path: redis_hll_counters.search.i_search_paid_monthly
description: Calculated unique users to perform a search with a paid license enabled
by month
product_section: enablement
product_stage: enablement
product_group: group::global search
product_category: global_search
value_type: number
status: data_available
time_frame: 28d
data_source: redis_hll
distribution:
- ee
tier:
- premium
- ultimate
...
```
## Export Usage Ping SQL queries
This action is available only for the GitLab instance [Administrator](../user/permissions.md) users.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57016) in GitLab 13.11.
> - [Deployed behind a feature flag](../user/feature_flags.md), disabled by default.
Return all of the raw SQL queries used to compute usage ping.
Return all of the raw SQL queries used to compute Usage Ping.
```plaintext
GET /usage_data/queries
```
Example request:
@ -23,7 +67,7 @@ Example request:
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/usage_data/queries"
```
Sample response
Example response:
```json
{

View File

@ -1411,37 +1411,6 @@ bin/rake gitlab:usage_data:dump_sql_in_json
bin/rake gitlab:usage_data:dump_sql_in_yaml > ~/Desktop/usage-metrics-2020-09-02.yaml
```
## Export metric definitions as a single YAML file
Use this API endpoint to export all metric definitions as a single YAML file, similar to the [Metrics Dictionary](dictionary.md), for easier importing.
```plaintext
GET /usage_data/metric_definitions
```
Response
```yaml
---
- key_path: redis_hll_counters.search.i_search_paid_monthly
description: Calculated unique users to perform a search with a paid license enabled
by month
product_section: enablement
product_stage: enablement
product_group: group::global search
product_category: global_search
value_type: number
status: data_available
time_frame: 28d
data_source: redis_hll
distribution:
- ee
tier:
- premium
- ultimate
...
```
## Generating and troubleshooting usage ping
This activity is to be done via a detached screen session on a remote server.

View File

@ -11,6 +11,8 @@ You can integrate GitLab.com and Jira Cloud using the
app in the Atlassian Marketplace. The user configuring GitLab for Jira must have
[Maintainer](../../user/permissions.md) permissions in the GitLab namespace.
This integration method supports [smart commits](dvcs.md#smart-commits).
This method is recommended when using GitLab.com and Jira Cloud because data is
synchronized in real-time. The DVCS connector updates data only once per hour.
If you are not using both of these environments, use the [Jira DVCS Connector](dvcs.md) method.

View File

@ -18,6 +18,25 @@ are accessible.
- **Jira Cloud**: Your instance must be accessible through the internet.
- **Jira Server**: Your network must allow access to your instance.
## Smart commits
When connecting GitLab with Jira with DVCS, you can process your Jira issues using
special commands, called
[Smart Commits](https://support.atlassian.com/jira-software-cloud/docs/process-issues-with-smart-commits/),
in your commit messages. With Smart Commits, you can:
- Comment on issues.
- Record time-tracking information against issues.
- Transition issues to any status defined in the Jira project's workflow.
Commands must be in the first line of the commit message. The
[Jira Software documentation](https://support.atlassian.com/jira-software-cloud/docs/process-issues-with-smart-commits/)
contains more information about how smart commits work, and what commands are available
for your use.
For smart commits to work, the committing user on GitLab must have a corresponding
user on Jira with the same email address or username.
## Configure a GitLab application for DVCS
We recommend you create and use a `jira` user in GitLab, and use the account only

View File

@ -8,9 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223061) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.4.
> - [In GitLab 13.10](https://gitlab.com/gitlab-org/gitlab/-/issues/300960), KAS became available on GitLab.com under `wss://kas.gitlab.com` through an Early Adopter Program.
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
> - Introduced in GitLab 13.11, the GitLab Kubernetes Agent became available to every project on GitLab.com.
The [GitLab Kubernetes Agent](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent)
is an active in-cluster component for solving GitLab and Kubernetes integration
@ -169,17 +167,21 @@ the Agent in subsequent steps. You can create an Agent record with GraphQL:
### Install the Agent into the cluster
Next, install the in-cluster component of the Agent.
NOTE:
For GitLab.com users, the KAS is available at `wss://kas.gitlab.com`.
#### One-liner installation
Replace the value of `agent-token` below with the token received from the previous step. Also, replace `kas-address` with the configured access of the Kubernetes Agent Server:
To install the in-cluster component of the Agent, first you need to define a namespace. To create a new namespace,
for example, `gitlab-kubernetes-agent`, run:
```shell
docker run --pull=always --rm registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/cli:stable generate --agent-token=your-agent-token --kas-address=wss://kas.gitlab.example.com --agent-version stable | kubectl apply -f -
kubectl create namespace gitlab-kubernetes-agent
```
To perform a one-liner installation, run the command below. Make sure to replace:
- `your-agent-token` with the token received from the previous step.
- `gitlab-kubernetes-agent` with the namespace you defined in the previous step.
- `wss://kas.gitlab.example.com` with the configured access of the Kubernetes Agent Server (KAS). For GitLab.com users, the KAS is available under `wss://kas.gitlab.com`.
```shell
docker run --pull=always --rm registry.gitlab.com/gitlab-org/cluster-integration/gitlab-agent/cli:stable generate --agent-token=your-agent-token --kas-address=wss://kas.gitlab.example.com --agent-version stable --namespace gitlab-kubernetes-agent | kubectl apply -f -
```
Set `--agent-version` to the latest released patch version matching your
@ -206,17 +208,11 @@ Otherwise, you can follow below for fully manual, detailed installation steps.
After generating the token, you must apply it to the Kubernetes cluster.
1. If you haven't previously defined or created a namespace, run the following command:
To create your Secret, run:
```shell
kubectl create namespace <YOUR-DESIRED-NAMESPACE>
```
1. Run the following command to create your Secret:
```shell
kubectl create secret generic -n <YOUR-DESIRED-NAMESPACE> gitlab-agent-token --from-literal=token='YOUR_AGENT_TOKEN'
```
```shell
kubectl create secret generic -n <YOUR_NAMESPACE> gitlab-agent-token --from-literal=token='YOUR_AGENT_TOKEN'
```
The following example file contains the
Kubernetes resources required for the Agent to be installed. You can modify this

View File

@ -1311,7 +1311,6 @@ X-Gitlab-Event: Job Hook
"name": "User",
"email": "user@gitlab.com",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon",
"email": "admin@example.com"
},
"commit": {
"id": 2366,

View File

@ -172,6 +172,9 @@ create a merge request from your fork to contribute back to the main project:
1. In the left menu, go to **Merge Requests**, and click **New Merge Request**.
1. In the **Source branch** drop-down list box, select your branch in your forked repository as the source branch.
1. In the **Target branch** drop-down list box, select the branch from the upstream repository as the target branch.
You can set a [default target project](#set-the-default-target-project) to
change the default target branch (which can be useful when working with a
forked project).
1. After entering the credentials, click **Compare branches and continue** to compare your local changes to the upstream repository.
1. Assign a user to review your changes, and click **Submit merge request**.
@ -183,6 +186,24 @@ fork from its upstream project in the **Settings > Advanced Settings** section b
For further details, [see the forking workflow documentation](../repository/forking_workflow.md).
## Set the default target project
Merge requests have a source and a target project which are the same, unless
forking is involved. Creating a fork of the project can cause either of these
scenarios when you create a new merge request:
- You target an upstream project (the project you forked, and the default
option).
- You target your own fork.
If you want to have merge requests from a fork by default target your own fork
(instead of the upstream project), you can change the default by:
1. In your project, go to **Settings > General > Merge requests**.
1. In the **Target project** section, select the option you want to use for
your default target project.
1. Select **Save changes**.
## New merge request by email **(FREE SELF)**
_This feature needs [incoming email](../../../administration/incoming_email.md)

View File

@ -214,6 +214,7 @@ Set up your project's merge request settings:
- Enable [merge only when all threads are resolved](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved).
- Enable [`delete source branch after merge` option by default](../merge_requests/getting_started.md#deleting-the-source-branch)
- Configure [suggested changes commit messages](../../discussions/index.md#configure-the-commit-message-for-applied-suggestions)
- Configure [the default target project](../merge_requests/creating_merge_requests.md#set-the-default-target-project) for merge requests coming from forks.
### Service Desk

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# migrates pages from legacy storage to zip format
# we intentionally use application code here because
# it has a lot of dependencies including models, carrierwave uploaders and service objects
# and copying all or part of this code in the background migration doesn't add much value
# see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54578 for discussion
class MigratePagesToZipStorage
def perform(start_id, stop_id)
::Pages::MigrateFromLegacyStorageService.new(Gitlab::AppLogger,
ignore_invalid_entries: false,
mark_projects_as_not_deployed: false)
.execute_for_batch(start_id..stop_id)
end
end
end
end

View File

@ -84,7 +84,7 @@ module Gitlab
end
def metrics
@metrics ||= ::Gitlab::Ci::Pipeline::Metrics.new
@metrics ||= ::Gitlab::Ci::Pipeline::Metrics
end
def observe_creation_duration(duration)
@ -97,6 +97,11 @@ module Gitlab
.observe({ source: pipeline.source.to_s }, pipeline.total_size)
end
def increment_pipeline_failure_reason_counter(reason)
metrics.pipeline_failure_reason_counter
.increment(reason: (reason || :unknown_failure).to_s)
end
def dangling_build?
%i[ondemand_dast_scan webide].include?(source)
end

View File

@ -13,16 +13,7 @@ module Gitlab
pipeline.add_error_message(message)
if drop_reason && persist_pipeline?
if Feature.enabled?(:ci_pipeline_ensure_iid_on_drop, pipeline.project, default_enabled: :yaml)
# Project iid must be called outside a transaction, so we ensure it is set here
# otherwise it may be set within the state transition transaction of the drop! call
# which it will lock the InternalId row for the whole transaction
pipeline.ensure_project_iid!
end
pipeline.drop!(drop_reason)
end
drop_pipeline!(drop_reason)
# TODO: consider not to rely on AR errors directly as they can be
# polluted with other unrelated errors (e.g. state machine)
@ -34,8 +25,23 @@ module Gitlab
pipeline.add_warning_message(message)
end
def persist_pipeline?
command.save_incompleted && !pipeline.readonly?
private
def drop_pipeline!(drop_reason)
return if pipeline.readonly?
if drop_reason && command.save_incompleted
if Feature.enabled?(:ci_pipeline_ensure_iid_on_drop, pipeline.project, default_enabled: :yaml)
# Project iid must be called outside a transaction, so we ensure it is set here
# otherwise it may be set within the state transition transaction of the drop! call
# which it will lock the InternalId row for the whole transaction
pipeline.ensure_project_iid!
end
pipeline.drop!(drop_reason)
else
command.increment_pipeline_failure_reason_counter(drop_reason)
end
end
end
end

View File

@ -14,7 +14,7 @@ module Gitlab
end
def counter
::Gitlab::Ci::Pipeline::Metrics.new.pipelines_created_counter
::Gitlab::Ci::Pipeline::Metrics.pipelines_created_counter
end
end
end

View File

@ -4,55 +4,57 @@ module Gitlab
module Ci
module Pipeline
class Metrics
include Gitlab::Utils::StrongMemoize
def self.pipeline_creation_duration_histogram
name = :gitlab_ci_pipeline_creation_duration_seconds
comment = 'Pipeline creation duration'
labels = {}
buckets = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 20.0, 50.0, 240.0]
def pipeline_creation_duration_histogram
strong_memoize(:pipeline_creation_duration_histogram) do
name = :gitlab_ci_pipeline_creation_duration_seconds
comment = 'Pipeline creation duration'
labels = {}
buckets = [0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 20.0, 50.0, 240.0]
::Gitlab::Metrics.histogram(name, comment, labels, buckets)
end
::Gitlab::Metrics.histogram(name, comment, labels, buckets)
end
def pipeline_size_histogram
strong_memoize(:pipeline_size_histogram) do
name = :gitlab_ci_pipeline_size_builds
comment = 'Pipeline size'
labels = { source: nil }
buckets = [0, 1, 5, 10, 20, 50, 100, 200, 500, 1000]
def self.pipeline_size_histogram
name = :gitlab_ci_pipeline_size_builds
comment = 'Pipeline size'
labels = { source: nil }
buckets = [0, 1, 5, 10, 20, 50, 100, 200, 500, 1000]
::Gitlab::Metrics.histogram(name, comment, labels, buckets)
end
::Gitlab::Metrics.histogram(name, comment, labels, buckets)
end
def pipeline_processing_events_counter
strong_memoize(:pipeline_processing_events_counter) do
name = :gitlab_ci_pipeline_processing_events_total
comment = 'Total amount of pipeline processing events'
def self.pipeline_processing_events_counter
name = :gitlab_ci_pipeline_processing_events_total
comment = 'Total amount of pipeline processing events'
Gitlab::Metrics.counter(name, comment)
end
Gitlab::Metrics.counter(name, comment)
end
def pipelines_created_counter
strong_memoize(:pipelines_created_count) do
name = :pipelines_created_total
comment = 'Counter of pipelines created'
def self.pipelines_created_counter
name = :pipelines_created_total
comment = 'Counter of pipelines created'
Gitlab::Metrics.counter(name, comment)
end
Gitlab::Metrics.counter(name, comment)
end
def legacy_update_jobs_counter
strong_memoize(:legacy_update_jobs_counter) do
name = :ci_legacy_update_jobs_as_retried_total
comment = 'Counter of occurrences when jobs were not being set as retried before update_retried'
def self.legacy_update_jobs_counter
name = :ci_legacy_update_jobs_as_retried_total
comment = 'Counter of occurrences when jobs were not being set as retried before update_retried'
Gitlab::Metrics.counter(name, comment)
end
Gitlab::Metrics.counter(name, comment)
end
def self.pipeline_failure_reason_counter
name = :gitlab_ci_pipeline_failure_reasons
comment = 'Counter of pipeline failure reasons'
Gitlab::Metrics.counter(name, comment)
end
def self.job_failure_reason_counter
name = :gitlab_ci_job_failure_reasons
comment = 'Counter of job failure reasons'
Gitlab::Metrics.counter(name, comment)
end
end
end

View File

@ -57,6 +57,13 @@ module Gitlab
def batch_class_name=(class_name)
write_attribute(:batch_class_name, class_name.demodulize)
end
def prometheus_labels
@prometheus_labels ||= {
migration_id: id,
migration_identifier: "%s/%s.%s" % [job_class_name, table_name, column_name]
}
end
end
end
end

View File

@ -4,6 +4,8 @@ module Gitlab
module Database
module BackgroundMigration
class BatchedMigrationWrapper
extend Gitlab::Utils::StrongMemoize
# Wraps the execution of a batched_background_migration.
#
# Updates the job's tracking records with the status of the migration
@ -23,6 +25,7 @@ module Gitlab
raise e
ensure
finish_tracking_execution(batch_tracking_record)
track_prometheus_metrics(batch_tracking_record)
end
private
@ -51,6 +54,65 @@ module Gitlab
tracking_record.finished_at = Time.current
tracking_record.save!
end
def track_prometheus_metrics(tracking_record)
migration = tracking_record.batched_migration
base_labels = migration.prometheus_labels
metric_for(:gauge_batch_size).set(base_labels, tracking_record.batch_size)
metric_for(:gauge_sub_batch_size).set(base_labels, tracking_record.sub_batch_size)
metric_for(:counter_updated_tuples).increment(base_labels, tracking_record.batch_size)
# Time efficiency: Ratio of duration to interval (ideal: less than, but close to 1)
efficiency = (tracking_record.finished_at - tracking_record.started_at).to_i / migration.interval.to_f
metric_for(:histogram_time_efficiency).observe(base_labels, efficiency)
if metrics = tracking_record.metrics
metrics['timings']&.each do |key, timings|
summary = metric_for(:histogram_timings)
labels = base_labels.merge(operation: key)
timings.each do |timing|
summary.observe(labels, timing)
end
end
end
end
def metric_for(name)
self.class.metrics[name]
end
def self.metrics
strong_memoize(:metrics) do
{
gauge_batch_size: Gitlab::Metrics.gauge(
:batched_migration_job_batch_size,
'Batch size for a batched migration job'
),
gauge_sub_batch_size: Gitlab::Metrics.gauge(
:batched_migration_job_sub_batch_size,
'Sub-batch size for a batched migration job'
),
counter_updated_tuples: Gitlab::Metrics.counter(
:batched_migration_job_updated_tuples_total,
'Number of tuples updated by batched migration job'
),
histogram_timings: Gitlab::Metrics.histogram(
:batched_migration_job_duration_seconds,
'Timings for a batched migration job',
{},
[0.1, 0.25, 0.5, 1, 5].freeze
),
histogram_time_efficiency: Gitlab::Metrics.histogram(
:batched_migration_job_time_efficiency,
'Ratio of job duration to interval',
{},
[0.5, 0.9, 1, 1.5, 2].freeze
)
}
end
end
end
end
end

View File

@ -34,8 +34,9 @@ module Gitlab
def labels
@labels ||= {
endpoint_id: current_context&.get_attribute(:caller_id),
feature_category: current_context&.get_attribute(:feature_category)
endpoint_id: endpoint_id,
feature_category: feature_category,
queue: queue
}
end
@ -44,6 +45,21 @@ module Gitlab
def current_context
Labkit::Context.current
end
def feature_category
current_context&.get_attribute(:feature_category)
end
def endpoint_id
current_context&.get_attribute(:caller_id)
end
def queue
worker_class = endpoint_id.to_s.safe_constantize
return if worker_class.blank? || !worker_class.respond_to?(:queue)
worker_class.queue.to_s
end
end
end
end

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
module Gitlab
module Pages
class MigrationHelper
def initialize(logger = nil)
@logger = logger
end
def migrate_to_remote_storage
deployments = ::PagesDeployment.with_files_stored_locally
migrate(deployments, ObjectStorage::Store::REMOTE)
end
def migrate_to_local_storage
deployments = ::PagesDeployment.with_files_stored_remotely
migrate(deployments, ObjectStorage::Store::LOCAL)
end
private
def batch_size
ENV.fetch('MIGRATION_BATCH_SIZE', 10).to_i
end
def migrate(deployments, store)
deployments.find_each(batch_size: batch_size) do |deployment| # rubocop:disable CodeReuse/ActiveRecord
deployment.file.migrate!(store)
log_success(deployment, store)
rescue => e
log_error(e, deployment)
end
end
def log_success(deployment, store)
logger.info("Transferred deployment ID #{deployment.id} of type #{deployment.file_type} with size #{deployment.size} to #{storage_label(store)} storage")
end
def log_error(err, deployment)
logger.warn("Failed to transfer deployment of type #{deployment.file_type} and ID #{deployment.id} with error: #{err.message}")
end
def storage_label(store)
if store == ObjectStorage::Store::LOCAL
'local'
else
'object'
end
end
end
end
end

View File

@ -24,6 +24,12 @@ module Gitlab
def histogram(relation, column, buckets:, bucket_size: buckets.size)
SQL_METRIC_DEFAULT
end
def maximum_id(model)
end
def minimum_id(model)
end
end
end
end

View File

@ -9,10 +9,9 @@ namespace :gitlab do
logger.info('Starting to migrate legacy pages storage to zip deployments')
result = ::Pages::MigrateFromLegacyStorageService.new(logger,
migration_threads: migration_threads,
batch_size: batch_size,
ignore_invalid_entries: ignore_invalid_entries,
mark_projects_as_not_deployed: mark_projects_as_not_deployed).execute
mark_projects_as_not_deployed: mark_projects_as_not_deployed)
.execute_with_threads(threads: migration_threads, batch_size: batch_size)
logger.info("A total of #{result[:migrated] + result[:errored]} projects were processed.")
logger.info("- The #{result[:migrated]} projects migrated successfully")
@ -58,5 +57,33 @@ namespace :gitlab do
ENV.fetch('PAGES_MIGRATION_MARK_PROJECTS_AS_NOT_DEPLOYED', 'false')
)
end
namespace :deployments do
task migrate_to_object_storage: :gitlab_environment do
logger = Logger.new(STDOUT)
logger.info('Starting transfer of pages deployments to remote storage')
helper = Gitlab::Pages::MigrationHelper.new(logger)
begin
helper.migrate_to_remote_storage
rescue => e
logger.error(e.message)
end
end
task migrate_to_local: :gitlab_environment do
logger = Logger.new(STDOUT)
logger.info('Starting transfer of Pages deployments to local storage')
helper = Gitlab::Pages::MigrationHelper.new(logger)
begin
helper.migrate_to_local_storage
rescue => e
logger.error(e.message)
end
end
end
end
end

View File

@ -5319,9 +5319,6 @@ msgstr ""
msgid "Branches|Show stale branches"
msgstr ""
msgid "Branches|Sort by"
msgstr ""
msgid "Branches|Stale"
msgstr ""
@ -6656,9 +6653,6 @@ msgstr ""
msgid "CloudLicense|Paste your activation code"
msgstr ""
msgid "CloudLicense|This instance is currently using the %{planName} plan."
msgstr ""
msgid "CloudLicense|This is the highest peak of users on your installation since the license started."
msgstr ""
@ -6677,9 +6671,6 @@ msgstr ""
msgid "CloudLicense|You'll be charged for %{trueUpLinkStart}users over license%{trueUpLinkEnd} on a quarterly or annual basis, depending on the terms of your agreement."
msgstr ""
msgid "CloudLicense|Your subscription"
msgstr ""
msgid "Cluster"
msgstr ""
@ -30378,6 +30369,12 @@ msgstr ""
msgid "SuperSonics|Valid From"
msgstr ""
msgid "SuperSonics|You do not have an active subscription"
msgstr ""
msgid "SuperSonics|Your subscription"
msgstr ""
msgid "Support"
msgstr ""

View File

@ -14,7 +14,6 @@ module QA
include SubMenus::Packages
view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do
element :activity_link
element :merge_requests_link
element :snippets_link
element :members_link
@ -24,6 +23,10 @@ module QA
element :wiki_link
end
view 'app/views/shared/nav/_sidebar_menu_item.html.haml' do
element :sidebar_menu_item_link
end
def click_merge_requests
within_sidebar do
click_element(:merge_requests_link)
@ -38,7 +41,7 @@ module QA
def click_activity
within_sidebar do
click_element(:activity_link)
click_element(:sidebar_menu_item_link, menu_item: 'Activity')
end
end

View File

@ -13,8 +13,8 @@ module QA
base.class_eval do
include QA::Page::Project::SubMenus::Common
view 'app/views/layouts/nav/sidebar/_project_menus.html.haml' do
element :project_link
view 'app/views/shared/nav/_sidebar_menu.html.haml' do
element :sidebar_menu_link
end
end
end
@ -22,7 +22,7 @@ module QA
def click_project
retry_on_exception do
within_sidebar do
click_element(:project_link)
click_element(:sidebar_menu_link, menu_item: 'Project overview')
end
end
end

View File

@ -12,10 +12,12 @@ RSpec.describe "User deletes branch", :js do
end
it "deletes branch" do
stub_feature_flags(gldropdown_branches: false)
visit(project_branches_path(project))
fill_in("branch-search", with: "improve/awesome").native.send_keys(:enter)
branch_search = find('input[data-testid="branch-search"]')
branch_search.set('improve/awesome')
branch_search.native.send_keys(:enter)
page.within(".js-branch-improve\\/awesome") do
accept_alert { find(".btn-danger").click }
@ -25,23 +27,4 @@ RSpec.describe "User deletes branch", :js do
expect(page).to have_css(".js-branch-improve\\/awesome", visible: :hidden)
end
context 'with gldropdown_branches enabled' do
it "deletes branch" do
visit(project_branches_path(project))
branch_search = find('input[data-testid="branch-search"]')
branch_search.set('improve/awesome')
branch_search.native.send_keys(:enter)
page.within(".js-branch-improve\\/awesome") do
accept_alert { find(".btn-danger").click }
end
wait_for_requests
expect(page).to have_css(".js-branch-improve\\/awesome", visible: :hidden)
end
end
end

View File

@ -86,29 +86,16 @@ RSpec.describe 'Branches' do
describe 'Find branches' do
it 'shows filtered branches', :js do
stub_feature_flags(gldropdown_branches: false)
visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
branch_search = find('input[data-testid="branch-search"]')
branch_search.set('fix')
branch_search.native.send_keys(:enter)
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
end
context 'with gldropdown_branches enabled' do
it 'shows filtered branches', :js do
visit project_branches_path(project)
branch_search = find('input[data-testid="branch-search"]')
branch_search.set('fix')
branch_search.native.send_keys(:enter)
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
end
end
end
describe 'Delete unprotected branch on Overview' do
@ -129,52 +116,28 @@ RSpec.describe 'Branches' do
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc))
end
it 'sorts the branches by name' do
stub_feature_flags(gldropdown_branches: false)
it 'sorts the branches by name', :js do
visit project_branches_filtered_path(project, state: 'all')
click_button "Last updated" # Open sorting dropdown
click_link "Name"
within '[data-testid="branches-dropdown"]' do
find('p', text: 'Name').click
end
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :name))
end
context 'with gldropdown_branches enabled' do
it 'sorts the branches by name', :js do
visit project_branches_filtered_path(project, state: 'all')
click_button "Last updated" # Open sorting dropdown
within '[data-testid="branches-dropdown"]' do
find('p', text: 'Name').click
end
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :name))
end
end
it 'sorts the branches by oldest updated' do
stub_feature_flags(gldropdown_branches: false)
it 'sorts the branches by oldest updated', :js do
visit project_branches_filtered_path(project, state: 'all')
click_button "Last updated" # Open sorting dropdown
click_link "Oldest updated"
within '[data-testid="branches-dropdown"]' do
find('p', text: 'Oldest updated').click
end
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_asc))
end
context 'with gldropdown_branches enabled' do
it 'sorts the branches by oldest updated', :js do
visit project_branches_filtered_path(project, state: 'all')
click_button "Last updated" # Open sorting dropdown
within '[data-testid="branches-dropdown"]' do
find('p', text: 'Oldest updated').click
end
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_asc))
end
end
it 'avoids a N+1 query in branches index' do
control_count = ActiveRecord::QueryRecorder.new { visit project_branches_path(project) }.count
@ -186,39 +149,26 @@ RSpec.describe 'Branches' do
describe 'Find branches on All branches' do
it 'shows filtered branches', :js do
stub_feature_flags(gldropdown_branches: false)
visit project_branches_filtered_path(project, state: 'all')
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
branch_search = find('input[data-testid="branch-search"]')
branch_search.set('fix')
branch_search.native.send_keys(:enter)
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
end
context 'with gldropdown_branches enabled' do
it 'shows filtered branches', :js do
visit project_branches_filtered_path(project, state: 'all')
branch_search = find('input[data-testid="branch-search"]')
branch_search.set('fix')
branch_search.native.send_keys(:enter)
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
end
end
end
describe 'Delete unprotected branch on All branches' do
it 'removes branch after confirmation', :js do
stub_feature_flags(gldropdown_branches: false)
visit project_branches_filtered_path(project, state: 'all')
fill_in 'branch-search', with: 'fix'
branch_search = find('input[data-testid="branch-search"]')
find('#branch-search').native.send_keys(:enter)
branch_search.set('fix')
branch_search.native.send_keys(:enter)
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
@ -227,24 +177,6 @@ RSpec.describe 'Branches' do
expect(page).not_to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 0)
end
context 'with gldropdown_branches enabled' do
it 'removes branch after confirmation', :js do
visit project_branches_filtered_path(project, state: 'all')
branch_search = find('input[data-testid="branch-search"]')
branch_search.set('fix')
branch_search.native.send_keys(:enter)
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
accept_confirm { find('.js-branch-fix .btn-danger').click }
expect(page).not_to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 0)
end
end
end
context 'on project with 0 branch' do

View File

@ -22,25 +22,13 @@ RSpec.describe 'Protected Branches', :js do
end
it 'does not allow developer to removes protected branch' do
stub_feature_flags(gldropdown_branches: false)
visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
find('input[data-testid="branch-search"]').set('fix')
find('input[data-testid="branch-search"]').native.send_keys(:enter)
expect(page).to have_css('.btn-danger.disabled')
end
context 'with gldropdown_branches enabled' do
it 'does not allow developer to removes protected branch' do
visit project_branches_path(project)
find('input[data-testid="branch-search"]').set('fix')
find('input[data-testid="branch-search"]').native.send_keys(:enter)
expect(page).to have_css('.btn-danger.disabled')
end
end
end
end
@ -57,11 +45,10 @@ RSpec.describe 'Protected Branches', :js do
end
it 'removes branch after modal confirmation' do
stub_feature_flags(gldropdown_branches: false)
visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
find('input[data-testid="branch-search"]').set('fix')
find('input[data-testid="branch-search"]').native.send_keys(:enter)
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
@ -71,33 +58,11 @@ RSpec.describe 'Protected Branches', :js do
fill_in 'delete_branch_input', with: 'fix'
click_link 'Delete protected branch'
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
find('input[data-testid="branch-search"]').set('fix')
find('input[data-testid="branch-search"]').native.send_keys(:enter)
expect(page).to have_content('No branches to show')
end
context 'with gldropdown_branches enabled' do
it 'removes branch after modal confirmation' do
visit project_branches_path(project)
find('input[data-testid="branch-search"]').set('fix')
find('input[data-testid="branch-search"]').native.send_keys(:enter)
expect(page).to have_content('fix')
expect(find('.all-branches')).to have_selector('li', count: 1)
page.find('[data-target="#modal-delete-branch"]').click
expect(page).to have_css('.js-delete-branch[disabled]')
fill_in 'delete_branch_input', with: 'fix'
click_link 'Delete protected branch'
find('input[data-testid="branch-search"]').set('fix')
find('input[data-testid="branch-search"]').native.send_keys(:enter)
expect(page).to have_content('No branches to show')
end
end
end
end

View File

@ -2,34 +2,60 @@
require "spec_helper"
RSpec.describe "renders a `whats new` dropdown item", :js do
RSpec.describe "renders a `whats new` dropdown item" do
let_it_be(:user) { create(:user) }
before do
sign_in(user)
end
context 'when not logged in' do
it 'and on .com it renders' do
allow(Gitlab).to receive(:com?).and_return(true)
it 'shows notification dot and count and removes it once viewed' do
visit root_dashboard_path
visit user_path(user)
page.within '.header-help' do
expect(page).to have_selector('.notification-dot', visible: true)
page.within '.header-help' do
find('.header-help-dropdown-toggle').click
find('.header-help-dropdown-toggle').click
expect(page).to have_button(text: "What's new")
expect(page).to have_selector('.js-whats-new-notification-count')
find('button', text: "What's new").click
expect(page).to have_button(text: "What's new")
end
end
find('.whats-new-drawer .gl-drawer-close-button').click
find('.header-help-dropdown-toggle').click
it "doesn't render what's new" do
visit user_path(user)
page.within '.header-help' do
expect(page).not_to have_selector('.notification-dot', visible: true)
expect(page).to have_button(text: "What's new")
expect(page).not_to have_selector('.js-whats-new-notification-count')
page.within '.header-help' do
find('.header-help-dropdown-toggle').click
expect(page).not_to have_button(text: "What's new")
end
end
end
context 'when logged in', :js do
before do
sign_in(user)
end
it 'shows notification dot and count and removes it once viewed' do
visit root_dashboard_path
page.within '.header-help' do
expect(page).to have_selector('.notification-dot', visible: true)
find('.header-help-dropdown-toggle').click
expect(page).to have_button(text: "What's new")
expect(page).to have_selector('.js-whats-new-notification-count')
find('button', text: "What's new").click
end
find('.whats-new-drawer .gl-drawer-close-button').click
find('.header-help-dropdown-toggle').click
page.within '.header-help' do
expect(page).not_to have_selector('.notification-dot', visible: true)
expect(page).to have_button(text: "What's new")
expect(page).not_to have_selector('.js-whats-new-notification-count')
end
end
end
end

View File

@ -0,0 +1,54 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::Ci::TestSuiteResolver do
include GraphqlHelpers
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public, :repository) }
describe '#resolve' do
subject(:test_suite) { resolve(described_class, obj: pipeline, args: { build_ids: build_ids }) }
context 'when pipeline has builds with test reports' do
let_it_be(:main_pipeline) { create(:ci_pipeline, :with_test_reports_with_three_failures, project: project) }
let_it_be(:pipeline) { create(:ci_pipeline, :with_test_reports_with_three_failures, project: project, ref: 'new-feature') }
let(:suite_name) { 'test' }
let(:build_ids) { pipeline.latest_builds.pluck(:id) }
before do
build = main_pipeline.builds.last
build.update_column(:finished_at, 1.day.ago) # Just to be sure we are included in the report window
# The JUnit fixture for the given build has 3 failures.
# This service will create 1 test case failure record for each.
Ci::TestFailureHistoryService.new(main_pipeline).execute
end
it 'renders test suite data' do
expect(test_suite[:name]).to eq('test')
# Each test failure in this pipeline has a matching failure in the default branch
recent_failures = test_suite[:test_cases].map { |tc| tc[:recent_failures] }
expect(recent_failures).to eq([
{ count: 1, base_branch: 'master' },
{ count: 1, base_branch: 'master' },
{ count: 1, base_branch: 'master' }
])
end
end
context 'when pipeline has no builds that matches the given build_ids' do
let_it_be(:pipeline) { create(:ci_empty_pipeline) }
let(:suite_name) { 'test' }
let(:build_ids) { [non_existing_record_id] }
it 'returns nil' do
expect(test_suite).to be_nil
end
end
end
end

View File

@ -13,7 +13,7 @@ RSpec.describe Types::Ci::PipelineType do
coverage created_at updated_at started_at finished_at committed_at
stages user retryable cancelable jobs source_job job downstream
upstream path project active user_permissions warnings commit_path uses_needs
test_report_summary
test_report_summary test_suite
]
if Gitlab.ee?

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::RecentFailuresType do
specify { expect(described_class.graphql_name).to eq('RecentFailures') }
it 'contains attributes related to a recent failure history for a test case' do
expected_fields = %w[
count base_branch
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::TestCaseStatusEnum do
specify { expect(described_class.graphql_name).to eq('TestCaseStatus') }
it 'exposes all test case status types' do
expect(described_class.values.keys).to eq(
::Gitlab::Ci::Reports::TestCase::STATUS_TYPES
)
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::TestCaseType do
specify { expect(described_class.graphql_name).to eq('TestCase') }
it 'contains attributes related to a pipeline test case' do
expected_fields = %w[
name status classname file attachment_url execution_time stack_trace system_output recent_failures
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Ci::TestSuiteType do
specify { expect(described_class.graphql_name).to eq('TestSuite') }
it 'contains attributes related to a pipeline test suite' do
expected_fields = %w[
name total_time total_count success_count failed_count skipped_count error_count suite_error test_cases
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end

View File

@ -0,0 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Types::Repository::BlobType do
specify { expect(described_class.graphql_name).to eq('RepositoryBlob') }
specify { expect(described_class).to have_graphql_fields(:id, :oid, :name, :path, :web_path, :lfs_oid, :mode) }
end

View File

@ -47,19 +47,4 @@ RSpec.describe BranchesHelper do
end
end
end
describe '#gl_dropdown_branches_enabled?' do
context 'when the feature is enabled' do
it 'returns true' do
expect(helper.gldropdrown_branches_enabled?).to be_truthy
end
end
context 'when the feature is disabled' do
it 'returns false' do
stub_feature_flags(gldropdown_branches: false)
expect(helper.gldropdrown_branches_enabled?).to be_falsy
end
end
end
end

View File

@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe WhatsNewHelper do
include Devise::Test::ControllerHelpers
describe '#whats_new_version_digest' do
let(:digest) { 'digest' }
@ -32,4 +34,30 @@ RSpec.describe WhatsNewHelper do
end
end
end
describe '#display_whats_new?' do
subject { helper.display_whats_new? }
it 'returns true when gitlab.com' do
allow(Gitlab).to receive(:dev_env_org_or_com?).and_return(true)
expect(subject).to be true
end
context 'when self-managed' do
before do
allow(Gitlab).to receive(:dev_env_org_or_com?).and_return(false)
end
it 'returns true if user is signed in' do
sign_in(create(:user))
expect(subject).to be true
end
it "returns false if user isn't signed in" do
expect(subject).to be false
end
end
end
end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::MigratePagesToZipStorage do
let(:namespace) { create(:group) } # rubocop: disable RSpec/FactoriesInMigrationSpecs
let(:migration) { described_class.new }
describe '#perform' do
context 'when there is project to migrate' do
let!(:project) { create_project('project') }
after do
FileUtils.rm_rf(project.pages_path)
end
it 'migrates project to zip storage' do
expect_next_instance_of(::Pages::MigrateFromLegacyStorageService,
anything,
ignore_invalid_entries: false,
mark_projects_as_not_deployed: false) do |service|
expect(service).to receive(:execute_for_batch).with(project.id..project.id).and_call_original
end
migration.perform(project.id, project.id)
expect(project.reload.pages_metadatum.pages_deployment.file.filename).to eq("_migrated.zip")
end
end
end
def create_project(path)
project = create(:project) # rubocop: disable RSpec/FactoriesInMigrationSpecs
project.mark_pages_as_deployed
FileUtils.mkdir_p File.join(project.pages_path, "public")
File.open(File.join(project.pages_path, "public/index.html"), "w") do |f|
f.write("Hello!")
end
project
end
end

View File

@ -321,4 +321,25 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
it { is_expected.to be_falsey }
end
end
describe '#increment_pipeline_failure_reason_counter' do
let(:command) { described_class.new }
let(:reason) { :size_limit_exceeded }
subject { command.increment_pipeline_failure_reason_counter(reason) }
it 'increments the error metric' do
counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
expect { subject }.to change { counter.get(reason: reason.to_s) }.by(1)
end
context 'when the reason is nil' do
let(:reason) { nil }
it 'increments the error metric with unknown_failure' do
counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
expect { subject }.to change { counter.get(reason: 'unknown_failure') }.by(1)
end
end
end
end

View File

@ -11,7 +11,7 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
let(:save_incompleted) { false }
let(:command) do
double(:command,
Gitlab::Ci::Pipeline::Chain::Command.new(
project: project,
pipeline_seed: pipeline_seed,
save_incompleted: save_incompleted
@ -49,6 +49,11 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
expect(pipeline.deployments_limit_exceeded?).to be true
end
it 'calls increment_pipeline_failure_reason_counter' do
counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
expect { perform }.to change { counter.get(reason: 'deployments_limit_exceeded') }.by(1)
end
end
context 'when not saving incomplete pipelines' do
@ -71,6 +76,12 @@ RSpec.describe ::Gitlab::Ci::Pipeline::Chain::Limit::Deployments do
expect(pipeline.errors.messages).to include(base: ['Pipeline has too many deployments! Requested 2, but the limit is 1.'])
end
it 'increments the error metric' do
expect(command).to receive(:increment_pipeline_failure_reason_counter).with(:deployments_limit_exceeded)
perform
end
end
it 'logs the error' do

View File

@ -96,6 +96,11 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Populate do
it 'wastes pipeline iid' do
expect(InternalId.ci_pipelines.where(project_id: project.id).last.last_value).to be > 0
end
it 'increments the error metric' do
counter = Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc')
expect { run_chain }.to change { counter.get(reason: 'unknown_failure') }.by(1)
end
end
describe 'pipeline protect' do

View File

@ -195,4 +195,17 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigration, type: :m
describe '#batch_class_name=' do
it_behaves_like 'an attr_writer that demodulizes assigned class names', :batch_class_name
end
describe '#prometheus_labels' do
let(:batched_migration) { create(:batched_background_migration, job_class_name: 'TestMigration', table_name: 'foo', column_name: 'bar') }
it 'returns a hash with labels for the migration' do
labels = {
migration_id: batched_migration.id,
migration_identifier: 'TestMigration/foo.bar'
}
expect(batched_migration.prometheus_labels).to eq(labels)
end
end
end

View File

@ -3,7 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '#perform' do
let(:migration_wrapper) { described_class.new }
subject { described_class.new.perform(job_record) }
let(:job_class) { Gitlab::BackgroundMigration::CopyColumnUsingBackgroundMigrationJob }
let_it_be(:active_migration) { create(:batched_background_migration, :active, job_arguments: [:id, :other_id]) }
@ -18,7 +19,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
it 'runs the migration job' do
expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
migration_wrapper.perform(job_record)
subject
end
it 'updates the tracking record in the database' do
@ -30,7 +31,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
expect(job_record).to receive(:update!).with(hash_including(attempts: 1, status: :running)).and_call_original
freeze_time do
migration_wrapper.perform(job_record)
subject
reloaded_job_record = job_record.reload
@ -41,12 +42,66 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
end
end
context 'reporting prometheus metrics' do
let(:labels) { job_record.batched_migration.prometheus_labels }
before do
allow(job_instance).to receive(:perform)
end
it 'reports batch_size' do
expect(described_class.metrics[:gauge_batch_size]).to receive(:set).with(labels, job_record.batch_size)
subject
end
it 'reports sub_batch_size' do
expect(described_class.metrics[:gauge_sub_batch_size]).to receive(:set).with(labels, job_record.sub_batch_size)
subject
end
it 'reports updated tuples (currently based on batch_size)' do
expect(described_class.metrics[:counter_updated_tuples]).to receive(:increment).with(labels, job_record.batch_size)
subject
end
it 'reports summary of query timings' do
metrics = { 'timings' => { 'update_all' => [1, 2, 3, 4, 5] } }
expect(job_instance).to receive(:batch_metrics).and_return(metrics)
metrics['timings'].each do |key, timings|
summary_labels = labels.merge(operation: key)
timings.each do |timing|
expect(described_class.metrics[:histogram_timings]).to receive(:observe).with(summary_labels, timing)
end
end
subject
end
it 'reports time efficiency' do
freeze_time do
expect(Time).to receive(:current).and_return(Time.zone.now - 5.seconds).ordered
expect(Time).to receive(:current).and_return(Time.zone.now).ordered
ratio = 5 / job_record.batched_migration.interval.to_f
expect(described_class.metrics[:histogram_time_efficiency]).to receive(:observe).with(labels, ratio)
subject
end
end
end
context 'when the migration job does not raise an error' do
it 'marks the tracking record as succeeded' do
expect(job_instance).to receive(:perform).with(1, 10, 'events', 'id', 1, 'id', 'other_id')
freeze_time do
migration_wrapper.perform(job_record)
subject
reloaded_job_record = job_record.reload
@ -63,7 +118,7 @@ RSpec.describe Gitlab::Database::BackgroundMigration::BatchedMigrationWrapper, '
.and_raise(RuntimeError, 'Something broke!')
freeze_time do
expect { migration_wrapper.perform(job_record) }.to raise_error(RuntimeError, 'Something broke!')
expect { subject }.to raise_error(RuntimeError, 'Something broke!')
reloaded_job_record = job_record.reload

View File

@ -29,17 +29,60 @@ RSpec.describe Gitlab::Metrics::BackgroundTransaction do
end
describe '#labels' do
it 'provides labels with endpoint_id and feature_category' do
Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects' })
context 'when the worker queue is accessible' do
before do
test_worker_class = Class.new do
def self.queue
'test_worker'
end
end
stub_const('TestWorker', test_worker_class)
end
it 'provides labels with endpoint_id, feature_category and queue' do
Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects', queue: 'test_worker' })
end
end
end
context 'when the worker name does not exist' do
it 'provides labels with endpoint_id and feature_category' do
# 123TestWorker is an invalid constant
Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: '123TestWorker') do
expect(transaction.labels).to eq({ endpoint_id: '123TestWorker', feature_category: 'projects', queue: nil })
end
end
end
context 'when the worker queue is not accessible' do
before do
stub_const('TestWorker', Class.new)
end
it 'provides labels with endpoint_id and feature_category' do
Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
expect(transaction.labels).to eq({ endpoint_id: 'TestWorker', feature_category: 'projects', queue: nil })
end
end
end
end
RSpec.shared_examples 'metric with labels' do |metric_method|
before do
test_worker_class = Class.new do
def self.queue
'test_worker'
end
end
stub_const('TestWorker', test_worker_class)
end
it 'measures with correct labels and value' do
value = 1
expect(prometheus_metric).to receive(metric_method).with({ endpoint_id: 'TestWorker', feature_category: 'projects' }, value)
expect(prometheus_metric).to receive(metric_method).with({
endpoint_id: 'TestWorker', feature_category: 'projects', queue: 'test_worker'
}, value)
Gitlab::ApplicationContext.with_raw_context(feature_category: 'projects', caller_id: 'TestWorker') do
transaction.send(metric_method, :test_metric, value)

Some files were not shown because too many files have changed in this diff Show More