Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-03 12:11:38 +00:00
parent dda49284fc
commit 91035102b4
68 changed files with 608 additions and 264 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module TimeTracking
class TimelogCategoryPolicy < BasePolicy
delegate { @subject.namespace }
end
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1 @@
c868a83176c8e0024ef16e0f95d8a16a0f1b7be0c1a5d58902397cc0462a7e34

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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