Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
dda49284fc
commit
91035102b4
|
@ -1,43 +0,0 @@
|
||||||
---
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
Performance/BlockGivenWithExplicitBlock:
|
|
||||||
Exclude:
|
|
||||||
- 'app/controllers/concerns/redis_tracking.rb'
|
|
||||||
- 'app/helpers/badges_helper.rb'
|
|
||||||
- 'app/helpers/instance_configuration_helper.rb'
|
|
||||||
- 'app/helpers/labels_helper.rb'
|
|
||||||
- 'app/helpers/tab_helper.rb'
|
|
||||||
- 'app/services/base_count_service.rb'
|
|
||||||
- 'app/services/error_tracking/base_service.rb'
|
|
||||||
- 'app/services/users/update_service.rb'
|
|
||||||
- 'ee/lib/elastic/latest/query_context.rb'
|
|
||||||
- 'ee/lib/gitlab/geo.rb'
|
|
||||||
- 'lib/bulk_imports/clients/http.rb'
|
|
||||||
- 'lib/gitlab/batch_pop_queueing.rb'
|
|
||||||
- 'lib/gitlab/cache/request_cache.rb'
|
|
||||||
- 'lib/gitlab/ci/trace/chunked_io.rb'
|
|
||||||
- 'lib/gitlab/database/bulk_update.rb'
|
|
||||||
- 'lib/gitlab/database/with_lock_retries.rb'
|
|
||||||
- 'lib/gitlab/github_import/client.rb'
|
|
||||||
- 'lib/gitlab/legacy_github_import/client.rb'
|
|
||||||
- 'lib/gitlab/metrics/methods/metric_options.rb'
|
|
||||||
- 'lib/gitlab/null_request_store.rb'
|
|
||||||
- 'lib/gitlab/quick_actions/dsl.rb'
|
|
||||||
- 'lib/gitlab/redis/multi_store.rb'
|
|
||||||
- 'lib/gitlab/safe_request_loader.rb'
|
|
||||||
- 'lib/gitlab/search/query.rb'
|
|
||||||
- 'lib/gitlab/string_placeholder_replacer.rb'
|
|
||||||
- 'lib/gitlab/terraform/state_migration_helper.rb'
|
|
||||||
- 'lib/gitlab/usage/metrics/instrumentations/base_metric.rb'
|
|
||||||
- 'lib/gitlab/usage/metrics/instrumentations/database_metric.rb'
|
|
||||||
- 'lib/gitlab/usage/metrics/instrumentations/numbers_metric.rb'
|
|
||||||
- 'lib/gitlab/usage_data_queries.rb'
|
|
||||||
- 'lib/gitlab/utils/usage_data.rb'
|
|
||||||
- 'qa/qa/page/view.rb'
|
|
||||||
- 'spec/lib/api/helpers/authentication_spec.rb'
|
|
||||||
- 'spec/lib/gitlab/slash_commands/deploy_spec.rb'
|
|
||||||
- 'spec/support/helpers/graphql_helpers.rb'
|
|
||||||
- 'spec/support/helpers/query_recorder.rb'
|
|
||||||
- 'spec/support/helpers/stub_method_calls.rb'
|
|
||||||
- 'tooling/lib/tooling/helm3_client.rb'
|
|
||||||
- 'tooling/lib/tooling/test_map_packer.rb'
|
|
|
@ -29,7 +29,7 @@ module RedisTracking
|
||||||
private
|
private
|
||||||
|
|
||||||
def track_unique_redis_hll_event(event_name, &block)
|
def track_unique_redis_hll_event(event_name, &block)
|
||||||
custom_id = block_given? ? yield(self) : nil
|
custom_id = block ? yield(self) : nil
|
||||||
|
|
||||||
unique_id = custom_id || visitor_id
|
unique_id = custom_id || visitor_id
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,12 @@ module Types
|
||||||
null: true,
|
null: true,
|
||||||
description: "Shared runners availability for the namespace and its descendants."
|
description: "Shared runners availability for the namespace and its descendants."
|
||||||
|
|
||||||
|
field :timelog_categories,
|
||||||
|
Types::TimeTracking::TimelogCategoryType.connection_type,
|
||||||
|
null: true,
|
||||||
|
description: "Timelog categories for the namespace.",
|
||||||
|
feature_flag: :timelog_categories
|
||||||
|
|
||||||
markdown_field :description_html, null: true
|
markdown_field :description_html, null: true
|
||||||
|
|
||||||
def cross_project_pipeline_available?
|
def cross_project_pipeline_available?
|
||||||
|
|
|
@ -438,6 +438,16 @@ module Types
|
||||||
' Returns `null` if `work_items` feature flag is disabled.' \
|
' Returns `null` if `work_items` feature flag is disabled.' \
|
||||||
' This flag is disabled by default, because the feature is experimental and is subject to change without notice.'
|
' This flag is disabled by default, because the feature is experimental and is subject to change without notice.'
|
||||||
|
|
||||||
|
field :timelog_categories,
|
||||||
|
Types::TimeTracking::TimelogCategoryType.connection_type,
|
||||||
|
null: true,
|
||||||
|
description: "Timelog categories for the project.",
|
||||||
|
feature_flag: :timelog_categories
|
||||||
|
|
||||||
|
def timelog_categories
|
||||||
|
object.project_namespace.timelog_categories
|
||||||
|
end
|
||||||
|
|
||||||
def label(title:)
|
def label(title:)
|
||||||
BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args|
|
BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args|
|
||||||
LabelsFinder
|
LabelsFinder
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Types
|
||||||
|
module TimeTracking
|
||||||
|
class TimelogCategoryType < BaseObject
|
||||||
|
graphql_name 'TimeTrackingTimelogCategory'
|
||||||
|
|
||||||
|
authorize :read_timelog_category
|
||||||
|
|
||||||
|
field :id,
|
||||||
|
GraphQL::Types::ID,
|
||||||
|
null: false,
|
||||||
|
description: 'Internal ID of the timelog category.'
|
||||||
|
|
||||||
|
field :name,
|
||||||
|
GraphQL::Types::String,
|
||||||
|
null: false,
|
||||||
|
description: 'Name of the category.'
|
||||||
|
|
||||||
|
field :description,
|
||||||
|
GraphQL::Types::String,
|
||||||
|
null: true,
|
||||||
|
description: 'Description of the category.'
|
||||||
|
|
||||||
|
field :color,
|
||||||
|
Types::ColorType,
|
||||||
|
null: true,
|
||||||
|
description: 'Color assigned to the category.'
|
||||||
|
|
||||||
|
field :billable,
|
||||||
|
GraphQL::Types::Boolean,
|
||||||
|
null: true,
|
||||||
|
description: 'Whether the category is billable or not.'
|
||||||
|
|
||||||
|
field :billing_rate,
|
||||||
|
GraphQL::Types::Float,
|
||||||
|
null: true,
|
||||||
|
description: 'Billing rate for the category.'
|
||||||
|
|
||||||
|
field :created_at,
|
||||||
|
Types::TimeType,
|
||||||
|
null: false,
|
||||||
|
description: 'When the category was created.'
|
||||||
|
|
||||||
|
field :updated_at,
|
||||||
|
Types::TimeType,
|
||||||
|
null: false,
|
||||||
|
description: 'When the category was last updated.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -53,7 +53,7 @@ module BadgesHelper
|
||||||
#
|
#
|
||||||
# See also https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/base-badge--default.
|
# See also https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/base-badge--default.
|
||||||
def gl_badge_tag(*args, &block)
|
def gl_badge_tag(*args, &block)
|
||||||
if block_given?
|
if block
|
||||||
build_gl_badge_tag(capture(&block), *args)
|
build_gl_badge_tag(capture(&block), *args)
|
||||||
else
|
else
|
||||||
build_gl_badge_tag(*args)
|
build_gl_badge_tag(*args)
|
||||||
|
|
|
@ -4,7 +4,7 @@ module InstanceConfigurationHelper
|
||||||
def instance_configuration_cell_html(value, &block)
|
def instance_configuration_cell_html(value, &block)
|
||||||
return '-' unless value.to_s.presence
|
return '-' unless value.to_s.presence
|
||||||
|
|
||||||
block_given? ? yield(value) : value
|
block ? yield(value) : value
|
||||||
end
|
end
|
||||||
|
|
||||||
def instance_configuration_host(host)
|
def instance_configuration_host(host)
|
||||||
|
|
|
@ -39,7 +39,7 @@ module LabelsHelper
|
||||||
def link_to_label(label, type: :issue, tooltip: true, small: false, css_class: nil, &block)
|
def link_to_label(label, type: :issue, tooltip: true, small: false, css_class: nil, &block)
|
||||||
link = label.filter_path(type: type)
|
link = label.filter_path(type: type)
|
||||||
|
|
||||||
if block_given?
|
if block
|
||||||
link_to link, class: css_class, &block
|
link_to link, class: css_class, &block
|
||||||
else
|
else
|
||||||
render_label(label, link: link, tooltip: tooltip, small: small)
|
render_label(label, link: link, tooltip: tooltip, small: small)
|
||||||
|
|
|
@ -17,7 +17,7 @@ module TabHelper
|
||||||
class: [*html_options[:class], gl_tabs_classes].join(' ')
|
class: [*html_options[:class], gl_tabs_classes].join(' ')
|
||||||
)
|
)
|
||||||
|
|
||||||
content = capture(&block) if block_given?
|
content = capture(&block) if block
|
||||||
content_tag(:ul, content, html_options)
|
content_tag(:ul, content, html_options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ module TabHelper
|
||||||
link_classes = %w[nav-link gl-tab-nav-item]
|
link_classes = %w[nav-link gl-tab-nav-item]
|
||||||
active_link_classes = %w[active gl-tab-nav-item-active]
|
active_link_classes = %w[active gl-tab-nav-item-active]
|
||||||
|
|
||||||
if block_given?
|
if block
|
||||||
# Shift params to skip the omitted "name" param
|
# Shift params to skip the omitted "name" param
|
||||||
html_options = options
|
html_options = options
|
||||||
options = name
|
options = name
|
||||||
|
@ -54,7 +54,7 @@ module TabHelper
|
||||||
tab_class = %w[nav-item].push(*extra_tab_classes)
|
tab_class = %w[nav-item].push(*extra_tab_classes)
|
||||||
|
|
||||||
content_tag(:li, class: tab_class) do
|
content_tag(:li, class: tab_class) do
|
||||||
if block_given?
|
if block
|
||||||
link_to(options, html_options, &block)
|
link_to(options, html_options, &block)
|
||||||
else
|
else
|
||||||
link_to(name, options, html_options)
|
link_to(name, options, html_options)
|
||||||
|
@ -150,7 +150,7 @@ module TabHelper
|
||||||
o[:class] = [*o[:class], klass].join(' ')
|
o[:class] = [*o[:class], klass].join(' ')
|
||||||
o[:class].strip!
|
o[:class].strip!
|
||||||
|
|
||||||
if block_given?
|
if block
|
||||||
content_tag(:li, capture(&block), o)
|
content_tag(:li, capture(&block), o)
|
||||||
else
|
else
|
||||||
content_tag(:li, nil, o)
|
content_tag(:li, nil, o)
|
||||||
|
|
|
@ -82,6 +82,8 @@ class Namespace < ApplicationRecord
|
||||||
has_many :work_items, inverse_of: :namespace
|
has_many :work_items, inverse_of: :namespace
|
||||||
has_many :issues, inverse_of: :namespace
|
has_many :issues, inverse_of: :namespace
|
||||||
|
|
||||||
|
has_many :timelog_categories, class_name: 'TimeTracking::TimelogCategory'
|
||||||
|
|
||||||
validates :owner, presence: true, if: ->(n) { n.owner_required? }
|
validates :owner, presence: true, if: ->(n) { n.owner_required? }
|
||||||
validates :name,
|
validates :name,
|
||||||
presence: true,
|
presence: true,
|
||||||
|
|
|
@ -2,8 +2,20 @@
|
||||||
|
|
||||||
module Namespaces
|
module Namespaces
|
||||||
class GroupProjectNamespaceSharedPolicy < ::NamespacePolicy
|
class GroupProjectNamespaceSharedPolicy < ::NamespacePolicy
|
||||||
# Nothing here at the moment, but as we move policies from ProjectPolicy to ProjectNamespacePolicy,
|
# As we move policies from ProjectPolicy to ProjectNamespacePolicy,
|
||||||
# anything common with GroupPolicy but not with UserNamespacePolicy can go in here.
|
# anything common with GroupPolicy but not with UserNamespacePolicy can go in here.
|
||||||
# See https://gitlab.com/groups/gitlab-org/-/epics/6689
|
# See https://gitlab.com/groups/gitlab-org/-/epics/6689
|
||||||
|
|
||||||
|
condition(:timelog_categories_enabled, score: 0, scope: :subject) do
|
||||||
|
Feature.enabled?(:timelog_categories, @subject)
|
||||||
|
end
|
||||||
|
|
||||||
|
rule { ~timelog_categories_enabled }.policy do
|
||||||
|
prevent :read_timelog_category
|
||||||
|
end
|
||||||
|
|
||||||
|
rule { can?(:reporter_access) }.policy do
|
||||||
|
enable :read_timelog_category
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
module Namespaces
|
module Namespaces
|
||||||
class ProjectNamespacePolicy < Namespaces::GroupProjectNamespaceSharedPolicy
|
class ProjectNamespacePolicy < Namespaces::GroupProjectNamespaceSharedPolicy
|
||||||
# For now users are not granted any permissions on project namespace
|
# TODO: once https://gitlab.com/gitlab-org/gitlab/-/issues/364277 is solved, this
|
||||||
# as it's completely hidden to them. When we start using project
|
# should not be necessary anymore, and should be replaced with `delegate(:project)`.
|
||||||
# namespaces in queries, we will have to extend this policy.
|
delegate(:reload_project)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module TimeTracking
|
||||||
|
class TimelogCategoryPolicy < BasePolicy
|
||||||
|
delegate { @subject.namespace }
|
||||||
|
end
|
||||||
|
end
|
|
@ -45,7 +45,7 @@ class BaseCountService
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_cache_for_key(key, &block)
|
def update_cache_for_key(key, &block)
|
||||||
Rails.cache.write(key, block_given? ? yield : uncached_count, raw: raw?)
|
Rails.cache.write(key, block ? yield : uncached_count, raw: raw?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ module ErrorTracking
|
||||||
errors = parse_errors(response)
|
errors = parse_errors(response)
|
||||||
return errors if errors
|
return errors if errors
|
||||||
|
|
||||||
yield if block_given?
|
yield if block
|
||||||
|
|
||||||
track_usage_event(params[:tracking_event], current_user.id) if params[:tracking_event]
|
track_usage_event(params[:tracking_event], current_user.id) if params[:tracking_event]
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ module Users
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(validate: true, check_password: false, &block)
|
def execute(validate: true, check_password: false, &block)
|
||||||
yield(@user) if block_given?
|
yield(@user) if block
|
||||||
|
|
||||||
user_exists = @user.persisted?
|
user_exists = @user.persisted?
|
||||||
@user.user_detail # prevent assignment
|
@user.user_detail # prevent assignment
|
||||||
|
|
|
@ -30,6 +30,8 @@ module Webauthn
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
##
|
##
|
||||||
# Validates that webauthn_credential is syntactically valid
|
# Validates that webauthn_credential is syntactically valid
|
||||||
#
|
#
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: timelog_categories
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88462
|
||||||
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/365829
|
||||||
|
milestone: '15.3'
|
||||||
|
type: development
|
||||||
|
group: group::project management
|
||||||
|
default_enabled: false
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class UpdateIndexVulnerabilitiesCommonFinder < Gitlab::Database::Migration[2.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
NEW_INDEX_NAME = 'index_vulnerabilities_common_finder_query_on_default_branch'
|
||||||
|
OLD_INDEX_NAME = 'index_vulnerabilites_common_finder_query'
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index :vulnerabilities, [:project_id, :state, :report_type, :present_on_default_branch,
|
||||||
|
:severity, :id], name: NEW_INDEX_NAME
|
||||||
|
|
||||||
|
remove_concurrent_index_by_name(:vulnerabilities, OLD_INDEX_NAME)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
add_concurrent_index :vulnerabilities, [:project_id, :state, :report_type, :severity, :id], name: OLD_INDEX_NAME
|
||||||
|
|
||||||
|
remove_concurrent_index_by_name(:vulnerabilities, NEW_INDEX_NAME)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
c868a83176c8e0024ef16e0f95d8a16a0f1b7be0c1a5d58902397cc0462a7e34
|
|
@ -30098,7 +30098,7 @@ CREATE INDEX index_vuln_reads_on_namespace_id_state_severity_and_vuln_id ON vuln
|
||||||
|
|
||||||
CREATE INDEX index_vuln_reads_on_project_id_state_severity_and_vuln_id ON vulnerability_reads USING btree (project_id, state, severity, vulnerability_id DESC);
|
CREATE INDEX index_vuln_reads_on_project_id_state_severity_and_vuln_id ON vulnerability_reads USING btree (project_id, state, severity, vulnerability_id DESC);
|
||||||
|
|
||||||
CREATE INDEX index_vulnerabilites_common_finder_query ON vulnerabilities USING btree (project_id, state, report_type, severity, id);
|
CREATE INDEX index_vulnerabilities_common_finder_query_on_default_branch ON vulnerabilities USING btree (project_id, state, report_type, present_on_default_branch, severity, id);
|
||||||
|
|
||||||
CREATE INDEX index_vulnerabilities_on_author_id ON vulnerabilities USING btree (author_id);
|
CREATE INDEX index_vulnerabilities_on_author_id ON vulnerabilities USING btree (author_id);
|
||||||
|
|
||||||
|
|
|
@ -8925,6 +8925,29 @@ The edge type for [`TestSuiteSummary`](#testsuitesummary).
|
||||||
| <a id="testsuitesummaryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
| <a id="testsuitesummaryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||||
| <a id="testsuitesummaryedgenode"></a>`node` | [`TestSuiteSummary`](#testsuitesummary) | The item at the end of the edge. |
|
| <a id="testsuitesummaryedgenode"></a>`node` | [`TestSuiteSummary`](#testsuitesummary) | The item at the end of the edge. |
|
||||||
|
|
||||||
|
#### `TimeTrackingTimelogCategoryConnection`
|
||||||
|
|
||||||
|
The connection type for [`TimeTrackingTimelogCategory`](#timetrackingtimelogcategory).
|
||||||
|
|
||||||
|
##### Fields
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| <a id="timetrackingtimelogcategoryconnectionedges"></a>`edges` | [`[TimeTrackingTimelogCategoryEdge]`](#timetrackingtimelogcategoryedge) | A list of edges. |
|
||||||
|
| <a id="timetrackingtimelogcategoryconnectionnodes"></a>`nodes` | [`[TimeTrackingTimelogCategory]`](#timetrackingtimelogcategory) | A list of nodes. |
|
||||||
|
| <a id="timetrackingtimelogcategoryconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||||
|
|
||||||
|
#### `TimeTrackingTimelogCategoryEdge`
|
||||||
|
|
||||||
|
The edge type for [`TimeTrackingTimelogCategory`](#timetrackingtimelogcategory).
|
||||||
|
|
||||||
|
##### Fields
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| <a id="timetrackingtimelogcategoryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||||
|
| <a id="timetrackingtimelogcategoryedgenode"></a>`node` | [`TimeTrackingTimelogCategory`](#timetrackingtimelogcategory) | The item at the end of the edge. |
|
||||||
|
|
||||||
#### `TimelineEventTypeConnection`
|
#### `TimelineEventTypeConnection`
|
||||||
|
|
||||||
The connection type for [`TimelineEventType`](#timelineeventtype).
|
The connection type for [`TimelineEventType`](#timelineeventtype).
|
||||||
|
@ -12157,6 +12180,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
||||||
| <a id="groupstoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | Total storage limit of the root namespace in bytes. |
|
| <a id="groupstoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | Total storage limit of the root namespace in bytes. |
|
||||||
| <a id="groupsubgroupcreationlevel"></a>`subgroupCreationLevel` | [`String`](#string) | Permission level required to create subgroups within the group. |
|
| <a id="groupsubgroupcreationlevel"></a>`subgroupCreationLevel` | [`String`](#string) | Permission level required to create subgroups within the group. |
|
||||||
| <a id="grouptemporarystorageincreaseendson"></a>`temporaryStorageIncreaseEndsOn` | [`Time`](#time) | Date until the temporary storage increase is active. |
|
| <a id="grouptemporarystorageincreaseendson"></a>`temporaryStorageIncreaseEndsOn` | [`Time`](#time) | Date until the temporary storage increase is active. |
|
||||||
|
| <a id="grouptimelogcategories"></a>`timelogCategories` | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | Timelog categories for the namespace. Available only when feature flag `timelog_categories` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
|
||||||
| <a id="grouptotalrepositorysize"></a>`totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. |
|
| <a id="grouptotalrepositorysize"></a>`totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. |
|
||||||
| <a id="grouptotalrepositorysizeexcess"></a>`totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. |
|
| <a id="grouptotalrepositorysizeexcess"></a>`totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. |
|
||||||
| <a id="grouptwofactorgraceperiod"></a>`twoFactorGracePeriod` | [`Int`](#int) | Time before two-factor authentication is enforced. |
|
| <a id="grouptwofactorgraceperiod"></a>`twoFactorGracePeriod` | [`Int`](#int) | Time before two-factor authentication is enforced. |
|
||||||
|
@ -14727,6 +14751,7 @@ Contains statistics about a milestone.
|
||||||
| <a id="namespacesharedrunnerssetting"></a>`sharedRunnersSetting` | [`SharedRunnersSetting`](#sharedrunnerssetting) | Shared runners availability for the namespace and its descendants. |
|
| <a id="namespacesharedrunnerssetting"></a>`sharedRunnersSetting` | [`SharedRunnersSetting`](#sharedrunnerssetting) | Shared runners availability for the namespace and its descendants. |
|
||||||
| <a id="namespacestoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | Total storage limit of the root namespace in bytes. |
|
| <a id="namespacestoragesizelimit"></a>`storageSizeLimit` | [`Float`](#float) | Total storage limit of the root namespace in bytes. |
|
||||||
| <a id="namespacetemporarystorageincreaseendson"></a>`temporaryStorageIncreaseEndsOn` | [`Time`](#time) | Date until the temporary storage increase is active. |
|
| <a id="namespacetemporarystorageincreaseendson"></a>`temporaryStorageIncreaseEndsOn` | [`Time`](#time) | Date until the temporary storage increase is active. |
|
||||||
|
| <a id="namespacetimelogcategories"></a>`timelogCategories` | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | Timelog categories for the namespace. Available only when feature flag `timelog_categories` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
|
||||||
| <a id="namespacetotalrepositorysize"></a>`totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. |
|
| <a id="namespacetotalrepositorysize"></a>`totalRepositorySize` | [`Float`](#float) | Total repository size of all projects in the root namespace in bytes. |
|
||||||
| <a id="namespacetotalrepositorysizeexcess"></a>`totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. |
|
| <a id="namespacetotalrepositorysizeexcess"></a>`totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. |
|
||||||
| <a id="namespacevisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
|
| <a id="namespacevisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
|
||||||
|
@ -15493,6 +15518,7 @@ Represents vulnerability finding of a security report on the pipeline.
|
||||||
| <a id="projectsuggestioncommitmessage"></a>`suggestionCommitMessage` | [`String`](#string) | Commit message used to apply merge request suggestions. |
|
| <a id="projectsuggestioncommitmessage"></a>`suggestionCommitMessage` | [`String`](#string) | Commit message used to apply merge request suggestions. |
|
||||||
| <a id="projecttaglist"></a>`tagList` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.12. Use `topics`. |
|
| <a id="projecttaglist"></a>`tagList` **{warning-solid}** | [`String`](#string) | **Deprecated** in 13.12. Use `topics`. |
|
||||||
| <a id="projectterraformstates"></a>`terraformStates` | [`TerraformStateConnection`](#terraformstateconnection) | Terraform states associated with the project. (see [Connections](#connections)) |
|
| <a id="projectterraformstates"></a>`terraformStates` | [`TerraformStateConnection`](#terraformstateconnection) | Terraform states associated with the project. (see [Connections](#connections)) |
|
||||||
|
| <a id="projecttimelogcategories"></a>`timelogCategories` | [`TimeTrackingTimelogCategoryConnection`](#timetrackingtimelogcategoryconnection) | Timelog categories for the project. Available only when feature flag `timelog_categories` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. (see [Connections](#connections)) |
|
||||||
| <a id="projecttopics"></a>`topics` | [`[String!]`](#string) | List of project topics. |
|
| <a id="projecttopics"></a>`topics` | [`[String!]`](#string) | List of project topics. |
|
||||||
| <a id="projectuserpermissions"></a>`userPermissions` | [`ProjectPermissions!`](#projectpermissions) | Permissions for the current user on the resource. |
|
| <a id="projectuserpermissions"></a>`userPermissions` | [`ProjectPermissions!`](#projectpermissions) | Permissions for the current user on the resource. |
|
||||||
| <a id="projectvisibility"></a>`visibility` | [`String`](#string) | Visibility of the project. |
|
| <a id="projectvisibility"></a>`visibility` | [`String`](#string) | Visibility of the project. |
|
||||||
|
@ -17733,6 +17759,21 @@ Represents the time report stats for timeboxes.
|
||||||
| <a id="timereportstatsincomplete"></a>`incomplete` | [`TimeboxMetrics`](#timeboxmetrics) | Incomplete issues metrics. |
|
| <a id="timereportstatsincomplete"></a>`incomplete` | [`TimeboxMetrics`](#timeboxmetrics) | Incomplete issues metrics. |
|
||||||
| <a id="timereportstatstotal"></a>`total` | [`TimeboxMetrics`](#timeboxmetrics) | Total issues metrics. |
|
| <a id="timereportstatstotal"></a>`total` | [`TimeboxMetrics`](#timeboxmetrics) | Total issues metrics. |
|
||||||
|
|
||||||
|
### `TimeTrackingTimelogCategory`
|
||||||
|
|
||||||
|
#### Fields
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| <a id="timetrackingtimelogcategorybillable"></a>`billable` | [`Boolean`](#boolean) | Whether the category is billable or not. |
|
||||||
|
| <a id="timetrackingtimelogcategorybillingrate"></a>`billingRate` | [`Float`](#float) | Billing rate for the category. |
|
||||||
|
| <a id="timetrackingtimelogcategorycolor"></a>`color` | [`Color`](#color) | Color assigned to the category. |
|
||||||
|
| <a id="timetrackingtimelogcategorycreatedat"></a>`createdAt` | [`Time!`](#time) | When the category was created. |
|
||||||
|
| <a id="timetrackingtimelogcategorydescription"></a>`description` | [`String`](#string) | Description of the category. |
|
||||||
|
| <a id="timetrackingtimelogcategoryid"></a>`id` | [`ID!`](#id) | Internal ID of the timelog category. |
|
||||||
|
| <a id="timetrackingtimelogcategoryname"></a>`name` | [`String!`](#string) | Name of the category. |
|
||||||
|
| <a id="timetrackingtimelogcategoryupdatedat"></a>`updatedAt` | [`Time!`](#time) | When the category was last updated. |
|
||||||
|
|
||||||
### `TimeboxMetrics`
|
### `TimeboxMetrics`
|
||||||
|
|
||||||
Represents measured stats metrics for timeboxes.
|
Represents measured stats metrics for timeboxes.
|
||||||
|
|
|
@ -48,6 +48,50 @@ To disable it:
|
||||||
Feature.disable(:execute_batched_migrations_on_schedule)
|
Feature.disable(:execute_batched_migrations_on_schedule)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Pause batched background migrations in GitLab 14.x
|
||||||
|
|
||||||
|
To pause an ongoing batched background migration, use the `disable` command above.
|
||||||
|
This command causes the migration to complete the current batch, and then wait to start the next batch.
|
||||||
|
|
||||||
|
Use the following database queries to see the state of the current batched background migration:
|
||||||
|
|
||||||
|
1. Obtain the ID of the running migration:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
id job_class_name,
|
||||||
|
table_name,
|
||||||
|
column_name,
|
||||||
|
job_arguments
|
||||||
|
FROM batched_background_migrations
|
||||||
|
WHERE status <> 3;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Run this query, replacing `XX` with the ID you obtained in the previous step,
|
||||||
|
to see the status of the migration:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
started_at,
|
||||||
|
finished_at,
|
||||||
|
finished_at - started_at AS duration,
|
||||||
|
min_value,
|
||||||
|
max_value,
|
||||||
|
batch_size,
|
||||||
|
sub_batch_size
|
||||||
|
FROM batched_background_migration_jobs
|
||||||
|
WHERE batched_background_migration_id = XX
|
||||||
|
ORDER BY id DESC
|
||||||
|
limit 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Run the query multiple times within a few minutes to ensure no new row has been added.
|
||||||
|
If no new row has been added, the migration has been paused.
|
||||||
|
|
||||||
|
1. After confirming the migration has paused, restart the migration (using the `enable`
|
||||||
|
command above) to proceed with the batch when ready. On larger instances,
|
||||||
|
background migrations can take as long as 48 hours to complete each batch.
|
||||||
|
|
||||||
## Automatic batch size optimization
|
## Automatic batch size optimization
|
||||||
|
|
||||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60133) in GitLab 13.12.
|
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/60133) in GitLab 13.12.
|
||||||
|
|
|
@ -1,27 +1,11 @@
|
||||||
---
|
---
|
||||||
stage: Monitor
|
redirect_to: '../../../../../operations/metrics/index.md'
|
||||||
group: Respond
|
remove_date: '2022-11-03'
|
||||||
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
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Install Prometheus with a cluster management project **(FREE)**
|
This document was moved to [another location](../../../../../operations/metrics/index.md).
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/project-templates/cluster-management/-/merge_requests/5) in GitLab 14.0.
|
<!-- This redirect file can be deleted after <2022-11-03>. -->
|
||||||
|
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||||
[Prometheus](https://prometheus.io/docs/introduction/overview/) is an
|
<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
|
||||||
open-source monitoring and alerting system for supervising your
|
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||||
deployed applications.
|
|
||||||
|
|
||||||
Assuming you already have a project created from a
|
|
||||||
[management project template](../../../../../user/clusters/management_project_template.md), to install Prometheus you should
|
|
||||||
uncomment this line from your `helmfile.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- path: applications/prometheus/helmfile.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
You can customize the installation of Prometheus by updating the
|
|
||||||
`applications/prometheus/values.yaml` file in your cluster
|
|
||||||
management project. Refer to the
|
|
||||||
[Configuration section](https://github.com/helm/charts/tree/master/stable/prometheus#configuration)
|
|
||||||
of the Prometheus chart's README for the available configuration options.
|
|
||||||
|
|
|
@ -1,70 +1,11 @@
|
||||||
---
|
---
|
||||||
stage: Monitor
|
redirect_to: '../../../../../operations/error_tracking.md'
|
||||||
group: Respond
|
remove_date: '2022-11-03'
|
||||||
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
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Install Sentry with a cluster management project **(FREE)**
|
This document was moved to [another location](../../../../../operations/error_tracking.md).
|
||||||
|
|
||||||
> [Introduced](https://gitlab.com/gitlab-org/project-templates/cluster-management/-/merge_requests/5) in GitLab 14.0.
|
<!-- This redirect file can be deleted after <2022-11-03>. -->
|
||||||
|
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||||
The Sentry Helm chart [recommends](https://github.com/helm/charts/blob/f6e5784f265dd459c5a77430185d0302ed372665/stable/sentry/values.yaml#L284-L285)
|
<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
|
||||||
at least 3 GB of available RAM for database migrations.
|
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||||
|
|
||||||
Assuming you already have a project created from a
|
|
||||||
[management project template](../../../../../user/clusters/management_project_template.md), to install Sentry you should
|
|
||||||
uncomment this line from your `helmfile.yaml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- path: applications/sentry/helmfile.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
Sentry is installed by default into the `gitlab-managed-apps` namespace
|
|
||||||
of your cluster.
|
|
||||||
|
|
||||||
You can customize the installation of Sentry by defining
|
|
||||||
`applications/sentry/values.yaml` file in your cluster
|
|
||||||
management project. Refer to the
|
|
||||||
[chart](https://github.com/helm/charts/tree/master/stable/sentry)
|
|
||||||
for the available configuration options.
|
|
||||||
|
|
||||||
We recommend you pay close attention to the following configuration options:
|
|
||||||
|
|
||||||
- `email`. Needed to invite users to your Sentry instance and to send error emails.
|
|
||||||
- `user`. Where you can set the login credentials for the default administrator user.
|
|
||||||
- `postgresql`. For a PostgreSQL password that can be used when running future updates.
|
|
||||||
|
|
||||||
When upgrading, it's important to provide the existing PostgreSQL password (given
|
|
||||||
using the `postgresql.postgresqlPassword` key) to avoid authentication errors.
|
|
||||||
Read the [PostgreSQL chart documentation](https://github.com/helm/charts/tree/master/stable/postgresql#upgrade)
|
|
||||||
for more information.
|
|
||||||
|
|
||||||
Here is an example configuration for Sentry:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Admin user to create
|
|
||||||
user:
|
|
||||||
# Indicated to create the admin user or not,
|
|
||||||
# Default is true as the initial installation.
|
|
||||||
create: true
|
|
||||||
email: "<your email>"
|
|
||||||
password: "<your password>"
|
|
||||||
|
|
||||||
email:
|
|
||||||
from_address: "<your from email>"
|
|
||||||
host: smtp
|
|
||||||
port: 25
|
|
||||||
use_tls: false
|
|
||||||
user: "<your email username>"
|
|
||||||
password: "<your email password>"
|
|
||||||
enable_replies: false
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: true
|
|
||||||
hostname: "<sentry.example.com>"
|
|
||||||
|
|
||||||
# Needs to be here between runs.
|
|
||||||
# See https://github.com/helm/charts/tree/master/stable/postgresql#upgrade for more info
|
|
||||||
postgresql:
|
|
||||||
postgresqlPassword: example-postgresql-password
|
|
||||||
```
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ module BulkImports
|
||||||
end
|
end
|
||||||
|
|
||||||
def each_page(method, resource, query = {}, &block)
|
def each_page(method, resource, query = {}, &block)
|
||||||
return to_enum(__method__, method, resource, query) unless block_given?
|
return to_enum(__method__, method, resource, query) unless block
|
||||||
|
|
||||||
next_page = @page
|
next_page = @page
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ module Gitlab
|
||||||
begin
|
begin
|
||||||
all_args = pop_all
|
all_args = pop_all
|
||||||
|
|
||||||
yield all_args if block_given?
|
yield all_args if block
|
||||||
|
|
||||||
{ status: :finished, new_items: peek_all }
|
{ status: :finished, new_items: peek_all }
|
||||||
ensure
|
ensure
|
||||||
|
|
|
@ -15,7 +15,7 @@ module Gitlab
|
||||||
attr_accessor :request_cache_key_block
|
attr_accessor :request_cache_key_block
|
||||||
|
|
||||||
def request_cache_key(&block)
|
def request_cache_key(&block)
|
||||||
if block_given?
|
if block
|
||||||
self.request_cache_key_block = block
|
self.request_cache_key_block = block
|
||||||
else
|
else
|
||||||
request_cache_key_block
|
request_cache_key_block
|
||||||
|
|
|
@ -22,7 +22,7 @@ module Gitlab
|
||||||
@chunks_cache = []
|
@chunks_cache = []
|
||||||
@tell = 0
|
@tell = 0
|
||||||
@size = calculate_size
|
@size = calculate_size
|
||||||
yield self if block_given?
|
yield self if block
|
||||||
end
|
end
|
||||||
|
|
||||||
def close
|
def close
|
||||||
|
|
|
@ -157,7 +157,7 @@ module Gitlab
|
||||||
def self.execute(columns, mapping, &to_class)
|
def self.execute(columns, mapping, &to_class)
|
||||||
raise ArgumentError if mapping.blank?
|
raise ArgumentError if mapping.blank?
|
||||||
|
|
||||||
entries_by_class = mapping.group_by { |k, v| block_given? ? to_class.call(k) : k.class }
|
entries_by_class = mapping.group_by { |k, v| to_class ? to_class.call(k) : k.class }
|
||||||
|
|
||||||
entries_by_class.each do |model, entries|
|
entries_by_class.each do |model, entries|
|
||||||
Setter.new(model, columns, entries).update!
|
Setter.new(model, columns, entries).update!
|
||||||
|
|
|
@ -83,7 +83,7 @@ module Gitlab
|
||||||
# @param [Boolean] raise_on_exhaustion whether to raise `AttemptsExhaustedError` when exhausting max attempts
|
# @param [Boolean] raise_on_exhaustion whether to raise `AttemptsExhaustedError` when exhausting max attempts
|
||||||
# @param [Proc] block of code that will be executed
|
# @param [Proc] block of code that will be executed
|
||||||
def run(raise_on_exhaustion: false, &block)
|
def run(raise_on_exhaustion: false, &block)
|
||||||
raise 'no block given' unless block_given?
|
raise 'no block given' unless block
|
||||||
|
|
||||||
@block = block
|
@block = block
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ module Gitlab
|
||||||
#
|
#
|
||||||
# rubocop: disable GitlabSecurity/PublicSend
|
# rubocop: disable GitlabSecurity/PublicSend
|
||||||
def each_page(method, *args, &block)
|
def each_page(method, *args, &block)
|
||||||
return to_enum(__method__, method, *args) unless block_given?
|
return to_enum(__method__, method, *args) unless block
|
||||||
|
|
||||||
page =
|
page =
|
||||||
if args.last.is_a?(Hash) && args.last[:page]
|
if args.last.is_a?(Hash) && args.last[:page]
|
||||||
|
@ -134,7 +134,7 @@ module Gitlab
|
||||||
# method - The method to send to Octokit for querying data.
|
# method - The method to send to Octokit for querying data.
|
||||||
# args - Any arguments to pass to the Octokit method.
|
# args - Any arguments to pass to the Octokit method.
|
||||||
def each_object(method, *args, &block)
|
def each_object(method, *args, &block)
|
||||||
return to_enum(__method__, method, *args) unless block_given?
|
return to_enum(__method__, method, *args) unless block
|
||||||
|
|
||||||
each_page(method, *args) do |page|
|
each_page(method, *args) do |page|
|
||||||
page.objects.each do |object|
|
page.objects.each do |object|
|
||||||
|
|
|
@ -136,7 +136,7 @@ module Gitlab
|
||||||
|
|
||||||
last_response = api.last_response
|
last_response = api.last_response
|
||||||
|
|
||||||
if block_given?
|
if block
|
||||||
yield data
|
yield data
|
||||||
# api.last_response could change while we're yielding (e.g. fetching labels for each PR)
|
# api.last_response could change while we're yielding (e.g. fetching labels for each PR)
|
||||||
# so we cache our own last response
|
# so we cache our own last response
|
||||||
|
|
|
@ -61,7 +61,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def evaluate(&block)
|
def evaluate(&block)
|
||||||
instance_eval(&block) if block_given?
|
instance_eval(&block) if block
|
||||||
|
|
||||||
self
|
self
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,7 +35,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(key, &block)
|
def delete(key, &block)
|
||||||
yield(key) if block_given?
|
yield(key) if block
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,11 +30,11 @@ module Gitlab
|
||||||
# # Awesome code block
|
# # Awesome code block
|
||||||
# end
|
# end
|
||||||
def desc(text = '', &block)
|
def desc(text = '', &block)
|
||||||
@description = block_given? ? block : text
|
@description = block || text
|
||||||
end
|
end
|
||||||
|
|
||||||
def warning(text = '', &block)
|
def warning(text = '', &block)
|
||||||
@warning = block_given? ? block : text
|
@warning = block || text
|
||||||
end
|
end
|
||||||
|
|
||||||
def icon(string = '')
|
def icon(string = '')
|
||||||
|
@ -51,7 +51,7 @@ module Gitlab
|
||||||
# # Awesome code block
|
# # Awesome code block
|
||||||
# end
|
# end
|
||||||
def params(*params, &block)
|
def params(*params, &block)
|
||||||
@params = block_given? ? block : params
|
@params = block || params
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allows to give an explanation of what the command will do when
|
# Allows to give an explanation of what the command will do when
|
||||||
|
@ -67,7 +67,7 @@ module Gitlab
|
||||||
# # Awesome code block
|
# # Awesome code block
|
||||||
# end
|
# end
|
||||||
def explanation(text = '', &block)
|
def explanation(text = '', &block)
|
||||||
@explanation = block_given? ? block : text
|
@explanation = block || text
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allows to provide a message about quick action execution result, success or failure.
|
# Allows to provide a message about quick action execution result, success or failure.
|
||||||
|
@ -96,7 +96,7 @@ module Gitlab
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
def execution_message(text = '', &block)
|
def execution_message(text = '', &block)
|
||||||
@execution_message = block_given? ? block : text
|
@execution_message = block || text
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allows to define type(s) that must be met in order for the command
|
# Allows to define type(s) that must be met in order for the command
|
||||||
|
|
|
@ -274,7 +274,7 @@ module Gitlab
|
||||||
|
|
||||||
# rubocop:disable GitlabSecurity/PublicSend
|
# rubocop:disable GitlabSecurity/PublicSend
|
||||||
def send_command(redis_instance, command_name, *args, **kwargs, &block)
|
def send_command(redis_instance, command_name, *args, **kwargs, &block)
|
||||||
if block_given?
|
if block
|
||||||
# Make sure that block is wrapped and executed only on the redis instance that is executing the block
|
# Make sure that block is wrapped and executed only on the redis instance that is executing the block
|
||||||
redis_instance.send(command_name, *args, **kwargs) do |*params|
|
redis_instance.send(command_name, *args, **kwargs) do |*params|
|
||||||
with_instance(redis_instance, *params, &block)
|
with_instance(redis_instance, *params, &block)
|
||||||
|
|
|
@ -14,7 +14,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute(&block)
|
def execute(&block)
|
||||||
raise ArgumentError, 'Block is mandatory' unless block_given?
|
raise ArgumentError, 'Block is mandatory' unless block
|
||||||
|
|
||||||
load_resource_data
|
load_resource_data
|
||||||
remove_loaded_resource_ids
|
remove_loaded_resource_ids
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Gitlab
|
||||||
@filters = []
|
@filters = []
|
||||||
@filter_options = { default_parser: :downcase.to_proc }.merge(filter_opts)
|
@filter_options = { default_parser: :downcase.to_proc }.merge(filter_opts)
|
||||||
|
|
||||||
self.instance_eval(&block) if block_given?
|
self.instance_eval(&block) if block
|
||||||
|
|
||||||
@query = Gitlab::Search::ParsedQuery.new(*extract_filters)
|
@query = Gitlab::Search::ParsedQuery.new(*extract_filters)
|
||||||
# set the ParsedQuery as our default delegator thanks to SimpleDelegator
|
# set the ParsedQuery as our default delegator thanks to SimpleDelegator
|
||||||
|
|
|
@ -10,7 +10,7 @@ module Gitlab
|
||||||
# placeholder will be returned.
|
# placeholder will be returned.
|
||||||
|
|
||||||
def self.replace_string_placeholders(string, placeholder_regex = nil, &block)
|
def self.replace_string_placeholders(string, placeholder_regex = nil, &block)
|
||||||
return string if string.blank? || placeholder_regex.blank? || !block_given?
|
return string if string.blank? || placeholder_regex.blank? || !block
|
||||||
|
|
||||||
replace_placeholders(string, placeholder_regex, &block)
|
replace_placeholders(string, placeholder_regex, &block)
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ module Gitlab
|
||||||
versions.find_each(batch_size: batch_size) do |version| # rubocop:disable CodeReuse/ActiveRecord
|
versions.find_each(batch_size: batch_size) do |version| # rubocop:disable CodeReuse/ActiveRecord
|
||||||
version.file.migrate!(store)
|
version.file.migrate!(store)
|
||||||
|
|
||||||
yield version if block_given?
|
yield version if block
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Gitlab
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def available?(&block)
|
def available?(&block)
|
||||||
return @metric_available = block if block_given?
|
return @metric_available = block if block
|
||||||
|
|
||||||
return @metric_available.call if instance_variable_defined?('@metric_available')
|
return @metric_available.call if instance_variable_defined?('@metric_available')
|
||||||
|
|
||||||
|
|
|
@ -23,25 +23,25 @@ module Gitlab
|
||||||
private_constant :IMPLEMENTED_OPERATIONS
|
private_constant :IMPLEMENTED_OPERATIONS
|
||||||
|
|
||||||
def start(&block)
|
def start(&block)
|
||||||
return @metric_start&.call unless block_given?
|
return @metric_start&.call unless block
|
||||||
|
|
||||||
@metric_start = block
|
@metric_start = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def finish(&block)
|
def finish(&block)
|
||||||
return @metric_finish&.call unless block_given?
|
return @metric_finish&.call unless block
|
||||||
|
|
||||||
@metric_finish = block
|
@metric_finish = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def relation(&block)
|
def relation(&block)
|
||||||
return @metric_relation&.call unless block_given?
|
return @metric_relation&.call unless block
|
||||||
|
|
||||||
@metric_relation = block
|
@metric_relation = block
|
||||||
end
|
end
|
||||||
|
|
||||||
def metric_options(&block)
|
def metric_options(&block)
|
||||||
return @metric_options&.call.to_h unless block_given?
|
return @metric_options&.call.to_h unless block
|
||||||
|
|
||||||
@metric_options = block
|
@metric_options = block
|
||||||
end
|
end
|
||||||
|
@ -55,7 +55,7 @@ module Gitlab
|
||||||
|
|
||||||
@metric_operation = symbol
|
@metric_operation = symbol
|
||||||
@column = column
|
@column = column
|
||||||
@metric_operation_block = block if block_given?
|
@metric_operation_block = block if block
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_start_and_finish_as(cache_key)
|
def cache_start_and_finish_as(cache_key)
|
||||||
|
|
|
@ -26,7 +26,7 @@ module Gitlab
|
||||||
private_constant :IMPLEMENTED_OPERATIONS
|
private_constant :IMPLEMENTED_OPERATIONS
|
||||||
|
|
||||||
def data(&block)
|
def data(&block)
|
||||||
return @metric_data&.call unless block_given?
|
return @metric_data&.call unless block
|
||||||
|
|
||||||
@metric_data = block
|
@metric_data = block
|
||||||
end
|
end
|
||||||
|
|
|
@ -53,7 +53,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def alt_usage_data(value = nil, fallback: FALLBACK, &block)
|
def alt_usage_data(value = nil, fallback: FALLBACK, &block)
|
||||||
if block_given?
|
if block
|
||||||
{ alt_usage_data_block: "non-SQL usage data block" }
|
{ alt_usage_data_block: "non-SQL usage data block" }
|
||||||
else
|
else
|
||||||
{ alt_usage_data_value: value }
|
{ alt_usage_data_value: value }
|
||||||
|
@ -61,7 +61,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def redis_usage_data(counter = nil, &block)
|
def redis_usage_data(counter = nil, &block)
|
||||||
if block_given?
|
if block
|
||||||
{ redis_usage_data_block: "non-SQL usage data block" }
|
{ redis_usage_data_block: "non-SQL usage data block" }
|
||||||
elsif counter.present?
|
elsif counter.present?
|
||||||
{ redis_usage_data_counter: counter.to_s }
|
{ redis_usage_data_counter: counter.to_s }
|
||||||
|
|
|
@ -196,7 +196,7 @@ module Gitlab
|
||||||
|
|
||||||
def alt_usage_data(value = nil, fallback: FALLBACK, &block)
|
def alt_usage_data(value = nil, fallback: FALLBACK, &block)
|
||||||
with_duration do
|
with_duration do
|
||||||
if block_given?
|
if block
|
||||||
yield
|
yield
|
||||||
else
|
else
|
||||||
value
|
value
|
||||||
|
@ -209,7 +209,7 @@ module Gitlab
|
||||||
|
|
||||||
def redis_usage_data(counter = nil, &block)
|
def redis_usage_data(counter = nil, &block)
|
||||||
with_duration do
|
with_duration do
|
||||||
if block_given?
|
if block
|
||||||
redis_usage_counter(&block)
|
redis_usage_counter(&block)
|
||||||
elsif counter.present?
|
elsif counter.present?
|
||||||
redis_usage_data_totals(counter)
|
redis_usage_data_totals(counter)
|
||||||
|
|
|
@ -16895,6 +16895,9 @@ msgstr ""
|
||||||
msgid "Geo|%{component} verified"
|
msgid "Geo|%{component} verified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Geo|%{label} %{timeAgo}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Geo|%{label} can't be blank"
|
msgid "Geo|%{label} can't be blank"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -23016,9 +23019,6 @@ msgstr ""
|
||||||
msgid "Last sign-in at:"
|
msgid "Last sign-in at:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Last successful sync"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Last successful update"
|
msgid "Last successful update"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -26432,9 +26432,6 @@ msgstr ""
|
||||||
msgid "Normal text"
|
msgid "Normal text"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Not Implemented"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Not all browsers support U2F devices. Therefore, we require that you set up a two-factor authentication app first. That way you'll always be able to sign in - even when you're using an unsupported browser."
|
msgid "Not all browsers support U2F devices. Therefore, we require that you set up a two-factor authentication app first. That way you'll always be able to sign in - even when you're using an unsupported browser."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
"lowlight": "^2.6.1",
|
"lowlight": "^2.6.1",
|
||||||
"marked": "^0.3.12",
|
"marked": "^0.3.12",
|
||||||
"mathjax": "3",
|
"mathjax": "3",
|
||||||
"mermaid": "^9.1.1",
|
"mermaid": "^9.1.3",
|
||||||
"micromatch": "^4.0.5",
|
"micromatch": "^4.0.5",
|
||||||
"minimatch": "^3.0.4",
|
"minimatch": "^3.0.4",
|
||||||
"monaco-editor": "^0.28.0",
|
"monaco-editor": "^0.28.0",
|
||||||
|
|
|
@ -37,7 +37,7 @@ module QA
|
||||||
|
|
||||||
def self.evaluate(&block)
|
def self.evaluate(&block)
|
||||||
Page::View::DSL.new.tap do |evaluator|
|
Page::View::DSL.new.tap do |evaluator|
|
||||||
evaluator.instance_exec(&block) if block_given?
|
evaluator.instance_exec(&block) if block
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ RSpec.describe GitlabSchema.types['Namespace'] do
|
||||||
expected_fields = %w[
|
expected_fields = %w[
|
||||||
id name path full_name full_path description description_html visibility
|
id name path full_name full_path description description_html visibility
|
||||||
lfs_enabled request_access_enabled projects root_storage_statistics shared_runners_setting
|
lfs_enabled request_access_enabled projects root_storage_statistics shared_runners_setting
|
||||||
|
timelog_categories
|
||||||
]
|
]
|
||||||
|
|
||||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||||
|
|
|
@ -36,8 +36,7 @@ RSpec.describe GitlabSchema.types['Project'] do
|
||||||
pipeline_analytics squash_read_only sast_ci_configuration
|
pipeline_analytics squash_read_only sast_ci_configuration
|
||||||
cluster_agent cluster_agents agent_configurations
|
cluster_agent cluster_agents agent_configurations
|
||||||
ci_template timelogs merge_commit_template squash_commit_template work_item_types
|
ci_template timelogs merge_commit_template squash_commit_template work_item_types
|
||||||
recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables
|
recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables timelog_categories
|
||||||
recent_issue_boards ci_config_path_or_default ci_variables
|
|
||||||
]
|
]
|
||||||
|
|
||||||
expect(described_class).to include_graphql_fields(*expected_fields)
|
expect(described_class).to include_graphql_fields(*expected_fields)
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe GitlabSchema.types['TimeTrackingTimelogCategory'] do
|
||||||
|
let(:fields) do
|
||||||
|
%w[
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
color
|
||||||
|
billable
|
||||||
|
billing_rate
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(described_class.graphql_name).to eq('TimeTrackingTimelogCategory') }
|
||||||
|
it { expect(described_class).to have_graphql_fields(fields) }
|
||||||
|
it { expect(described_class).to require_graphql_authorizations(:read_timelog_category) }
|
||||||
|
end
|
|
@ -34,7 +34,7 @@ RSpec.describe API::Helpers::Authentication do
|
||||||
class << cls
|
class << cls
|
||||||
def helpers(*modules, &block)
|
def helpers(*modules, &block)
|
||||||
modules.each { |m| include m }
|
modules.each { |m| include m }
|
||||||
include Module.new.tap { |m| m.class_eval(&block) } if block_given?
|
include Module.new.tap { |m| m.class_eval(&block) } if block
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -165,7 +165,7 @@ RSpec.describe Gitlab::SlashCommands::Deploy do
|
||||||
context 'with ReDoS attempts' do
|
context 'with ReDoS attempts' do
|
||||||
def duration_for(&block)
|
def duration_for(&block)
|
||||||
start = Time.zone.now
|
start = Time.zone.now
|
||||||
yield if block_given?
|
yield if block
|
||||||
Time.zone.now - start
|
Time.zone.now - start
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,14 @@ require 'spec_helper'
|
||||||
RSpec.describe Namespaces::ProjectNamespace, type: :model do
|
RSpec.describe Namespaces::ProjectNamespace, type: :model do
|
||||||
describe 'relationships' do
|
describe 'relationships' do
|
||||||
it { is_expected.to have_one(:project).with_foreign_key(:project_namespace_id).inverse_of(:project_namespace) }
|
it { is_expected.to have_one(:project).with_foreign_key(:project_namespace_id).inverse_of(:project_namespace) }
|
||||||
|
|
||||||
|
specify do
|
||||||
|
project = create(:project)
|
||||||
|
namespace = project.project_namespace
|
||||||
|
namespace.reload_project
|
||||||
|
|
||||||
|
expect(namespace.project).to eq project
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
|
|
|
@ -6,21 +6,67 @@ RSpec.describe U2fRegistration do
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
let(:u2f_registration_name) { 'u2f_device' }
|
let(:u2f_registration_name) { 'u2f_device' }
|
||||||
let(:u2f_registration) do
|
let(:app_id) { FFaker::BaconIpsum.characters(5) }
|
||||||
device = U2F::FakeU2F.new(FFaker::BaconIpsum.characters(5))
|
let(:device) { U2F::FakeU2F.new(app_id) }
|
||||||
create(
|
|
||||||
:u2f_registration, name: u2f_registration_name,
|
describe '.authenticate' do
|
||||||
user: user,
|
context 'when registration is found' do
|
||||||
certificate: Base64.strict_encode64(device.cert_raw),
|
it 'returns true' do
|
||||||
key_handle: U2F.urlsafe_encode64(device.key_handle_raw),
|
create_u2f_registration
|
||||||
public_key: Base64.strict_encode64(device.origin_public_key_raw)
|
device_challenge = U2F.urlsafe_encode64(SecureRandom.random_bytes(32))
|
||||||
)
|
sign_response_json = device.sign_response(device_challenge)
|
||||||
|
|
||||||
|
response = U2fRegistration.authenticate(
|
||||||
|
user,
|
||||||
|
app_id,
|
||||||
|
sign_response_json,
|
||||||
|
device_challenge
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).to eq true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when registration not found' do
|
||||||
|
it 'returns nil' do
|
||||||
|
device_challenge = U2F.urlsafe_encode64(SecureRandom.random_bytes(32))
|
||||||
|
sign_response_json = device.sign_response(device_challenge)
|
||||||
|
|
||||||
|
# data is valid but user does not have any u2f_registrations
|
||||||
|
response = U2fRegistration.authenticate(
|
||||||
|
user,
|
||||||
|
app_id,
|
||||||
|
sign_response_json,
|
||||||
|
device_challenge
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).to eq nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when args passed in are invalid' do
|
||||||
|
it 'returns false' do
|
||||||
|
some_app_id = 123
|
||||||
|
invalid_json = 'invalid JSON'
|
||||||
|
challenges = 'whatever'
|
||||||
|
|
||||||
|
response = U2fRegistration.authenticate(
|
||||||
|
user,
|
||||||
|
some_app_id,
|
||||||
|
invalid_json,
|
||||||
|
challenges
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response).to eq false
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'callbacks' do
|
describe 'callbacks' do
|
||||||
describe 'after create' do
|
describe 'after create' do
|
||||||
shared_examples_for 'creates webauthn registration' do
|
shared_examples_for 'creates webauthn registration' do
|
||||||
it 'creates webauthn registration' do
|
it 'creates webauthn registration' do
|
||||||
|
u2f_registration = create_u2f_registration
|
||||||
webauthn_registration = WebauthnRegistration.where(u2f_registration_id: u2f_registration.id)
|
webauthn_registration = WebauthnRegistration.where(u2f_registration_id: u2f_registration.id)
|
||||||
expect(webauthn_registration).to exist
|
expect(webauthn_registration).to exist
|
||||||
end
|
end
|
||||||
|
@ -51,13 +97,14 @@ RSpec.describe U2fRegistration do
|
||||||
receive(:track_exception).with(kind_of(StandardError),
|
receive(:track_exception).with(kind_of(StandardError),
|
||||||
u2f_registration_id: 123))
|
u2f_registration_id: 123))
|
||||||
|
|
||||||
u2f_registration
|
create_u2f_registration
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'after update' do
|
describe 'after update' do
|
||||||
context 'when counter is updated' do
|
context 'when counter is updated' do
|
||||||
it 'updates the webauthn registration counter to be the same value' do
|
it 'updates the webauthn registration counter to be the same value' do
|
||||||
|
u2f_registration = create_u2f_registration
|
||||||
new_counter = u2f_registration.counter + 1
|
new_counter = u2f_registration.counter + 1
|
||||||
webauthn_registration = WebauthnRegistration.find_by(u2f_registration_id: u2f_registration.id)
|
webauthn_registration = WebauthnRegistration.find_by(u2f_registration_id: u2f_registration.id)
|
||||||
|
|
||||||
|
@ -70,6 +117,7 @@ RSpec.describe U2fRegistration do
|
||||||
|
|
||||||
context 'when sign count of registration is not updated' do
|
context 'when sign count of registration is not updated' do
|
||||||
it 'does not update the counter' do
|
it 'does not update the counter' do
|
||||||
|
u2f_registration = create_u2f_registration
|
||||||
webauthn_registration = WebauthnRegistration.find_by(u2f_registration_id: u2f_registration.id)
|
webauthn_registration = WebauthnRegistration.find_by(u2f_registration_id: u2f_registration.id)
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
|
@ -79,4 +127,15 @@ RSpec.describe U2fRegistration do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_u2f_registration
|
||||||
|
create(
|
||||||
|
:u2f_registration,
|
||||||
|
name: u2f_registration_name,
|
||||||
|
user: user,
|
||||||
|
certificate: Base64.strict_encode64(device.cert_raw),
|
||||||
|
key_handle: U2F.urlsafe_encode64(device.key_handle_raw),
|
||||||
|
public_key: Base64.strict_encode64(device.origin_public_key_raw)
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1229,4 +1229,12 @@ RSpec.describe GroupPolicy do
|
||||||
it { is_expected.to be_disallowed(:admin_crm_contact) }
|
it { is_expected.to be_disallowed(:admin_crm_contact) }
|
||||||
it { is_expected.to be_disallowed(:admin_crm_organization) }
|
it { is_expected.to be_disallowed(:admin_crm_organization) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'checks timelog categories permissions' do
|
||||||
|
let(:group) { create(:group) }
|
||||||
|
let(:namespace) { group }
|
||||||
|
let(:users_container) { group }
|
||||||
|
|
||||||
|
subject { described_class.new(current_user, group) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,45 +3,11 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Namespaces::ProjectNamespacePolicy do
|
RSpec.describe Namespaces::ProjectNamespacePolicy do
|
||||||
let_it_be(:parent) { create(:namespace) }
|
|
||||||
let_it_be(:project) { create(:project, namespace: parent) }
|
|
||||||
let_it_be(:namespace) { project.project_namespace }
|
|
||||||
|
|
||||||
let(:permissions) do
|
|
||||||
[:owner_access, :create_projects, :admin_namespace, :read_namespace,
|
|
||||||
:read_statistics, :transfer_projects, :admin_package,
|
|
||||||
:create_jira_connect_subscription]
|
|
||||||
end
|
|
||||||
|
|
||||||
subject { described_class.new(current_user, namespace) }
|
subject { described_class.new(current_user, namespace) }
|
||||||
|
|
||||||
context 'with no user' do
|
it_behaves_like 'checks timelog categories permissions' do
|
||||||
let_it_be(:current_user) { nil }
|
let(:project) { create(:project) }
|
||||||
|
let(:namespace) { project.project_namespace }
|
||||||
it { is_expected.to be_disallowed(*permissions) }
|
let(:users_container) { project }
|
||||||
end
|
|
||||||
|
|
||||||
context 'regular user' do
|
|
||||||
let_it_be(:current_user) { create(:user) }
|
|
||||||
|
|
||||||
it { is_expected.to be_disallowed(*permissions) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'parent owner' do
|
|
||||||
let_it_be(:current_user) { parent.first_owner }
|
|
||||||
|
|
||||||
it { is_expected.to be_disallowed(*permissions) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'admin' do
|
|
||||||
let_it_be(:current_user) { create(:admin) }
|
|
||||||
|
|
||||||
context 'when admin mode is enabled', :enable_admin_mode do
|
|
||||||
it { is_expected.to be_disallowed(*permissions) }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when admin mode is disabled' do
|
|
||||||
it { is_expected.to be_disallowed(*permissions) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -122,6 +122,75 @@ RSpec.describe 'getting group information' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with timelog categories' do
|
||||||
|
let_it_be(:group) { create(:group) }
|
||||||
|
let_it_be(:timelog_category) { create(:timelog_category, namespace: group, name: 'TimelogCategoryTest') }
|
||||||
|
|
||||||
|
context 'when user is guest' do
|
||||||
|
it 'includes empty timelog categories array' do
|
||||||
|
post_graphql(group_query(group), current_user: user2)
|
||||||
|
|
||||||
|
expect(graphql_data_at(:group, :timelogCategories, :nodes)).to match([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user has reporter role' do
|
||||||
|
before do
|
||||||
|
group.add_reporter(user2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the timelog category with all its fields' do
|
||||||
|
post_graphql(group_query(group), current_user: user2)
|
||||||
|
|
||||||
|
expect(graphql_data_at(:group, :timelogCategories, :nodes))
|
||||||
|
.to contain_exactly(a_graphql_entity_for(timelog_category))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for N+1 queries' do
|
||||||
|
let!(:group1) { create(:group) }
|
||||||
|
let!(:group2) { create(:group) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
group1.add_reporter(user2)
|
||||||
|
group2.add_reporter(user2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'avoids N+1 database queries' do
|
||||||
|
pending('See: https://gitlab.com/gitlab-org/gitlab/-/issues/369396')
|
||||||
|
|
||||||
|
ctx = { current_user: user2 }
|
||||||
|
|
||||||
|
baseline_query = <<~GQL
|
||||||
|
query {
|
||||||
|
a: group(fullPath: "#{group1.full_path}") { ... g }
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment g on Group {
|
||||||
|
timelogCategories { nodes { name } }
|
||||||
|
}
|
||||||
|
GQL
|
||||||
|
|
||||||
|
query = <<~GQL
|
||||||
|
query {
|
||||||
|
a: group(fullPath: "#{group1.full_path}") { ... g }
|
||||||
|
b: group(fullPath: "#{group2.full_path}") { ... g }
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment g on Group {
|
||||||
|
timelogCategories { nodes { name } }
|
||||||
|
}
|
||||||
|
GQL
|
||||||
|
|
||||||
|
control = ActiveRecord::QueryRecorder.new do
|
||||||
|
run_with_clean_state(baseline_query, context: ctx)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect { run_with_clean_state(query, context: ctx) }.not_to exceed_query_limit(control)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "when authenticated as admin" do
|
context "when authenticated as admin" do
|
||||||
it "returns any existing group" do
|
it "returns any existing group" do
|
||||||
post_graphql(group_query(private_group), current_user: admin)
|
post_graphql(group_query(private_group), current_user: admin)
|
||||||
|
|
|
@ -190,4 +190,88 @@ RSpec.describe 'getting project information' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with timelog categories' do
|
||||||
|
let_it_be(:timelog_category) do
|
||||||
|
create(:timelog_category, namespace: project.project_namespace, name: 'TimelogCategoryTest')
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:project_fields) do
|
||||||
|
<<~GQL
|
||||||
|
timelogCategories {
|
||||||
|
nodes {
|
||||||
|
#{all_graphql_fields_for('TimeTrackingTimelogCategory')}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GQL
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user is guest and the project is public' do
|
||||||
|
before do
|
||||||
|
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes empty timelog categories array' do
|
||||||
|
post_graphql(query, current_user: current_user)
|
||||||
|
|
||||||
|
expect(graphql_data_at(:project, :timelogCategories, :nodes)).to match([])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user has reporter role' do
|
||||||
|
before do
|
||||||
|
project.add_reporter(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the timelog category with all its fields' do
|
||||||
|
post_graphql(query, current_user: current_user)
|
||||||
|
|
||||||
|
expect(graphql_data_at(:project, :timelogCategories, :nodes))
|
||||||
|
.to contain_exactly(a_graphql_entity_for(timelog_category))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'for N+1 queries' do
|
||||||
|
let!(:project1) { create(:project) }
|
||||||
|
let!(:project2) { create(:project) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project1.add_reporter(current_user)
|
||||||
|
project2.add_reporter(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'avoids N+1 database queries' do
|
||||||
|
pending('See: https://gitlab.com/gitlab-org/gitlab/-/issues/369396')
|
||||||
|
|
||||||
|
ctx = { current_user: current_user }
|
||||||
|
|
||||||
|
baseline_query = <<~GQL
|
||||||
|
query {
|
||||||
|
a: project(fullPath: "#{project1.full_path}") { ... p }
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment p on Project {
|
||||||
|
timelogCategories { nodes { name } }
|
||||||
|
}
|
||||||
|
GQL
|
||||||
|
|
||||||
|
query = <<~GQL
|
||||||
|
query {
|
||||||
|
a: project(fullPath: "#{project1.full_path}") { ... p }
|
||||||
|
b: project(fullPath: "#{project2.full_path}") { ... p }
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment p on Project {
|
||||||
|
timelogCategories { nodes { name } }
|
||||||
|
}
|
||||||
|
GQL
|
||||||
|
|
||||||
|
control = ActiveRecord::QueryRecorder.new do
|
||||||
|
run_with_clean_state(baseline_query, context: ctx)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect { run_with_clean_state(query, context: ctx) }.not_to exceed_query_limit(control)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,19 +30,28 @@ RSpec.describe Webauthn::AuthenticateService do
|
||||||
get_result['clientExtensionResults'] = {}
|
get_result['clientExtensionResults'] = {}
|
||||||
service = Webauthn::AuthenticateService.new(user, get_result.to_json, challenge)
|
service = Webauthn::AuthenticateService.new(user, get_result.to_json, challenge)
|
||||||
|
|
||||||
expect(service.execute).to be_truthy
|
expect(service.execute).to eq true
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns false if the response is valid but no matching stored credential is present' do
|
context 'when response is valid but no matching stored credential is present' do
|
||||||
other_client = WebAuthn::FakeClient.new(origin)
|
it 'returns false' do
|
||||||
other_client.create(challenge: challenge) # rubocop:disable Rails/SaveBang
|
other_client = WebAuthn::FakeClient.new(origin)
|
||||||
|
other_client.create(challenge: challenge) # rubocop:disable Rails/SaveBang
|
||||||
|
|
||||||
get_result = other_client.get(challenge: challenge)
|
get_result = other_client.get(challenge: challenge)
|
||||||
|
|
||||||
get_result['clientExtensionResults'] = {}
|
get_result['clientExtensionResults'] = {}
|
||||||
service = Webauthn::AuthenticateService.new(user, get_result.to_json, challenge)
|
service = Webauthn::AuthenticateService.new(user, get_result.to_json, challenge)
|
||||||
|
|
||||||
expect(service.execute).to be_falsey
|
expect(service.execute).to eq false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when device response includes invalid json' do
|
||||||
|
it 'returns false' do
|
||||||
|
service = Webauthn::AuthenticateService.new(user, 'invalid JSON', '')
|
||||||
|
expect(service.execute).to eq false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -307,14 +307,14 @@ module GraphqlHelpers
|
||||||
end
|
end
|
||||||
|
|
||||||
def graphql_mutation(name, input, fields = nil, &block)
|
def graphql_mutation(name, input, fields = nil, &block)
|
||||||
raise ArgumentError, 'Please pass either `fields` parameter or a block to `#graphql_mutation`, but not both.' if fields.present? && block_given?
|
raise ArgumentError, 'Please pass either `fields` parameter or a block to `#graphql_mutation`, but not both.' if fields.present? && block
|
||||||
|
|
||||||
name = name.graphql_name if name.respond_to?(:graphql_name)
|
name = name.graphql_name if name.respond_to?(:graphql_name)
|
||||||
mutation_name = GraphqlHelpers.fieldnamerize(name)
|
mutation_name = GraphqlHelpers.fieldnamerize(name)
|
||||||
input_variable_name = "$#{input_variable_name_for_mutation(name)}"
|
input_variable_name = "$#{input_variable_name_for_mutation(name)}"
|
||||||
mutation_field = GitlabSchema.mutation.fields[mutation_name]
|
mutation_field = GitlabSchema.mutation.fields[mutation_name]
|
||||||
|
|
||||||
fields = yield if block_given?
|
fields = yield if block
|
||||||
fields ||= all_graphql_fields_for(mutation_field.type.to_type_signature)
|
fields ||= all_graphql_fields_for(mutation_field.type.to_type_signature)
|
||||||
|
|
||||||
query = <<~MUTATION
|
query = <<~MUTATION
|
||||||
|
|
|
@ -14,7 +14,7 @@ module ActiveRecord
|
||||||
@skip_schema_queries = skip_schema_queries
|
@skip_schema_queries = skip_schema_queries
|
||||||
@query_recorder_debug = ENV['QUERY_RECORDER_DEBUG'] || query_recorder_debug
|
@query_recorder_debug = ENV['QUERY_RECORDER_DEBUG'] || query_recorder_debug
|
||||||
@log_file = log_file
|
@log_file = log_file
|
||||||
record(&block) if block_given?
|
record(&block) if block
|
||||||
end
|
end
|
||||||
|
|
||||||
def record(&block)
|
def record(&block)
|
||||||
|
|
|
@ -44,7 +44,7 @@ module StubMethodCalls
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.stub_method(object, method, &block)
|
def self.stub_method(object, method, &block)
|
||||||
raise ArgumentError, "Block is required" unless block_given?
|
raise ArgumentError, "Block is required" unless block
|
||||||
|
|
||||||
backup_method(object, method) unless backed_up_method?(object, method)
|
backup_method(object, method) unless backed_up_method?(object, method)
|
||||||
object.define_singleton_method(method, &block)
|
object.define_singleton_method(method, &block)
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
RSpec.shared_examples 'checks timelog categories permissions' do
|
||||||
|
context 'with no user' do
|
||||||
|
let_it_be(:current_user) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(:read_timelog_category) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a regular user' do
|
||||||
|
let_it_be(:current_user) { create(:user) }
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(:read_timelog_category) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a reporter user' do
|
||||||
|
let_it_be(:current_user) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
users_container.add_reporter(current_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when timelog_categories is enabled' do
|
||||||
|
it { is_expected.to be_allowed(:read_timelog_category) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when timelog_categories is disabled' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(timelog_categories: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { is_expected.to be_disallowed(:read_timelog_category) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -84,7 +84,7 @@ module Tooling
|
||||||
# method - The Octokit method to use for getting the data.
|
# method - The Octokit method to use for getting the data.
|
||||||
# args - Arguments to pass to the `helm list` command.
|
# args - Arguments to pass to the `helm list` command.
|
||||||
def each_releases_page(args, &block)
|
def each_releases_page(args, &block)
|
||||||
return to_enum(__method__, args) unless block_given?
|
return to_enum(__method__, args) unless block
|
||||||
|
|
||||||
page = 0
|
page = 0
|
||||||
final_args = args.dup
|
final_args = args.dup
|
||||||
|
@ -100,7 +100,7 @@ module Tooling
|
||||||
#
|
#
|
||||||
# args - Any arguments to pass to the `helm list` command.
|
# args - Any arguments to pass to the `helm list` command.
|
||||||
def each_release(args, &block)
|
def each_release(args, &block)
|
||||||
return to_enum(__method__, args) unless block_given?
|
return to_enum(__method__, args) unless block
|
||||||
|
|
||||||
each_releases_page(args) do |page|
|
each_releases_page(args) do |page|
|
||||||
page.releases.each do |release|
|
page.releases.each do |release|
|
||||||
|
|
|
@ -44,7 +44,7 @@ module Tooling
|
||||||
end
|
end
|
||||||
|
|
||||||
def traverse(tree, segments = [], &block)
|
def traverse(tree, segments = [], &block)
|
||||||
return to_enum(__method__, tree, segments) unless block_given?
|
return to_enum(__method__, tree, segments) unless block
|
||||||
|
|
||||||
if tree == MARKER
|
if tree == MARKER
|
||||||
return yield segments.join(SEPARATOR)
|
return yield segments.join(SEPARATOR)
|
||||||
|
|
18
yarn.lock
18
yarn.lock
|
@ -4805,10 +4805,10 @@ domhandler@^4.0.0, domhandler@^4.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
domelementtype "^2.2.0"
|
domelementtype "^2.2.0"
|
||||||
|
|
||||||
dompurify@2.3.6:
|
dompurify@2.3.8:
|
||||||
version "2.3.6"
|
version "2.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.6.tgz#2e019d7d7617aacac07cbbe3d88ae3ad354cf875"
|
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.8.tgz#224fe9ae57d7ebd9a1ae1ac18c1c1ca3f532226f"
|
||||||
integrity sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==
|
integrity sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw==
|
||||||
|
|
||||||
dompurify@^2.3.10:
|
dompurify@^2.3.10:
|
||||||
version "2.3.10"
|
version "2.3.10"
|
||||||
|
@ -8236,16 +8236,16 @@ merge2@^1.3.0, merge2@^1.4.1:
|
||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
|
||||||
mermaid@^9.1.1:
|
mermaid@^9.1.3:
|
||||||
version "9.1.1"
|
version "9.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.1.1.tgz#5d3d330ca4adf7f3c8ca51095f8bb2f0fb1a93bb"
|
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.1.3.tgz#15d08662c66250124ce31106a4620285061ac59c"
|
||||||
integrity sha512-2RVD+WkzZ4VDyO9gQvQAuQ/ux2gLigJtKDTlbwjYqOR/NwsVzTSfGm/kx648/qWJsg6Sv04tE9BWCO8s6a+pFA==
|
integrity sha512-jTIYiqKwsUXVCoxHUVkK8t0QN3zSKIdJlb9thT0J5jCnzXyc+gqTbZE2QmjRfavFTPPn5eRy5zaFp7V+6RhxYg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@braintree/sanitize-url" "^6.0.0"
|
"@braintree/sanitize-url" "^6.0.0"
|
||||||
d3 "^7.0.0"
|
d3 "^7.0.0"
|
||||||
dagre "^0.8.5"
|
dagre "^0.8.5"
|
||||||
dagre-d3 "^0.6.4"
|
dagre-d3 "^0.6.4"
|
||||||
dompurify "2.3.6"
|
dompurify "2.3.8"
|
||||||
graphlib "^2.1.8"
|
graphlib "^2.1.8"
|
||||||
khroma "^2.0.0"
|
khroma "^2.0.0"
|
||||||
moment-mini "^2.24.0"
|
moment-mini "^2.24.0"
|
||||||
|
|
Loading…
Reference in New Issue