Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-09-09 18:12:57 +00:00
parent 427dbb30f0
commit 7c0e5472c8
57 changed files with 1217 additions and 135 deletions

View File

@ -167,6 +167,7 @@ setup-test-env:
variables:
SETUP_DB: "false"
script:
- echo $CI_MERGE_REQUEST_APPROVED
- source scripts/gitlab_workhorse_component_helpers.sh
- run_timed_command "download_and_extract_gitlab_workhorse_package" || true
- run_timed_command "scripts/setup-test-env"

View File

@ -538,6 +538,9 @@
rules:
- <<: *if-merge-request-approved
when: never
# Temporarily disabled minimal rspec jobs before and after approval because of https://gitlab.com/gitlab-org/gitlab/-/issues/373064.
- <<: *if-merge-request-not-approved
when: never
- <<: *if-automated-merge-request
when: never
- <<: *if-security-merge-request
@ -559,8 +562,6 @@
changes: *backend-patterns
- <<: *if-security-merge-request
changes: *backend-patterns
- <<: *if-merge-request-not-approved
when: never
.rails:rules:as-if-foss-migration-unit-integration:minimal-default-rules:
rules:
@ -590,8 +591,6 @@
changes: *code-backstage-patterns
- <<: *if-security-merge-request
changes: *code-backstage-patterns
- <<: *if-merge-request-not-approved
when: never
.rails:rules:system:minimal-default-rules:
rules:
@ -725,6 +724,16 @@
##################
# Frontend rules #
##################
.frontend:rules:minimal-default-rules:
rules:
- <<: *if-merge-request-approved
when: never
- <<: *if-automated-merge-request
when: never
- <<: *if-security-merge-request
when: never
.frontend:rules:compile-production-assets:
rules:
- <<: *if-not-canonical-namespace
@ -798,7 +807,7 @@
rules:
- <<: *if-fork-merge-request
changes: *code-backstage-patterns
- !reference [".rails:rules:minimal-default-rules", rules]
- !reference [".frontend:rules:minimal-default-rules", rules]
- <<: *if-merge-request-labels-run-all-jest
when: never
- changes: *core-frontend-patterns
@ -824,7 +833,7 @@
.frontend:rules:jest:minimal:as-if-foss:
rules:
- !reference [".strict-ee-only-rules", rules]
- !reference [".rails:rules:minimal-default-rules", rules]
- !reference [".frontend:rules:minimal-default-rules", rules]
- <<: *if-merge-request-labels-run-all-jest
when: never
- <<: *if-fork-merge-request
@ -1022,8 +1031,6 @@
changes: *db-patterns
- <<: *if-security-merge-request
changes: *db-patterns
- <<: *if-merge-request-not-approved
when: never
- changes: *db-patterns
.rails:rules:ee-and-foss-migration:minimal:
@ -1127,8 +1134,6 @@
changes: *db-patterns
- <<: *if-security-merge-request
changes: *db-patterns
- <<: *if-merge-request-not-approved
when: never
- changes: *db-patterns
.rails:rules:ee-only-migration:minimal:
@ -1218,8 +1223,6 @@
changes: *db-patterns
- <<: *if-security-merge-request
changes: *db-patterns
- <<: *if-merge-request-not-approved
when: never
.rails:rules:as-if-foss-migration:minimal:
rules:

View File

@ -90,7 +90,7 @@ export default {
</script>
<template>
<div v-if="showBlobControls">
<div v-if="showBlobControls" class="gl-display-flex gl-gap-3">
<gl-button data-testid="find" :href="blobInfo.findFilePath" :class="$options.buttonClassList">
{{ $options.i18n.findFile }}
</gl-button>

View File

@ -50,7 +50,11 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
result = DraftNotes::PublishService.new(merge_request, current_user).execute(draft_note(allow_nil: true))
if Feature.enabled?(:mr_review_submit_comment, @project)
::Notes::CreateService.new(@project, current_user, create_note_params).execute if create_note_params[:note]
if create_note_params[:note]
::Notes::CreateService.new(@project, current_user, create_note_params).execute
merge_request_activity_counter.track_submit_review_comment(user: current_user)
end
if Gitlab::Utils.to_boolean(approve_params[:approve])
success = ::MergeRequests::ApprovalService.new(project: @project, current_user: current_user, params: approve_params).execute(merge_request)
@ -58,6 +62,8 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
unless success
return render json: { message: _('An error occurred while approving, please try again.') }, status: :internal_server_error
end
merge_request_activity_counter.track_submit_review_approve(user: current_user)
end
end
@ -159,6 +165,10 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
def authorize_create_note!
access_denied! unless can?(current_user, :create_note, merge_request)
end
def merge_request_activity_counter
Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter
end
end
Projects::MergeRequests::DraftsController.prepend_mod

View File

@ -11,7 +11,7 @@ module Projects
before_action :integration, only: [:edit, :update, :test]
before_action :default_integration, only: [:edit, :update]
before_action :web_hook_logs, only: [:edit, :update]
before_action -> { check_rate_limit!(:project_testing_integration, scope: [@project, current_user]) }, only: :test
before_action -> { check_test_rate_limit! }, only: :test
respond_to :html
@ -140,6 +140,15 @@ module Projects
def use_inherited_settings?(attributes)
default_integration && attributes[:inherit_from_id] == default_integration.id.to_s
end
def check_test_rate_limit!
check_rate_limit!(:project_testing_integration, scope: [@project, current_user]) do
render json: {
error: true,
message: _('This endpoint has been requested too many times. Try again later.')
}, status: :ok
end
end
end
end
end

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Types
module BranchRules
class BranchProtectionType < BaseObject
graphql_name 'BranchProtection'
description 'Branch protection details for a branch rule.'
accepts ::ProtectedBranch
authorize :read_protected_branch
field :allow_force_push,
type: GraphQL::Types::Boolean,
null: false,
description: 'Toggle force push to the branch for users with write access.'
end
end
end

View File

@ -13,6 +13,12 @@ module Types
null: false,
description: 'Branch name, with wildcards, for the branch rules.'
field :branch_protection,
type: Types::BranchRules::BranchProtectionType,
null: false,
description: 'Branch protections configured for this branch rule.',
method: :itself
field :created_at,
Types::TimeType,
null: false,

View File

@ -38,6 +38,7 @@ class NotificationRecipient
return !unsubscribed? if @type == :subscription
return false unless suitable_notification_level?
return false if email_blocked?
# check this last because it's expensive
# nobody should receive notifications if they've specifically unsubscribed
@ -95,6 +96,15 @@ class NotificationRecipient
end
end
def email_blocked?
return false if Feature.disabled?(:block_emails_with_failures)
recipient_email = user.notification_email_for(@group)
Gitlab::ApplicationRateLimiter.peek(:permanent_email_failure, scope: recipient_email) ||
Gitlab::ApplicationRateLimiter.peek(:temporary_email_failure, scope: recipient_email)
end
def has_access?
DeclarativePolicy.subject_scope do
break false unless user.can?(:receive_notifications)

View File

@ -0,0 +1,103 @@
# frozen_string_literal: true
module MergeRequests
module Mergeability
class Logger
include Gitlab::Utils::StrongMemoize
def initialize(merge_request:, destination: Gitlab::AppJsonLogger)
@destination = destination
@merge_request = merge_request
end
def commit
return unless enabled?
commit_logs
end
def instrument(mergeability_name:)
raise ArgumentError, 'block not given' unless block_given?
return yield unless enabled?
op_start_db_counters = current_db_counter_payload
op_started_at = current_monotonic_time
result = yield
observe("mergeability.#{mergeability_name}.duration_s", current_monotonic_time - op_started_at)
observe_sql_counters(mergeability_name, op_start_db_counters, current_db_counter_payload)
result
end
private
attr_reader :destination, :merge_request
def observe(name, value)
return unless enabled?
observations[name.to_s].push(value)
end
def commit_logs
attributes = Gitlab::ApplicationContext.current.merge({
mergeability_project_id: merge_request.project.id
})
attributes[:mergeability_merge_request_id] = merge_request.id
attributes.merge!(observations_hash)
attributes.compact!
attributes.stringify_keys!
destination.info(attributes)
end
def observations_hash
transformed = observations.transform_values do |values|
next if values.empty?
{
'values' => values
}
end.compact
transformed.each_with_object({}) do |key, hash|
key[1].each { |k, v| hash["#{key[0]}.#{k}"] = v }
end
end
def observations
strong_memoize(:observations) do
Hash.new { |hash, key| hash[key] = [] }
end
end
def observe_sql_counters(name, start_db_counters, end_db_counters)
end_db_counters.each do |key, value|
result = value - start_db_counters.fetch(key, 0)
next if result == 0
observe("mergeability.#{name}.#{key}", result)
end
end
def current_db_counter_payload
::Gitlab::Metrics::Subscribers::ActiveRecord.db_counter_payload
end
def enabled?
strong_memoize(:enabled) do
::Feature.enabled?(:mergeability_checks_logger, merge_request.project)
end
end
def current_monotonic_time
::Gitlab::Metrics::System.monotonic_time
end
end
end
end

View File

@ -15,12 +15,17 @@ module MergeRequests
next if check.skip?
check_result = run_check(check)
check_result = logger.instrument(mergeability_name: check_class.to_s.demodulize.underscore) do
run_check(check)
end
result_hash << check_result
break result_hash if check_result.failed?
end
logger.commit
self
end
@ -57,6 +62,12 @@ module MergeRequests
Gitlab::MergeRequests::Mergeability::ResultsStore.new(merge_request: merge_request)
end
end
def logger
strong_memoize(:logger) do
MergeRequests::Mergeability::Logger.new(merge_request: merge_request)
end
end
end
end
end

View File

@ -0,0 +1,46 @@
# rubocop:disable Naming/FileName
# frozen_string_literal: true
require_relative 'cdn/google_cdn'
module ObjectStorage
module CDN
module Concern
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
def use_cdn?(request_ip)
return false unless cdn_options.is_a?(Hash) && cdn_options['provider']
return false unless cdn_provider
cdn_provider.use_cdn?(request_ip)
end
def cdn_signed_url
cdn_provider&.signed_url(path)
end
private
def cdn_provider
strong_memoize(:cdn_provider) do
provider = cdn_options['provider']&.downcase
next unless provider
next GoogleCDN.new(cdn_options) if provider == 'google'
raise "Unknown CDN provider: #{provider}"
end
end
def cdn_options
return {} unless options.object_store.key?('cdn')
options.object_store.cdn
end
end
end
end
# rubocop:enable Naming/FileName

View File

@ -0,0 +1,141 @@
# rubocop:disable Naming/FileName
# frozen_string_literal: true
module ObjectStorage
module CDN
class GoogleCDN
include Gitlab::Utils::StrongMemoize
IpListNotRetrievedError = Class.new(StandardError)
GOOGLE_CDN_LIST_KEY = 'google_cdn_ip_list'
GOOGLE_IP_RANGES_URL = 'https://www.gstatic.com/ipranges/cloud.json'
EXPECTED_CONTENT_TYPE = 'application/json'
RESPONSE_BODY_LIMIT = 1.megabyte
CACHE_EXPIRATION_TIME = 1.day
attr_reader :options
def initialize(options)
@options = HashWithIndifferentAccess.new(options.to_h)
end
def use_cdn?(request_ip)
return false unless config_valid?
ip = IPAddr.new(request_ip)
return false if ip.private?
return false unless google_ip_ranges.present?
!google_ip?(request_ip)
end
def signed_url(path, expiry: 10.minutes)
expiration = (Time.current + expiry).utc.to_i
uri = Addressable::URI.parse(cdn_url)
uri.path = path
uri.query = "Expires=#{expiration}&KeyName=#{key_name}"
signature = OpenSSL::HMAC.digest('SHA1', decoded_key, uri.to_s)
encoded_signature = Base64.urlsafe_encode64(signature)
uri.query += "&Signature=#{encoded_signature}"
uri.to_s
end
private
def config_valid?
[key_name, decoded_key, cdn_url].all?(&:present?)
end
def key_name
strong_memoize(:key_name) do
options['key_name']
end
end
def decoded_key
strong_memoize(:decoded_key) do
Base64.urlsafe_decode64(options['key']) if options['key']
rescue ArgumentError
Gitlab::ErrorTracking.log_exception(ArgumentError.new("Google CDN key is not base64-encoded"))
nil
end
end
def cdn_url
strong_memoize(:cdn_url) do
options['url']
end
end
def google_ip?(request_ip)
google_ip_ranges.any? { |range| range.include?(request_ip) }
end
def google_ip_ranges
strong_memoize(:google_ip_ranges) do
cache_value(GOOGLE_CDN_LIST_KEY) { fetch_google_ip_list }
end
rescue IpListNotRetrievedError => err
Gitlab::ErrorTracking.log_exception(err)
[]
end
def cache_value(key, expires_in: CACHE_EXPIRATION_TIME, &block)
l1_cache.fetch(key, expires_in: expires_in) do
l2_cache.fetch(key, expires_in: expires_in) { yield }
end
end
def l1_cache
Gitlab::ProcessMemoryCache.cache_backend
end
def l2_cache
Rails.cache
end
def fetch_google_ip_list
response = Gitlab::HTTP.get(GOOGLE_IP_RANGES_URL)
raise IpListNotRetrievedError, "response was #{response.code}" unless response.code == 200
if response.body&.bytesize.to_i > RESPONSE_BODY_LIMIT
raise IpListNotRetrievedError, "response was too large: #{response.body.bytesize}"
end
parsed_response = response.parsed_response
unless response.content_type == EXPECTED_CONTENT_TYPE && parsed_response.is_a?(Hash)
raise IpListNotRetrievedError, "response was not JSON"
end
parse_google_prefixes(parsed_response)
end
def parse_google_prefixes(parsed_response)
prefixes = parsed_response['prefixes']
raise IpListNotRetrievedError, "JSON was type #{prefixes.class}, expected Array" unless prefixes.is_a?(Array)
ranges = prefixes.map do |prefix|
ip_range = prefix['ipv4Prefix'] || prefix['ipv6Prefix']
next unless ip_range
IPAddr.new(ip_range)
end.compact
raise IpListNotRetrievedError, "#{GOOGLE_IP_RANGES_URL} did not return any IP ranges" if ranges.empty?
ranges
end
end
end
end
# rubocop:enable Naming/FileName

View File

@ -46,18 +46,18 @@
= f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control gl-form-input gl-mt-2'
.help-block
= _('Specify an email address regex pattern to identify default internal users.')
= link_to _('Learn more'), help_page_path('user/permissions', anchor: 'setting-new-users-to-external'), target: '_blank', rel: 'noopener noreferrer'
= link_to _('Learn more.'), help_page_path('user/permissions', anchor: 'setting-new-users-to-external'), target: '_blank', rel: 'noopener noreferrer'
- unless Gitlab.com?
.form-group
= f.label :deactivate_dormant_users, _('Dormant users'), class: 'label-bold'
- dormant_users_help_link = help_page_path('user/admin_area/moderate_users', anchor: 'automatically-deactivate-dormant-users')
- dormant_users_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: dormant_users_help_link }
= f.gitlab_ui_checkbox_component :deactivate_dormant_users, _('Deactivate dormant users after a period of inactivity'), help_text: _('Users can reactivate their account by signing in. %{link_start}Learn more%{link_end}').html_safe % { link_start: dormant_users_help_link_start, link_end: '</a>'.html_safe }
= f.gitlab_ui_checkbox_component :deactivate_dormant_users, _('Deactivate dormant users after a period of inactivity'), help_text: _('Users can reactivate their account by signing in. %{link_start}Learn more.%{link_end}').html_safe % { link_start: dormant_users_help_link_start, link_end: '</a>'.html_safe }
.form-group
= f.label :deactivate_dormant_users_period, _('Period of inactivity (days)'), class: 'label-light'
= f.number_field :deactivate_dormant_users_period, class: 'form-control gl-form-input', min: '1'
.form-text.text-muted
= _('Period of inactivity before deactivation')
= _('Period of inactivity before deactivation.')
.form-group
= f.label :personal_access_token_prefix, _('Personal Access Token prefix'), class: 'label-light'

View File

@ -0,0 +1,8 @@
---
name: block_emails_with_failures
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96902
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/373159
milestone: '15.4'
type: development
group: group::project management
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: mergeability_checks_logger
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96128
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/371717
milestone: '15.4'
type: development
group: group::code review
default_enabled: false

View File

@ -37,6 +37,7 @@ ActiveSupport::Inflector.inflections do |inflect|
vulnerabilities_feedback
vulnerability_feedback
)
inflect.acronym 'CDN'
inflect.acronym 'EE'
inflect.acronym 'JH'
inflect.acronym 'CSP'

View File

@ -116,6 +116,8 @@
- 'i_code_review_merge_request_widget_status_checks_expand_success'
- 'i_code_review_merge_request_widget_status_checks_expand_warning'
- 'i_code_review_merge_request_widget_status_checks_expand_failed'
- 'i_code_review_submit_review_approve'
- 'i_code_review_submit_review_comment'
- name: code_review_category_monthly_active_users
operator: OR
source: redis
@ -220,6 +222,8 @@
- 'i_code_review_merge_request_widget_status_checks_expand_success'
- 'i_code_review_merge_request_widget_status_checks_expand_warning'
- 'i_code_review_merge_request_widget_status_checks_expand_failed'
- 'i_code_review_submit_review_approve'
- 'i_code_review_submit_review_comment'
- name: code_review_extension_category_monthly_active_users
operator: OR
source: redis

View File

@ -0,0 +1,25 @@
---
key_path: redis_hll_counters.code_review.i_code_review_submit_review_approve_monthly
description: Count of unique users per month who submit a review and approve
product_stage: create
product_group: code_review
product_category: code_review
product_section: dev
value_type: number
status: active
milestone: '15.4'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91073
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- i_code_review_submit_review_approve
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,25 @@
---
key_path: redis_hll_counters.code_review.i_code_review_submit_review_comment_monthly
description: Count of unique users per month who submit a review with a comment
product_stage: create
product_group: code_review
product_category: code_review
product_section: dev
value_type: number
status: active
milestone: '15.4'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91073
time_frame: 28d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- i_code_review_submit_review_comment
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,25 @@
---
key_path: redis_hll_counters.code_review.i_code_review_submit_review_approve_weekly
description: Count of unique users per week who submit a review and approve
product_stage: create
product_group: code_review
product_category: code_review
product_section: dev
value_type: number
status: active
milestone: '15.4'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91073
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- i_code_review_submit_review_approve
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,25 @@
---
key_path: redis_hll_counters.code_review.i_code_review_submit_review_comment_weekly
description: Count of unique users per week who submit a review with a comment
product_stage: create
product_group: code_review
product_category: code_review
product_section: dev
value_type: number
status: active
milestone: '15.4'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91073
time_frame: 7d
data_source: redis_hll
data_category: optional
instrumentation_class: RedisHLLMetric
options:
events:
- i_code_review_submit_review_comment
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -10074,6 +10074,16 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="branchcommit"></a>`commit` | [`Commit`](#commit) | Commit for the branch. |
| <a id="branchname"></a>`name` | [`String!`](#string) | Name of the branch. |
### `BranchProtection`
Branch protection details for a branch rule.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="branchprotectionallowforcepush"></a>`allowForcePush` | [`Boolean!`](#boolean) | Toggle force push to the branch for users with write access. |
### `BranchRule`
List of branch rules for a project, grouped by branch name.
@ -10082,6 +10092,7 @@ List of branch rules for a project, grouped by branch name.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="branchrulebranchprotection"></a>`branchProtection` | [`BranchProtection!`](#branchprotection) | Branch protections configured for this branch rule. |
| <a id="branchrulecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the branch rule was created. |
| <a id="branchrulename"></a>`name` | [`String!`](#string) | Branch name, with wildcards, for the branch rules. |
| <a id="branchruleupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the branch rule was last updated. |

View File

@ -36,7 +36,8 @@ Sign in to BrowserStack with the credentials saved in the **Engineering** vault
## Initiatives
Current high-level frontend goals are listed on [Frontend Epics](https://gitlab.com/groups/gitlab-org/-/epics?label_name%5B%5D=frontend).
You can find current frontend initiatives with a cross-functional impact on epics
with the label [frontend-initiative](https://gitlab.com/groups/gitlab-org/-/epics?state=opened&page=1&sort=UPDATED_AT_DESC&label_name[]=frontend-initiative).
## Principles

View File

@ -383,7 +383,7 @@ The following table lists group permissions available for each role:
| Pull a container image using the dependency proxy | ✓ | ✓ | ✓ | ✓ | ✓ |
| View Contribution analytics | ✓ | ✓ | ✓ | ✓ | ✓ |
| View group [epic](group/epics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
| View [group wiki](project/wiki/group.md) pages | ✓ (6) | ✓ | ✓ | ✓ | ✓ |
| View [group wiki](project/wiki/group.md) pages | ✓ (5) | ✓ | ✓ | ✓ | ✓ |
| View [Insights](project/insights/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
| View [Insights](project/insights/index.md) charts | ✓ | ✓ | ✓ | ✓ | ✓ |
| View [Issue analytics](analytics/issue_analytics.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
@ -395,13 +395,13 @@ The following table lists group permissions available for each role:
| Pull [packages](packages/index.md) | | ✓ | ✓ | ✓ | ✓ |
| Delete [packages](packages/index.md) | | | | ✓ | ✓ |
| Create/edit/delete [Maven and generic package duplicate settings](packages/generic_packages/index.md#do-not-allow-duplicate-generic-packages) | | | | ✓ | ✓ |
| Pull a Container Registry image | ✓ (7) | ✓ | ✓ | ✓ | ✓ |
| Pull a Container Registry image | ✓ (6) | ✓ | ✓ | ✓ | ✓ |
| Remove a Container Registry image | | | ✓ | ✓ | ✓ |
| View [Group DevOps Adoption](group/devops_adoption/index.md) | | ✓ | ✓ | ✓ | ✓ |
| View metrics dashboard annotations | | ✓ | ✓ | ✓ | ✓ |
| View [Productivity analytics](analytics/productivity_analytics.md) | | ✓ | ✓ | ✓ | ✓ |
| Create and edit [group wiki](project/wiki/group.md) pages | | | ✓ | ✓ | ✓ |
| Create project in group | | | ✓ (3)(5) | ✓ (3) | ✓ (3) |
| Create project in group | | | ✓ (2)(4) | ✓ (2) | ✓ (2) |
| Create/edit/delete group milestones | | ✓ | ✓ | ✓ | ✓ |
| Create/edit/delete iterations | | ✓ | ✓ | ✓ | ✓ |
| Create/edit/delete metrics dashboard annotations | | | ✓ | ✓ | ✓ |
@ -409,10 +409,10 @@ The following table lists group permissions available for each role:
| Purge the dependency proxy for a group | | | | | ✓ |
| Create/edit/delete dependency proxy [cleanup policies](packages/dependency_proxy/reduce_dependency_proxy_storage.md#cleanup-policies) | | | | ✓ | ✓ |
| Use [security dashboard](application_security/security_dashboard/index.md) | | | ✓ | ✓ | ✓ |
| View group Audit Events | | | ✓ (7) | ✓ (7) | ✓ |
| View group Audit Events | | | ✓ (6) | ✓ (6) | ✓ |
| Create subgroup | | | | ✓ (1) | ✓ |
| Delete [group wiki](project/wiki/group.md) pages | | | ✓ | ✓ | ✓ |
| Edit [epic](group/epics/index.md) comments (posted by any user) | | | | ✓ (2) | ✓ (2) |
| Edit [epic](group/epics/index.md) comments (posted by any user) | | | | ✓ | ✓ |
| List group deploy tokens | | | | ✓ | ✓ |
| Manage [group push rules](group/access_and_permissions.md#group-push-rules) | | | | ✓ | ✓ |
| View/manage group-level Kubernetes cluster | | | | ✓ | ✓ |
@ -423,14 +423,14 @@ The following table lists group permissions available for each role:
| Delete group [epic](group/epics/index.md) | | | | | ✓ |
| Disable notification emails | | | | | ✓ |
| Edit group settings | | | | | ✓ |
| Edit [SAML SSO](group/saml_sso/index.md) | | | | | ✓ (4) |
| Edit [SAML SSO](group/saml_sso/index.md) | | | | | ✓ (3) |
| Filter members by 2FA status | | | | | ✓ |
| Manage group level CI/CD variables | | | | | ✓ |
| Manage group members | | | | | ✓ |
| Share (invite) groups with groups | | | | | ✓ |
| View 2FA status of members | | | | | ✓ |
| View [Billing](../subscriptions/gitlab_com/index.md#view-your-gitlab-saas-subscription) | | | | | ✓ (4) |
| View group [Usage Quotas](usage_quotas.md) page | | | | | ✓ (4) |
| View [Billing](../subscriptions/gitlab_com/index.md#view-your-gitlab-saas-subscription) | | | | | ✓ (3) |
| View group [Usage Quotas](usage_quotas.md) page | | | | | ✓ (3) |
| Manage group runners | | | | | ✓ |
| [Migrate groups](group/import/index.md) | | | | | ✓ |
| Manage [subscriptions, and purchase CI/CD minutes and storage](../subscriptions/gitlab_com/index.md) | | | | | ✓ |
@ -438,14 +438,13 @@ The following table lists group permissions available for each role:
<!-- markdownlint-disable MD029 -->
1. Groups can be set to allow either Owners, or Owners and users with the Maintainer role, to [create subgroups](group/subgroups/index.md#create-a-subgroup).
2. Introduced in GitLab 12.2.
3. Default project creation role can be changed at:
2. Default project creation role can be changed at:
- The [instance level](admin_area/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects).
- The [group level](group/manage.md#specify-who-can-add-projects-to-a-group).
4. Does not apply to subgroups.
5. Developers can push commits to the default branch of a new project only if the [default branch protection](group/manage.md#change-the-default-branch-protection-of-a-group) is set to "Partially protected" or "Not protected".
6. In addition, if your group is public or internal, all users who can see the group can also see group wiki pages.
7. Users can only view events based on their individual actions.
3. Does not apply to subgroups.
4. Developers can push commits to the default branch of a new project only if the [default branch protection](group/manage.md#change-the-default-branch-protection-of-a-group) is set to "Partially protected" or "Not protected".
5. In addition, if your group is public or internal, all users who can see the group can also see group wiki pages.
6. Users can only view events based on their individual actions.
<!-- markdownlint-enable MD029 -->

View File

@ -10,9 +10,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Creating, editing, and deleting tasks](https://gitlab.com/groups/gitlab-org/-/epics/7169) introduced in GitLab 15.0.
> - [Enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/334812) in GitLab 15.3.
WARNING:
Tasks are in [**Alpha**](../policy/alpha-beta-support.md#alpha-features).
Known limitation:
- [Tasks currently cannot be accessed via REST API.](https://gitlab.com/gitlab-org/gitlab/-/issues/368055)

View File

@ -55,7 +55,10 @@ you must purchase additional storage. For more details, see [Excess storage usag
## View storage usage
You can view storage usage for your project or [namespace](../user/namespace/index.md).
Prerequisites:
- To view storage usage for a project, you must be a project Maintainer or namespace Owner.
- To view storage usage for a namespace, you must be the namespace Owner.
1. Go to your project or namespace:
- For a project, on the top bar, select **Menu > Projects** and find your project.
@ -84,16 +87,16 @@ The following storage usage statistics are available to a maintainer:
## Manage your storage usage
You can use several methods to manage and reduce your usage for some storage types.
To manage your storage, if you are a namespace Owner you can [purchase more storage for the namespace](../subscriptions/gitlab_com/index.md#purchase-more-storage-and-transfer).
For more information, see the following pages:
Depending on your role, you can also use the following methods to manage or reduce your storage:
- [Reduce package registry storage](packages/package_registry/reduce_package_registry_storage.md)
- [Reduce dependency proxy storage](packages/dependency_proxy/reduce_dependency_proxy_storage.md)
- [Reduce repository size](project/repository/reducing_the_repo_size_using_git.md)
- [Reduce container registry storage](packages/container_registry/reduce_container_registry_storage.md)
- [Reduce container registry data transfers](packages/container_registry/reduce_container_registry_data_transfer.md)
- [Reduce wiki repository size](../administration/wikis/index.md#reduce-wiki-repository-size)
- [Reduce package registry storage](packages/package_registry/reduce_package_registry_storage.md).
- [Reduce dependency proxy storage](packages/dependency_proxy/reduce_dependency_proxy_storage.md).
- [Reduce repository size](project/repository/reducing_the_repo_size_using_git.md).
- [Reduce container registry storage](packages/container_registry/reduce_container_registry_storage.md).
- [Reduce container registry data transfers](packages/container_registry/reduce_container_registry_data_transfer.md).
- [Reduce wiki repository size](../administration/wikis/index.md#reduce-wiki-repository-size).
## Excess storage usage

View File

@ -46,7 +46,8 @@ module Gitlab
search_rate_limit_unauthenticated: { threshold: -> { application_settings.search_rate_limit_unauthenticated }, interval: 1.minute },
gitlab_shell_operation: { threshold: 600, interval: 1.minute },
pipelines_create: { threshold: -> { application_settings.pipeline_limit_per_project_user_sha }, interval: 1.minute },
temporary_email_failure: { threshold: 50, interval: 1.day },
temporary_email_failure: { threshold: 300, interval: 1.day },
permanent_email_failure: { threshold: 5, interval: 1.day },
project_testing_integration: { threshold: 5, interval: 1.minute },
email_verification: { threshold: 10, interval: 10.minutes },
email_verification_code_send: { threshold: 10, interval: 1.hour },

View File

@ -7,7 +7,8 @@ module Gitlab
TAGS_SORT_KEY = {
'name' => Gitaly::FindAllTagsRequest::SortBy::Key::REFNAME,
'updated' => Gitaly::FindAllTagsRequest::SortBy::Key::CREATORDATE
'updated' => Gitaly::FindAllTagsRequest::SortBy::Key::CREATORDATE,
'version' => Gitaly::FindAllTagsRequest::SortBy::Key::VERSION_REFNAME
}.freeze
TAGS_SORT_DIRECTION = {
@ -258,7 +259,7 @@ module Gitlab
end
def sort_tags_by_param(sort_by)
match = sort_by.match(/^(?<key>name|updated)_(?<direction>asc|desc)$/)
match = sort_by.match(/^(?<key>name|updated|version)_(?<direction>asc|desc)$/)
return unless match

View File

@ -5,11 +5,12 @@ module Gitlab
module WebhookProcessors
class FailureLogger < Base
def execute
log_failure if permanent_failure? || temporary_failure_over_threshold?
log_failure if permanent_failure_over_threshold? || temporary_failure_over_threshold?
end
def permanent_failure?
payload['event'] == 'failed' && payload['severity'] == 'permanent'
def permanent_failure_over_threshold?
payload['event'] == 'failed' && payload['severity'] == 'permanent' &&
Gitlab::ApplicationRateLimiter.throttled?(:permanent_email_failure, scope: payload['recipient'])
end
def temporary_failure_over_threshold?

View File

@ -21,7 +21,6 @@ module Gitlab
CATEGORIES_FOR_TOTALS = %w[
analytics
compliance
epic_boards_usage
epics_usage
error_tracking
ide_edit

View File

@ -396,3 +396,11 @@
redis_slot: code_review
category: code_review
aggregation: weekly
- name: i_code_review_submit_review_approve
redis_slot: code_review
category: code_review
aggregation: weekly
- name: i_code_review_submit_review_comment
redis_slot: code_review
category: code_review
aggregation: weekly

View File

@ -337,11 +337,6 @@
redis_slot: testing
category: testing
aggregation: weekly
# Container Security - Network Policies
- name: clusters_using_network_policies_ui
redis_slot: network_policies
category: network_policies
aggregation: weekly
# Geo group
- name: g_geo_proxied_requests
category: geo

View File

@ -1,19 +0,0 @@
# Epic board events
#
# We are using the same slot of issue events 'project_management' for
# epic events to allow data aggregation.
# More information in: https://gitlab.com/gitlab-org/gitlab/-/issues/322405
- name: g_project_management_users_creating_epic_boards
category: epic_boards_usage
redis_slot: project_management
aggregation: daily
- name: g_project_management_users_viewing_epic_boards
category: epic_boards_usage
redis_slot: project_management
aggregation: daily
- name: g_project_management_users_updating_epic_board_names
category: epic_boards_usage
redis_slot: project_management
aggregation: daily

View File

@ -49,6 +49,8 @@ module Gitlab
MR_LOAD_CONFLICT_UI_ACTION = 'i_code_review_user_load_conflict_ui'
MR_RESOLVE_CONFLICT_ACTION = 'i_code_review_user_resolve_conflict'
MR_RESOLVE_THREAD_IN_ISSUE_ACTION = 'i_code_review_user_resolve_thread_in_issue'
MR_SUBMIT_REVIEW_APPROVE = 'i_code_review_submit_review_approve'
MR_SUBMIT_REVIEW_COMMENT = 'i_code_review_submit_review_comment'
class << self
def track_mr_diffs_action(merge_request:)
@ -230,6 +232,14 @@ module Gitlab
track_unique_action_by_user(MR_RESOLVE_THREAD_IN_ISSUE_ACTION, user)
end
def track_submit_review_approve(user:)
track_unique_action_by_user(MR_SUBMIT_REVIEW_APPROVE, user)
end
def track_submit_review_comment(user:)
track_unique_action_by_user(MR_SUBMIT_REVIEW_COMMENT, user)
end
private
def track_unique_action_by_merge_request(action, merge_request)

View File

@ -21005,7 +21005,7 @@ msgstr ""
msgid "Include the username in the URL if required: %{code_open}https://username@gitlab.company.com/group/project.git%{code_close}."
msgstr ""
msgid "Includes LFS objects. It can be overridden per group, or per project. 0 for unlimited."
msgid "Includes LFS objects. It can be overridden per group, or per project. Set to 0 for no limit."
msgstr ""
msgid "Includes an MVC structure to help you get started"
@ -24385,7 +24385,7 @@ msgstr ""
msgid "Maximum allowable lifetime for access token (days)"
msgstr ""
msgid "Maximum allowed lifetime for SSH keys (in days)"
msgid "Maximum allowed lifetime for SSH keys (days)"
msgstr ""
msgid "Maximum artifacts size"
@ -28677,7 +28677,7 @@ msgstr ""
msgid "Period of inactivity (days)"
msgstr ""
msgid "Period of inactivity before deactivation"
msgid "Period of inactivity before deactivation."
msgstr ""
msgid "Permalink"
@ -43092,7 +43092,7 @@ msgstr ""
msgid "Users can launch a development environment from a GitLab browser tab when the %{linkStart}Gitpod%{linkEnd} integration is enabled."
msgstr ""
msgid "Users can reactivate their account by signing in. %{link_start}Learn more%{link_end}"
msgid "Users can reactivate their account by signing in. %{link_start}Learn more.%{link_end}"
msgstr ""
msgid "Users can render diagrams in AsciiDoc, Markdown, reStructuredText, and Textile documents using Kroki."

View File

@ -12,6 +12,8 @@ module QA
NoValueError = Class.new(RuntimeError)
attr_reader :retrieved_from_cache
class << self
# Initialize new instance of class without fabrication
#
@ -81,7 +83,7 @@ module QA
Support::FabricationTracker.start_fabrication
result = yield.tap do
fabrication_time = Time.now - start
fabrication_http_method = if resource.api_fabrication_http_method == :get
fabrication_http_method = if resource.api_fabrication_http_method == :get || resource.retrieved_from_cache
if include?(Reusable)
"Retrieved for reuse"
else
@ -92,24 +94,28 @@ module QA
end
Support::FabricationTracker.save_fabrication(:"#{fabrication_method}_fabrication", fabrication_time)
Tools::TestResourceDataProcessor.collect(
resource: resource,
info: resource.identifier,
fabrication_method: fabrication_method,
fabrication_time: fabrication_time
)
unless resource.retrieved_from_cache
Tools::TestResourceDataProcessor.collect(
resource: resource,
info: resource.identifier,
fabrication_method: fabrication_method,
fabrication_time: fabrication_time
)
end
Runtime::Logger.info do
msg = ["==#{'=' * parents.size}>"]
msg << "#{fabrication_http_method} a #{Rainbow(name).black.bg(:white)}"
msg << resource.identifier
msg << "as a dependency of #{parents.last}" if parents.any?
msg << "via #{fabrication_method}"
msg << "via #{resource.retrieved_from_cache ? 'cache' : fabrication_method}"
msg << "in #{fabrication_time.round(2)} seconds"
msg.compact.join(' ')
end
end
Support::FabricationTracker.finish_fabrication
result

View File

@ -46,8 +46,15 @@ module QA
rescue NoValueError
user_id = user.respond_to?(:id) ? user.id : Resource::User.build(user).reload!.id
token = auto_paginated_response(request_url("/personal_access_tokens?user_id=#{user_id}", per_page: '100'))
.find { |t| t[:name] == name }
api_client = Runtime::API::Client.new(:gitlab,
is_new_session: false,
user: user,
personal_access_token: self.token)
request_url = Runtime::API::Request.new(api_client,
"/personal_access_tokens?user_id=#{user_id}",
per_page: '100').url
token = auto_paginated_response(request_url).find { |t| t[:name] == name }
raise ResourceNotFoundError unless token
@ -56,7 +63,7 @@ module QA
end
def name
@name ||= "api-personal-access-token-#{Faker::Alphanumeric.alphanumeric(number: 8)}"
@name ||= "api-pat-#{user.username}-#{Faker::Alphanumeric.alphanumeric(number: 8)}"
end
def api_post_body
@ -75,6 +82,9 @@ module QA
def find_and_set_value
@token ||= QA::Resource::PersonalAccessTokenCache.get_token_for_username(user.username)
@retrieved_from_cache = true if @token
@token
end
def cache_token

View File

@ -2,11 +2,7 @@
module QA
RSpec.describe 'Manage' do
describe 'Project owner permissions', :reliable, quarantine: {
only: { pipeline: %i[staging staging-canary production canary] },
type: :investigating,
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/373038'
} do
describe 'Project owner permissions', :reliable do
let!(:owner) do
Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
end

View File

@ -404,6 +404,11 @@ RSpec.describe Projects::MergeRequests::DraftsController do
end
context 'when feature flag is enabled' do
before do
allow(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_submit_review_comment)
end
it 'creates note' do
post :publish, params: params.merge!(note: 'Hello world')
@ -415,6 +420,13 @@ RSpec.describe Projects::MergeRequests::DraftsController do
expect(merge_request.notes.reload.size).to be(1)
end
it 'tracks merge request activity' do
post :publish, params: params.merge!(note: 'Hello world')
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to have_received(:track_submit_review_comment).with(user: user)
end
end
end
@ -436,6 +448,11 @@ RSpec.describe Projects::MergeRequests::DraftsController do
end
context 'when feature flag is enabled' do
before do
allow(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to receive(:track_submit_review_approve)
end
it 'approves merge request' do
post :publish, params: params.merge!(approve: true)
@ -447,6 +464,13 @@ RSpec.describe Projects::MergeRequests::DraftsController do
expect(merge_request.approvals.reload.size).to be(0)
end
it 'tracks merge request activity' do
post :publish, params: params.merge!(approve: true)
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to have_received(:track_submit_review_approve).with(user: user)
end
end
end
end

View File

@ -50,7 +50,7 @@ RSpec.describe Projects::Settings::IntegrationsController do
end
end
context 'when validations fail' do
context 'when validations fail', :clean_gitlab_redis_rate_limiting do
let(:integration_params) { { active: 'true', url: '' } }
it 'returns error messages in JSON response' do
@ -62,7 +62,7 @@ RSpec.describe Projects::Settings::IntegrationsController do
end
end
context 'when successful' do
context 'when successful', :clean_gitlab_redis_rate_limiting do
context 'with empty project' do
let_it_be(:project) { create(:project) }
@ -200,8 +200,8 @@ RSpec.describe Projects::Settings::IntegrationsController do
2.times { post :test, params: project_params(service: integration_params) }
expect(response.body).to eq(_('This endpoint has been requested too many times. Try again later.'))
expect(response).to have_gitlab_http_status(:too_many_requests)
expect(response.body).to include(_('This endpoint has been requested too many times. Try again later.'))
expect(response).to have_gitlab_http_status(:ok)
end
end
end

View File

@ -17,6 +17,7 @@ RSpec.describe 'User uses header search field', :js do
end
before do
allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).and_return(0)
allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).with(:search_rate_limit).and_return(1000)
allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).with(:search_rate_limit_unauthenticated).and_return(1000)
sign_in(user)

17
spec/fixtures/cdn/google_cloud.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
"syncToken": "1661533328840",
"creationTime": "2022-08-26T10:02:08.840384",
"prefixes": [{
"ipv4Prefix": "34.80.0.0/15",
"service": "Google Cloud",
"scope": "asia-east1"
}, {
"ipv4Prefix": "34.137.0.0/16",
"service": "Google Cloud",
"scope": "asia-east1"
}, {
"ipv6Prefix": "2600:1900:4180::/44",
"service": "Google Cloud",
"scope": "us-west4"
}]
}

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['BranchProtection'] do
subject { described_class }
let(:fields) { %i[allow_force_push] }
specify { is_expected.to require_graphql_authorizations(:read_protected_branch) }
specify { is_expected.to have_graphql_fields(fields) }
end

View File

@ -7,21 +7,16 @@ RSpec.describe GitlabSchema.types['BranchRule'] do
subject { described_class }
let(:fields) { %i[name created_at updated_at] }
specify { is_expected.to have_graphql_name('BranchRule') }
let(:fields) do
%i[
name
branch_protection
created_at
updated_at
]
end
specify { is_expected.to require_graphql_authorizations(:read_protected_branch) }
specify { is_expected.to have_graphql_description }
specify { is_expected.to have_graphql_fields(fields) }
describe 'graphql_fields' do
subject do
described_class.all_field_definitions
end
specify { is_expected.to all(have_graphql_description) }
end
end

View File

@ -260,6 +260,22 @@ RSpec.describe Gitlab::GitalyClient::RefService do
client.tags(sort_by: 'name_asc')
end
context 'with semantic version sorting' do
it 'sends a correct find_all_tags message' do
expected_sort_by = Gitaly::FindAllTagsRequest::SortBy.new(
key: :VERSION_REFNAME,
direction: :ASCENDING
)
expect_any_instance_of(Gitaly::RefService::Stub)
.to receive(:find_all_tags)
.with(gitaly_request_with_params(sort_by: expected_sort_by), kind_of(Hash))
.and_return([])
client.tags(sort_by: 'version_asc')
end
end
end
context 'with pagination option' do

View File

@ -20,18 +20,43 @@ RSpec.describe Gitlab::Mailgun::WebhookProcessors::FailureLogger do
context 'on permanent failure' do
let(:processor) { described_class.new(base_payload.merge({ 'severity' => 'permanent' })) }
it 'logs the failure immediately' do
expect(Gitlab::ErrorTracking::Logger).to receive(:error).with(
event: 'email_delivery_failure',
mailgun_event_id: base_payload['id'],
recipient: base_payload['recipient'],
failure_type: 'permanent',
failure_reason: base_payload['reason'],
failure_code: base_payload['delivery-status']['code'],
failure_message: base_payload['delivery-status']['message']
)
before do
allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits)
.and_return(permanent_email_failure: { threshold: 1, interval: 1.minute })
end
processor.execute
context 'when threshold is not exceeded' do
it 'increments counter but does not log the failure' do
expect(Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(
:permanent_email_failure, scope: 'recipient@gitlab.com'
).and_call_original
expect(Gitlab::ErrorTracking::Logger).not_to receive(:error)
processor.execute
end
end
context 'when threshold is exceeded' do
before do
processor.execute
end
it 'increments counter and logs the failure' do
expect(Gitlab::ApplicationRateLimiter).to receive(:throttled?).with(
:permanent_email_failure, scope: 'recipient@gitlab.com'
).and_call_original
expect(Gitlab::ErrorTracking::Logger).to receive(:error).with(
event: 'email_delivery_failure',
mailgun_event_id: base_payload['id'],
recipient: base_payload['recipient'],
failure_type: 'permanent',
failure_reason: base_payload['reason'],
failure_code: base_payload['delivery-status']['code'],
failure_message: base_payload['delivery-status']['message']
)
processor.execute
end
end
end

View File

@ -107,10 +107,8 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
'quickactions',
'pipeline_authoring',
'epics_usage',
'epic_boards_usage',
'secure',
'importer',
'network_policies',
'geo',
'growth',
'work_items',

View File

@ -130,18 +130,22 @@ RSpec.describe NamespaceSetting, type: :model do
describe '#show_diff_preview_in_email?' do
context 'when not a subgroup' do
it 'returns false' do
settings = create(:namespace_settings, show_diff_preview_in_email: false)
group = create(:group, namespace_settings: settings )
context 'when :show_diff_preview_in_email is false' do
it 'returns false' do
settings = create(:namespace_settings, show_diff_preview_in_email: false)
group = create(:group, namespace_settings: settings )
expect(group.show_diff_preview_in_email?).to be_falsey
expect(group.show_diff_preview_in_email?).to be_falsey
end
end
it 'returns true' do
settings = create(:namespace_settings, show_diff_preview_in_email: true)
group = create(:group, namespace_settings: settings )
context 'when :show_diff_preview_in_email is true' do
it 'returns true' do
settings = create(:namespace_settings, show_diff_preview_in_email: true)
group = create(:group, namespace_settings: settings )
expect(group.show_diff_preview_in_email?).to be_truthy
expect(group.show_diff_preview_in_email?).to be_truthy
end
end
it 'does not query the db when there is no parent group' do

View File

@ -39,6 +39,56 @@ RSpec.describe NotificationRecipient do
expect(recipient.notifiable?).to eq true
end
end
context 'when recipient email is blocked', :clean_gitlab_redis_rate_limiting do
before do
allow(Gitlab::ApplicationRateLimiter).to receive(:rate_limits)
.and_return(
temporary_email_failure: { threshold: 1, interval: 1.minute },
permanent_email_failure: { threshold: 1, interval: 1.minute }
)
end
context 'with permanent failures' do
before do
2.times { Gitlab::ApplicationRateLimiter.throttled?(:permanent_email_failure, scope: user.email) }
end
it 'returns false' do
expect(recipient.notifiable?).to eq(false)
end
context 'when block_emails_with_failures is disabled' do
before do
stub_feature_flags(block_emails_with_failures: false)
end
it 'returns true' do
expect(recipient.notifiable?).to eq(true)
end
end
end
context 'with temporary failures' do
before do
2.times { Gitlab::ApplicationRateLimiter.throttled?(:temporary_email_failure, scope: user.email) }
end
it 'returns false' do
expect(recipient.notifiable?).to eq(false)
end
context 'when block_emails_with_failures is disabled' do
before do
stub_feature_flags(block_emails_with_failures: false)
end
it 'returns true' do
expect(recipient.notifiable?).to eq(true)
end
end
end
end
end
describe '#has_access?' do

View File

@ -161,6 +161,33 @@ RSpec.describe Repository do
end
end
context 'semantic versioning sort' do
let(:version_two) { 'v2.0.0' }
let(:version_ten) { 'v10.0.0' }
before do
repository.add_tag(user, version_two, repository.commit.id)
repository.add_tag(user, version_ten, repository.commit.id)
end
after do
repository.rm_tag(user, version_two)
repository.rm_tag(user, version_ten)
end
context 'desc' do
subject { repository.tags_sorted_by('version_desc').map(&:name) & (tags_to_compare + [version_two, version_ten]) }
it { is_expected.to eq([version_ten, version_two, 'v1.1.0', 'v1.0.0']) }
end
context 'asc' do
subject { repository.tags_sorted_by('version_asc').map(&:name) & (tags_to_compare + [version_two, version_ten]) }
it { is_expected.to eq(['v1.0.0', 'v1.1.0', version_two, version_ten]) }
end
end
context 'unknown option' do
subject { repository.tags_sorted_by('unknown_desc').map(&:name) & tags_to_compare }

View File

@ -0,0 +1,60 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'getting branch protection for a branch rule' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:branch_rule) { create(:protected_branch) }
let_it_be(:project) { branch_rule.project }
let(:branch_protection_data) do
graphql_data_at('project', 'branchRules', 'nodes', 0, 'branchProtection')
end
let(:variables) { { path: project.full_path } }
let(:fields) { all_graphql_fields_for('BranchProtection') }
let(:query) do
<<~GQL
query($path: ID!) {
project(fullPath: $path) {
branchRules(first: 1) {
nodes {
branchProtection {
#{fields}
}
}
}
}
}
GQL
end
context 'when the user does not have read_protected_branch abilities' do
before do
project.add_guest(current_user)
post_graphql(query, current_user: current_user, variables: variables)
end
it_behaves_like 'a working graphql query'
it { expect(branch_protection_data).not_to be_present }
end
context 'when the user does have read_protected_branch abilities' do
before do
project.add_maintainer(current_user)
post_graphql(query, current_user: current_user, variables: variables)
end
it_behaves_like 'a working graphql query'
it 'includes allow_force_push' do
expect(branch_protection_data['allowForcePush']).to be_in([true, false])
expect(branch_protection_data['allowForcePush']).to eq(branch_rule.allow_force_push)
end
end
end

View File

@ -34,6 +34,7 @@ RSpec.describe API::GroupExport do
before do
allow_next_instance_of(Gitlab::ApplicationRateLimiter::BaseStrategy) do |strategy|
allow(strategy).to receive(:increment).and_return(0)
allow(strategy).to receive(:read).and_return(0)
end
upload.export_file = fixture_file_upload('spec/fixtures/group_export.tar.gz', "`/tar.gz")

View File

@ -9,6 +9,7 @@ RSpec.describe API::Search do
let_it_be(:repo_project) { create(:project, :public, :repository, group: group) }
before do
allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).and_return(0)
allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).with(:search_rate_limit).and_return(1000)
allow(Gitlab::ApplicationRateLimiter).to receive(:threshold).with(:search_rate_limit_unauthenticated).and_return(1000)
end

View File

@ -0,0 +1,121 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MergeRequests::Mergeability::Logger, :request_store do
let_it_be(:merge_request) { create(:merge_request) }
subject(:logger) { described_class.new(merge_request: merge_request) }
let(:caller_id) { 'a' }
before do
allow(Gitlab::ApplicationContext).to receive(:current_context_attribute).with(:caller_id).and_return(caller_id)
end
def loggable_data(**extras)
{
'mergeability.expensive_operation.duration_s.values' => a_kind_of(Array),
"mergeability_merge_request_id" => merge_request.id,
"correlation_id" => a_kind_of(String),
"mergeability_project_id" => merge_request.project.id
}.merge(extras)
end
describe '#instrument' do
let(:operation_count) { 1 }
context 'when enabled' do
it "returns the block's value" do
expect(logger.instrument(mergeability_name: :expensive_operation) { 123 }).to eq(123)
end
it 'records durations of instrumented operations' do
expect_next_instance_of(Gitlab::AppJsonLogger) do |app_logger|
expect(app_logger).to receive(:info).with(match(a_hash_including(loggable_data)))
end
expect(logger.instrument(mergeability_name: :expensive_operation) { 123 }).to eq(123)
logger.commit
end
context 'with multiple observations' do
let(:operation_count) { 2 }
it 'records durations of instrumented operations' do
expect_next_instance_of(Gitlab::AppJsonLogger) do |app_logger|
expect(app_logger).to receive(:info).with(match(a_hash_including(loggable_data)))
end
2.times do
expect(logger.instrument(mergeability_name: :expensive_operation) { 123 }).to eq(123)
end
logger.commit
end
end
context 'when its a query' do
let(:extra_data) do
{
'mergeability.expensive_operation.db_count.values' => a_kind_of(Array),
'mergeability.expensive_operation.db_main_count.values' => a_kind_of(Array),
'mergeability.expensive_operation.db_main_duration_s.values' => a_kind_of(Array),
'mergeability.expensive_operation.db_primary_count.values' => a_kind_of(Array),
'mergeability.expensive_operation.db_primary_duration_s.values' => a_kind_of(Array)
}
end
context 'with a single query' do
it 'includes SQL metrics' do
expect_next_instance_of(Gitlab::AppJsonLogger) do |app_logger|
expect(app_logger).to receive(:info).with(match(a_hash_including(loggable_data(**extra_data))))
end
expect(logger.instrument(mergeability_name: :expensive_operation) { MergeRequest.count }).to eq(1)
logger.commit
end
end
context 'with multiple queries' do
it 'includes SQL metrics' do
expect_next_instance_of(Gitlab::AppJsonLogger) do |app_logger|
expect(app_logger).to receive(:info).with(match(a_hash_including(loggable_data(**extra_data))))
end
expect(logger.instrument(mergeability_name: :expensive_operation) { Project.count + MergeRequest.count })
.to eq(2)
logger.commit
end
end
end
end
context 'when disabled' do
before do
stub_feature_flags(mergeability_checks_logger: false)
end
it "returns the block's value" do
expect(logger.instrument(mergeability_name: :expensive_operation) { 123 }).to eq(123)
end
it 'does not call the logger' do
expect(Gitlab::AppJsonLogger).not_to receive(:new)
expect(logger.instrument(mergeability_name: :expensive_operation) { Project.count + MergeRequest.count })
.to eq(2)
logger.commit
end
end
it 'raises an error when block is not provided' do
expect { logger.instrument(mergeability_name: :expensive_operation) }
.to raise_error(ArgumentError, 'block not given')
end
end
end

View File

@ -69,6 +69,11 @@ RSpec.describe MergeRequests::Mergeability::RunChecksService do
expect(service).to receive(:read).with(merge_check: merge_check).and_return(success_result)
end
expect_next_instance_of(MergeRequests::Mergeability::Logger, merge_request: merge_request) do |logger|
expect(logger).to receive(:instrument).with(mergeability_name: 'check_ci_status_service').and_call_original
expect(logger).to receive(:commit)
end
expect(execute.success?).to eq(true)
end
end
@ -80,6 +85,11 @@ RSpec.describe MergeRequests::Mergeability::RunChecksService do
expect(service).to receive(:write).with(merge_check: merge_check, result_hash: success_result.to_hash).and_return(true)
end
expect_next_instance_of(MergeRequests::Mergeability::Logger, merge_request: merge_request) do |logger|
expect(logger).to receive(:instrument).with(mergeability_name: 'check_ci_status_service').and_call_original
expect(logger).to receive(:commit)
end
expect(execute.success?).to eq(true)
end
end

View File

@ -0,0 +1,137 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ObjectStorage::CDN::GoogleCDN, :use_clean_rails_memory_store_caching do
include StubRequests
let(:key) { SecureRandom.hex }
let(:key_name) { 'test-key' }
let(:options) { { url: 'https://cdn.gitlab.example.com', key_name: key_name, key: Base64.urlsafe_encode64(key) } }
let(:google_cloud_ips) { File.read(Rails.root.join('spec/fixtures/cdn/google_cloud.json')) }
let(:headers) { { 'Content-Type' => 'application/json' } }
let(:public_ip) { '18.245.0.42' }
subject { described_class.new(options) }
before do
WebMock.stub_request(:get, described_class::GOOGLE_IP_RANGES_URL)
.to_return(status: 200, body: google_cloud_ips, headers: headers)
end
describe '#use_cdn?' do
using RSpec::Parameterized::TableSyntax
where(:ip_address, :expected) do
'34.80.0.1' | false
'18.245.0.42' | true
'2500:1900:4180:0000:0000:0000:0000:0000' | true
'2600:1900:4180:0000:0000:0000:0000:0000' | false
'10.10.1.5' | false
'fc00:0000:0000:0000:0000:0000:0000:0000' | false
end
with_them do
it { expect(subject.use_cdn?(ip_address)).to eq(expected) }
end
it 'caches the value' do
expect(subject.use_cdn?(public_ip)).to be true
expect(Rails.cache.fetch(described_class::GOOGLE_CDN_LIST_KEY)).to be_present
expect(Gitlab::ProcessMemoryCache.cache_backend.fetch(described_class::GOOGLE_CDN_LIST_KEY)).to be_present
end
context 'when the key name is missing' do
let(:options) { { url: 'https://cdn.gitlab.example.com', key: Base64.urlsafe_encode64(SecureRandom.hex) } }
it 'returns false' do
expect(subject.use_cdn?(public_ip)).to be false
end
end
context 'when the key is missing' do
let(:options) { { url: 'https://invalid.example.com' } }
it 'returns false' do
expect(subject.use_cdn?(public_ip)).to be false
end
end
context 'when the key is invalid' do
let(:options) { { key_name: key_name, key: '\0x1' } }
it 'returns false' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original
expect(subject.use_cdn?(public_ip)).to be false
end
end
context 'when the URL is missing' do
let(:options) { { key: Base64.urlsafe_encode64(SecureRandom.hex) } }
it 'returns false' do
expect(subject.use_cdn?(public_ip)).to be false
end
end
shared_examples 'IP range retrieval failure' do
it 'does not cache the result and logs an error' do
expect(Gitlab::ErrorTracking).to receive(:log_exception).and_call_original
expect(subject.use_cdn?(public_ip)).to be false
expect(Rails.cache.fetch(described_class::GOOGLE_CDN_LIST_KEY)).to be_nil
expect(Gitlab::ProcessMemoryCache.cache_backend.fetch(described_class::GOOGLE_CDN_LIST_KEY)).to be_nil
end
end
context 'when the URL returns a 404' do
before do
WebMock.stub_request(:get, described_class::GOOGLE_IP_RANGES_URL).to_return(status: 404)
end
it_behaves_like 'IP range retrieval failure'
end
context 'when the URL returns too large of a payload' do
before do
stub_const("#{described_class}::RESPONSE_BODY_LIMIT", 300)
end
it_behaves_like 'IP range retrieval failure'
end
context 'when the URL returns HTML' do
let(:headers) { { 'Content-Type' => 'text/html' } }
it_behaves_like 'IP range retrieval failure'
end
context 'when the URL returns empty results' do
let(:google_cloud_ips) { '{}' }
it_behaves_like 'IP range retrieval failure'
end
end
describe '#signed_url' do
let(:path) { '/path/to/file.txt' }
it 'returns a valid signed URL' do
url = subject.signed_url(path)
expect(url).to start_with("#{options[:url]}#{path}")
uri = Addressable::URI.parse(url)
parsed_query = Rack::Utils.parse_nested_query(uri.query)
signature = parsed_query.delete('Signature')
signed_url = "#{options[:url]}#{path}?Expires=#{parsed_query['Expires']}&KeyName=#{key_name}"
computed_signature = OpenSSL::HMAC.digest('SHA1', key, signed_url)
aggregate_failures do
expect(parsed_query['Expires'].to_i).to be > 0
expect(parsed_query['KeyName']).to eq(key_name)
expect(signature).to eq(Base64.urlsafe_encode64(computed_signature))
end
end
end
end

View File

@ -0,0 +1,85 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ObjectStorage::CDN do
let(:cdn_options) do
{
'object_store' => {
'cdn' => {
'provider' => 'google',
'url' => 'https://gitlab.example.com',
'key_name' => 'test-key',
'key' => '12345'
}
}
}.freeze
end
let(:uploader_class) do
Class.new(GitlabUploader) do
include ObjectStorage::Concern
include ObjectStorage::CDN::Concern
private
# user/:id
def dynamic_segment
File.join(model.class.underscore, model.id.to_s)
end
end
end
let(:object) { build_stubbed(:user) }
subject { uploader_class.new(object, :file) }
context 'with CDN config' do
before do
uploader_class.options = Settingslogic.new(Gitlab.config.uploads.deep_merge(cdn_options))
end
describe '#use_cdn?' do
it 'returns true' do
expect_next_instance_of(ObjectStorage::CDN::GoogleCDN) do |cdn|
expect(cdn).to receive(:use_cdn?).and_return(true)
end
expect(subject.use_cdn?('18.245.0.1')).to be true
end
end
describe '#cdn_signed_url' do
it 'returns a URL' do
expect_next_instance_of(ObjectStorage::CDN::GoogleCDN) do |cdn|
expect(cdn).to receive(:signed_url).and_return("https://cdn.example.com/path")
end
expect(subject.cdn_signed_url).to eq("https://cdn.example.com/path")
end
end
end
context 'without CDN config' do
before do
uploader_class.options = Gitlab.config.uploads
end
describe '#use_cdn?' do
it 'returns false' do
expect(subject.use_cdn?('18.245.0.1')).to be false
end
end
end
context 'with an unknown CDN provider' do
before do
cdn_options['object_store']['cdn']['provider'] = 'amazon'
uploader_class.options = Settingslogic.new(Gitlab.config.uploads.deep_merge(cdn_options))
end
it 'raises an error' do
expect { subject.use_cdn?('18.245.0.1') }.to raise_error("Unknown CDN provider: amazon")
end
end
end