Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot
2025-05-15 21:12:45 +00:00
parent 9d935a8ee1
commit 1bf957f0b2
24 changed files with 806 additions and 103 deletions

View File

@ -1 +1 @@
5ce562f1608201580a29260861728a6e0a9bd087
f32d3d9029112f6740e784a031d4b9b60f49aa48

View File

@ -61,22 +61,37 @@ export default {
Boolean(this.sshUrl) && {
text: __('Visual Studio Code (SSH)'),
href: `${this.$options.vsCodeBaseUrl}${this.sshUrlEncoded}`,
extraAttrs: {
isUnsafeLink: true,
},
},
Boolean(this.httpUrl) && {
text: __('Visual Studio Code (HTTPS)'),
href: `${this.$options.vsCodeBaseUrl}${this.httpUrlEncoded}`,
extraAttrs: {
isUnsafeLink: true,
},
},
Boolean(this.sshUrl) && {
text: __('IntelliJ IDEA (SSH)'),
href: `${this.$options.jetBrainsBaseUrl}${this.sshUrlEncoded}`,
extraAttrs: {
isUnsafeLink: true,
},
},
Boolean(this.httpUrl) && {
text: __('IntelliJ IDEA (HTTPS)'),
href: `${this.$options.jetBrainsBaseUrl}${this.httpUrlEncoded}`,
extraAttrs: {
isUnsafeLink: true,
},
},
Boolean(this.xcodeUrl) && {
text: __('Xcode'),
href: this.xcodeUrl,
extraAttrs: {
isUnsafeLink: true,
},
},
].filter(Boolean);

View File

@ -0,0 +1,100 @@
- name: "GitLab Premium and Ultimate with Duo"
description: |
We're excited to announce GitLab Premium with Duo and GitLab Ultimate with Duo. GitLab Premium and Ultimate now include AI-native features.
GitLab's AI-native features include Code Suggestions and Chat within the IDE. Development teams can use these features to:
- Analyze, understand, and explain code
- Write secure code faster
- Quickly generate tests to maintain code quality
- Easily refactor code to improve performance or use specific libraries
stage: ai-powered
self-managed: true
gitlab-com: true
available_in: [Premium, Ultimate]
documentation_link: 'https://docs.gitlab.com/user/gitlab_duo/#summary-of-gitlab-duo-features'
image_url: https://about.gitlab.com/images/18_0/Premium_Duo.png
published_at: 2025-05-15
release: 18.0
- name: "Automatic reviews with Duo Code Review"
description: |
Duo Code Review provides valuable insights during the review process, but currently requires you to manually request reviews on each merge request.
You can now configure GitLab Duo Code Review to run automatically on merge requests by updating your project's merge request settings. When enabled, Duo Code Review automatically reviews merge requests unless:
- The merge request is marked as draft.
- The merge request contains no changes.
Automatic reviews ensure that all code in your project receives a review, consistently improving code quality across your codebase.
stage: create
self-managed: true
gitlab-com: true
available_in: [Premium, Ultimate]
documentation_link: 'https://docs.gitlab.com/user/project/merge_requests/duo_in_merge_requests/#automatic-reviews-from-gitlab-duo'
image_url: https://about.gitlab.com/images/18_0/create-auto-dcr.png
published_at: 2025-05-15
release: 18.0
- name: "GitLab Query Language views enhancements"
description: |
We've made significant improvements to GitLab Query Language (GLQL) views. These improvements include support for:
- The `>=` and `<=` operators for all date types
- The **View actions** dropdown in views
- The **Reload** action
- Field aliases
- Aliasing columns to a custom name in GLQL tables
We welcome your feedback on this enhancement, and on GLQL views in general, in [issue 509791](https://gitlab.com/gitlab-org/gitlab/-/issues/509791).
stage: plan
self-managed: true
gitlab-com: true
available_in: [Free, Premium, Ultimate]
documentation_link: 'https://docs.gitlab.com/user/glql/'
published_at: 2025-05-15
release: 18.0
- name: "New CI/CD analytics view for projects in limited availability"
description: |
The redesigned CI/CD analytics view transforms how your development teams analyze, monitor, and optimize pipeline performance
and reliability. Developers can access intuitive visualizations in the GitLab UI that reveal performance
trends and reliability metrics. Embedding these insights in your project repository eliminates context-switching
that disrupts developer flow. Teams can identify and address pipeline bottlenecks that drain productivity.
This enhancement leads to faster development cycles, improved collaboration, and data-driven confidence to optimize your
CI/CD workflows in GitLab.
stage: verify
self-managed: true
gitlab-com: true
available_in: [Free, Premium, Ultimate]
documentation_link: 'https://docs.gitlab.com/user/analytics/ci_cd_analytics/'
image_url: https://img.youtube.com/vi/78Nxbem9OAk/hqdefault.jpg
published_at: 2025-05-15
release: 18.0
- name: "Shared Kubernetes namespace for workspaces"
description: |
You can now create GitLab workspaces in a shared Kubernetes namespace. This removes the need to create
a new namespace for every workspace and eliminates the requirement to give elevated ClusterRole
permission to the agent. With this feature, you can more easily adopt workspaces in secure or
restricted environments, offering a simpler path to scale.
To enable shared namespaces, set the `shared_namespace` field in your agent configuration file to
specify the Kubernetes namespace you want to use for all workspaces.
Thank you to the half dozen community contributors who helped build this feature through
[GitLab's Co-Create program](https://about.gitlab.com/community/co-create/)!
stage: create
self-managed: true
gitlab-com: true
available_in: [Premium, Ultimate]
documentation_link: 'https://docs.gitlab.com/user/workspace/settings/#shared_namespace'
image_url: https://img.youtube.com/vi/CXakdRuoGgU/hqdefault.jpg
published_at: 2025-05-15
release: 18.0
- name: "Event data collection"
description: |
In GitLab 18.0, we are enabling event-level product usage data collection from GitLab Self-Managed and GitLab Dedicated instances. Unlike aggregated data, event-level data provides GitLab with deeper insights into usage, allowing us to improve user experience on the platform and increase feature adoption. For detailed instructions on how to adjust data sharing settings, please refer to our documentation.
stage: monitor
self-managed: true
gitlab-com: false
available_in: [Free, Premium, Ultimate]
documentation_link: 'https://docs.gitlab.com/administration/settings/event_data/'
published_at: 2025-05-15
release: 18.0

View File

@ -0,0 +1,8 @@
---
migration_job_name: FixIncompleteExternalAuditDestinations
description: Fix incomplete migrations of dependency tables for audit events
feature_category: audit_events
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189699
milestone: '18.1'
queued_migration_version: 20250429171748
finalized_by: # version of the migration that finalized this BBM

View File

@ -0,0 +1,8 @@
---
migration_job_name: FixIncompleteInstanceExternalAuditDestinations
description: Fix incomplete migrations of dependency tables for audit events
feature_category: audit_events
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/189699
milestone: '18.1'
queued_migration_version: 20250429171801
finalized_by: # version of the migration that finalized this BBM

View File

@ -12,7 +12,7 @@ class QueueBackfillExternalGroupAuditEventDestinations < Gitlab::Database::Migra
def up
# no-op because there was a bug in the original migration (double JSON encoding),
# which has been fixed by QueueBackfillExternalGroupAuditEventDestinationsFixed
# which has been fixed by QueueFixIncompleteInstanceExternalAuditDestinations
end
def down; end

View File

@ -11,18 +11,9 @@ class QueueBackfillExternalGroupAuditEventDestinationsFixed < Gitlab::Database::
SUB_BATCH_SIZE = 10
def up
delete_batched_background_migration(ORIGINAL_MIGRATION, :audit_events_external_audit_event_destinations, :id, [])
queue_batched_background_migration(
MIGRATION,
:audit_events_external_audit_event_destinations,
:id,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
# no-op because there was a bug in the migration
# replaced by QueueFixIncompleteInstanceExternalAuditDestinations
end
def down
delete_batched_background_migration(MIGRATION, :audit_events_external_audit_event_destinations, :id, [])
end
def down; end
end

View File

@ -11,19 +11,9 @@ class QueueBackfillExternalInstanceAuditEventDestinationsFixed < Gitlab::Databas
SUB_BATCH_SIZE = 10
def up
delete_batched_background_migration(ORIGINAL_MIGRATION, :audit_events_instance_external_audit_event_destinations,
:id, [])
queue_batched_background_migration(
MIGRATION,
:audit_events_instance_external_audit_event_destinations,
:id,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
# no-op because there was a bug in the migration
# replaced by QueueFixIncompleteExternalAuditDestinations
end
def down
delete_batched_background_migration(MIGRATION, :audit_events_instance_external_audit_event_destinations, :id, [])
end
def down; end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class QueueFixIncompleteExternalAuditDestinations < Gitlab::Database::Migration[2.3]
milestone '18.1'
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION = "FixIncompleteExternalAuditDestinations"
BATCH_SIZE = 100
SUB_BATCH_SIZE = 10
def up
queue_batched_background_migration(
MIGRATION,
:audit_events_external_audit_event_destinations,
:id,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :audit_events_external_audit_event_destinations, :id, [])
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class QueueFixIncompleteInstanceExternalAuditDestinations < Gitlab::Database::Migration[2.3]
milestone '18.1'
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION = "FixIncompleteInstanceExternalAuditDestinations"
BATCH_SIZE = 100
SUB_BATCH_SIZE = 10
def up
queue_batched_background_migration(
MIGRATION,
:audit_events_instance_external_audit_event_destinations,
:id,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :audit_events_instance_external_audit_event_destinations, :id, [])
end
end

View File

@ -0,0 +1 @@
d30e225a8dfbe92bd8dcb7b3fb1483bf0b1689633aac799f25bef9492362e928

View File

@ -0,0 +1 @@
f65a4a840431a711e8f8ad0e1e2628942fdbfd09318bba87147c03a8c0866277

View File

@ -24079,6 +24079,7 @@ Represents a ComplianceRequirementsControl associated with a ComplianceRequireme
| ---- | ---- | ----------- |
| <a id="compliancerequirementscontrolcontroltype"></a>`controlType` | [`String!`](#string) | Type of the compliance control. |
| <a id="compliancerequirementscontrolexpression"></a>`expression` | [`String`](#string) | Expression of the compliance control. |
| <a id="compliancerequirementscontrolexternalcontrolname"></a>`externalControlName` | [`String`](#string) | Name of the external control. |
| <a id="compliancerequirementscontrolexternalurl"></a>`externalUrl` | [`String`](#string) | URL of the external control. |
| <a id="compliancerequirementscontrolid"></a>`id` | [`ID!`](#id) | Compliance requirements control ID. |
| <a id="compliancerequirementscontrolname"></a>`name` | [`String!`](#string) | Name of the compliance control. |
@ -49539,6 +49540,7 @@ Attributes for defining a CI/CD variable.
| ---- | ---- | ----------- |
| <a id="compliancerequirementscontrolinputcontroltype"></a>`controlType` | [`String`](#string) | Type of the compliance control. |
| <a id="compliancerequirementscontrolinputexpression"></a>`expression` | [`String`](#string) | Expression of the compliance control. |
| <a id="compliancerequirementscontrolinputexternalcontrolname"></a>`externalControlName` | [`String`](#string) | Name of the external control. |
| <a id="compliancerequirementscontrolinputexternalurl"></a>`externalUrl` | [`String`](#string) | URL of the external control. |
| <a id="compliancerequirementscontrolinputname"></a>`name` | [`String!`](#string) | New name for the compliance requirement control. |
| <a id="compliancerequirementscontrolinputsecrettoken"></a>`secretToken` | [`String`](#string) | Secret token for an external control. |

View File

@ -2,9 +2,11 @@
stage: Software Supply Chain Security
group: Compliance
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
title: Compliance standards adherence dashboard
title: Compliance standards adherence dashboard (deprecated)
---
<!--- start_remove The following content will be removed on remove_date: '2026-02-01' -->
{{< details >}}
- Tier: Ultimate
@ -12,6 +14,13 @@ title: Compliance standards adherence dashboard
{{< /details >}}
{{< alert type="warning" >}}
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/470834) in GitLab 17.11
and is planned for removal in 18.6. Use the [compliance status report](compliance_status_report.md) instead.
{{< /alert >}}
{{< history >}}
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125875) GraphQL APIs in GitLab 16.2 [with a flag](../../../administration/feature_flags.md) named `compliance_adherence_report`. Disabled by default.
@ -150,3 +159,5 @@ To export the compliance standards adherence report for projects in a group:
1. Select **Export standards adherence report**.
A report is compiled and delivered to your email inbox as an attachment.
<!--- end_remove -->

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# This batched background migration is EE-only
class FixIncompleteExternalAuditDestinations < BatchedMigrationJob
feature_category :audit_events
def perform; end
end
end
end
Gitlab::BackgroundMigration::FixIncompleteExternalAuditDestinations.prepend_mod

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# This batched background migration is EE-only
class FixIncompleteInstanceExternalAuditDestinations < BatchedMigrationJob
feature_category :audit_events
def perform; end
end
end
end
Gitlab::BackgroundMigration::FixIncompleteInstanceExternalAuditDestinations.prepend_mod

View File

@ -51,6 +51,7 @@ module Gitlab
@ok = err_value.nil?
@value = ok? ? ok_value : err_value
end
private :initialize
# "#unwrap" corresponds to "unwrap" in Rust.
@ -110,7 +111,7 @@ module Gitlab
# @return [Result]
# @raise [TypeError]
def and_then(lambda_or_singleton_method)
validate_lambda_or_singleton_method(lambda_or_singleton_method)
validate_lambda_or_singleton_method(callee: lambda_or_singleton_method, invoking_method: __method__)
# Return/passthough the Result itself if it is an err
return self if err?
@ -119,7 +120,7 @@ module Gitlab
result = lambda_or_singleton_method.call(value)
unless result.is_a?(Result)
err_msg = "'Result##{__method__}' expects a lambda or singleton method object which returns a 'Result' " \
err_msg = "Result##{__method__} expects a lambda or singleton method object which returns a 'Result' " \
"type, but instead received '#{lambda_or_singleton_method.inspect}' which returned '#{result.class}'. " \
"Check that the previous method calls in the '#and_then' chain are correct."
raise(TypeError, err_msg)
@ -145,7 +146,7 @@ module Gitlab
# @return [Result]
# @raise [TypeError]
def map(lambda_or_singleton_method)
validate_lambda_or_singleton_method(lambda_or_singleton_method)
validate_lambda_or_singleton_method(callee: lambda_or_singleton_method, invoking_method: __method__)
# Return/passthrough the Result itself if it is an err
return self if err?
@ -154,7 +155,7 @@ module Gitlab
mapped_value = lambda_or_singleton_method.call(value)
if mapped_value.is_a?(Result)
err_msg = "'Result##{__method__}' expects a lambda or singleton method object which returns an unwrapped " \
err_msg = "Result##{__method__} expects a lambda or singleton method object which returns an unwrapped " \
"value, not a 'Result', but instead received '#{lambda_or_singleton_method.inspect}' which returned " \
"a 'Result'."
raise(TypeError, err_msg)
@ -164,6 +165,99 @@ module Gitlab
Result.ok(mapped_value)
end
# `map_err` is the inverse of `map`. It behaves identically, but it only processes `err` values
# instead of `ok` values.
#
# "#map_err" corresponds to "map_err" in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err
#
# @param [Proc, Method] lambda_or_singleton_method
# @return [Result]
# @raise [TypeError]
def map_err(lambda_or_singleton_method)
validate_lambda_or_singleton_method(callee: lambda_or_singleton_method, invoking_method: __method__)
# Return/passthrough the Result itself if it is an ok
return self if ok?
# If the Result is err, call the lambda or singleton method with the contained value
mapped_value = lambda_or_singleton_method.call(value)
if mapped_value.is_a?(Result)
err_msg = "Result##{__method__} expects a lambda or singleton method object which returns an unwrapped " \
"value, not a 'Result', but instead received '#{lambda_or_singleton_method.inspect}' which returned " \
"a 'Result'."
raise(TypeError, err_msg)
end
# wrap the returned mapped_value in an "err" Result.
Result.err(mapped_value)
end
# `inspect_ok` is similar to `map`, becuase it receives the wrapped `ok` value, but it does not allow modification
# of the value like `map`. The original result is always returned from `inspect_ok`.
#
# The passed lambda or singleton method must return, `nil`, to enforce the fact that the return value is ignored,
# and the original Result is always returned. This corresponds to the `void` type in YARD/RBS type annotations,
# and the `unit` type in Rust (https://doc.rust-lang.org/std/primitive.unit.html).
#
# If the passed method does not return `nil`, an error will be raised.
#
# "#inspect_ok" corresponds to "inspect" in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect
#
# But note that we could not call it `inspect` here, because that would conflict with the
# Kernel#inspect method in Ruby.
#
# @param [Proc, Method] lambda_or_singleton_method
# @return [Result]
# @raise [TypeError]
def inspect_ok(lambda_or_singleton_method)
validate_lambda_or_singleton_method(callee: lambda_or_singleton_method, invoking_method: __method__)
# Return/passthrough the Result itself if it is an err
return self if err?
# If the Result is ok, call the lambda or singleton method with the contained value
call_and_enforce_value_is_not_mutated(
callee: lambda_or_singleton_method,
value: value,
invoking_method: __method__
)
# Return/passthrough the original Result
self
end
# `inspect_err` is the inverse of `inspect_ok`. It behaves identically, but it only processes `err` values
# instead of `ok` values.
#
# The passed lambda or singleton method must return, `nil`, to enforce the fact that the return value is ignored,
# and the original Result is always returned. This corresponds to the `void` type in YARD/RBS type annotations,
# and the `unit` type in Rust (https://doc.rust-lang.org/std/primitive.unit.html).
#
# If the passed method does not return `nil`, an error will be raised.
#
# "#inspect_err" corresponds to "inspect_err" in Rust: https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err
#
# @param [Proc, Method] lambda_or_singleton_method
# @return [Result]
# @raise [TypeError]
def inspect_err(lambda_or_singleton_method)
validate_lambda_or_singleton_method(callee: lambda_or_singleton_method, invoking_method: __method__)
# Return/passthrough the Result itself if it is an ok
return self if ok?
# If the Result is err, call the lambda or singleton method with the contained value
call_and_enforce_value_is_not_mutated(
callee: lambda_or_singleton_method,
value: value,
invoking_method: __method__
)
# Return/passthrough the original Result
self
end
# `to_h` supports destructuring of a result object, for example: `result => { ok: }; puts ok`
#
# @return [Hash]
@ -205,29 +299,80 @@ module Gitlab
@value
end
# @param [Proc, Method] lambda_or_singleton_method
# @param [Proc, Method] callee
# @param [Symbol] invoking_method
# @return [void]
# @raise [TypeError]
def validate_lambda_or_singleton_method(lambda_or_singleton_method)
is_lambda = lambda_or_singleton_method.is_a?(Proc) && lambda_or_singleton_method.lambda?
def validate_lambda_or_singleton_method(callee:, invoking_method:)
is_lambda = callee.is_a?(Proc) && callee.lambda?
is_singleton_method =
lambda_or_singleton_method.is_a?(Method) && lambda_or_singleton_method.owner.singleton_class?
callee.is_a?(Method) && callee.owner.singleton_class?
unless is_lambda || is_singleton_method
err_msg = "'Result##{__method__}' expects a lambda or singleton method object, " \
"but instead received '#{lambda_or_singleton_method.inspect}'."
err_msg = "Result##{invoking_method} expects a lambda or singleton method object, " \
"but instead received '#{callee.inspect}'."
raise(TypeError, err_msg)
end
arity = lambda_or_singleton_method.arity
arity = callee.arity
return if arity == 1
return if arity == -1 && lambda_or_singleton_method.source_location[0].include?('rspec')
return if arity == -1 && callee.source_location[0].include?('rspec')
err_msg = "'Result##{__method__}' expects a lambda or singleton method object with a single argument " \
"(arity of 1), but instead received '#{lambda_or_singleton_method.inspect}' with an arity of #{arity}."
err_msg = "Result##{invoking_method} expects a lambda or singleton method object with a single argument " \
"(arity of 1), but instead received '#{callee.inspect}' with an arity of #{arity}."
raise(ArgumentError, err_msg)
end
# @param [Proc, Method] callee
# @param [Object] value
# @param [Symbol] invoking_method
# @return [void]
# @raise [RuntimeError]
def call_and_enforce_value_is_not_mutated(callee:, value:, invoking_method:)
value_before = value.clone
begin
marshalled_value_before = Marshal.dump(value)
rescue StandardError
# Marshal.dump will fail if there are singletons in the object
marshalled_value_before = nil
end
return_value_from_call = callee.call(value)
validate_return_value_is_void(return_value: return_value_from_call, invoking_method: invoking_method)
begin
marshalled_value_after = Marshal.dump(value)
rescue StandardError
# Marshal.dump will fail if there are singletons in the object
marshalled_value_after = nil
end
value_was_mutated =
# First do an equality check, but this might return a false positive for some deeply nested objects
# or objects which don't implement equality properly, so also do the marshalled value equality check
value_before != value || marshalled_value_before != marshalled_value_after
return unless value_was_mutated
raise "ERROR: #{callee} must not modify the passed value argument, " \
"because it was invoked via Result##{invoking_method}"
end
# @param [Proc, Method] return_value
# @param [Symbol] invoking_method
# @return [void]
# @raise [TypeError]
def validate_return_value_is_void(return_value:, invoking_method:)
return if return_value.nil?
err_msg = "The method passed to Result##{invoking_method} must always return 'nil' (void). This enforces " \
"that the return value is never used or modified. The existing 'Result' object is always passed along the " \
"chain unchanged. The return value received was '#{return_value.inspect}' instead of 'nil'."
raise(TypeError, err_msg)
end
end
end
end

View File

@ -127,6 +127,7 @@ describe('Code Dropdown component', () => {
expect(item.props('item').text).toBe(name);
expect(item.props('item').href).toContain(href);
expect(item.props('item').extraAttrs.isUnsafeLink).toBe(true);
});
it('closes the dropdown on click', () => {

View File

@ -6,9 +6,14 @@ require 'fast_spec_helper'
# This spec is intended to serve as documentation examples of idiomatic usage for the `Result` type.
# These examples can be executed as-is in a Rails console to see the results.
#
# To support this, we have intentionally used some `rubocop:disable` comments to allow for more
# explicit and readable examples.
# To support this, we have intentionally used some `rubocop:disable` and RubyMine `noinspection` comments
# to allow for more explicit and readable examples.
#
# There is also not much attempt to DRY up the examples. There is some duplication, but this is intentional to
# support easily understandable and readable examples.
#
# rubocop:disable RSpec/DescribedClass, Lint/ConstantDefinitionInBlock, RSpec/LeakyConstantDeclaration -- intentionally disabled per comment above
# noinspection MissingYardReturnTag, MissingYardParamTag - intentionally disabled per comment above
RSpec.describe Gitlab::Fp::Result, feature_category: :shared do
describe 'usage of Gitlab::Fp::Result.ok and Gitlab::Fp::Result.err' do
context 'when checked with .ok? and .err?' do
@ -159,7 +164,7 @@ RSpec.describe Gitlab::Fp::Result, feature_category: :shared do
describe 'enforcement of argument type' do
it 'raises TypeError if passed anything other than a lambda or singleton method object' do
ex = TypeError
msg = /expects a lambda or singleton method object/
msg = /Result#and_then expects a lambda or singleton method object/
# noinspection RubyMismatchedArgumentType -- intentionally passing invalid types
expect { Gitlab::Fp::Result.ok(1).and_then('string') }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).and_then(proc { Gitlab::Fp::Result.ok(1) }) }.to raise_error(ex, msg)
@ -172,7 +177,7 @@ RSpec.describe Gitlab::Fp::Result, feature_category: :shared do
it 'raises ArgumentError if passed lambda or singleton method object with an arity other than 1' do
expect do
Gitlab::Fp::Result.ok(1).and_then(->(a, b) { Gitlab::Fp::Result.ok(a + b) })
end.to raise_error(ArgumentError, /expects .* with a single argument \(arity of 1\)/)
end.to raise_error(ArgumentError, /Result#and_then expects .* with a single argument \(arity of 1\)/)
end
end
@ -180,7 +185,7 @@ RSpec.describe Gitlab::Fp::Result, feature_category: :shared do
it 'raises ArgumentError if passed lambda or singleton method object which returns non-Result type' do
expect do
Gitlab::Fp::Result.ok(1).and_then(->(a) { a + 1 })
end.to raise_error(TypeError, /expects .* which returns a 'Result' type/)
end.to raise_error(TypeError, /Result#and_then expects .* which returns a 'Result' type/)
end
end
end
@ -252,7 +257,7 @@ RSpec.describe Gitlab::Fp::Result, feature_category: :shared do
describe 'enforcement of argument type' do
it 'raises TypeError if passed anything other than a lambda or singleton method object' do
ex = TypeError
msg = /expects a lambda or singleton method object/
msg = /Result#map expects a lambda or singleton method object/
# noinspection RubyMismatchedArgumentType -- intentionally passing invalid types
expect { Gitlab::Fp::Result.ok(1).map('string') }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).map(proc { 1 }) }.to raise_error(ex, msg)
@ -265,7 +270,7 @@ RSpec.describe Gitlab::Fp::Result, feature_category: :shared do
it 'raises ArgumentError if passed lambda or singleton method object with an arity other than 1' do
expect do
Gitlab::Fp::Result.ok(1).map(->(a, b) { a + b })
end.to raise_error(ArgumentError, /expects .* with a single argument \(arity of 1\)/)
end.to raise_error(ArgumentError, /Result#map expects .* with a single argument \(arity of 1\)/)
end
end
@ -273,7 +278,318 @@ RSpec.describe Gitlab::Fp::Result, feature_category: :shared do
it 'raises TypeError if passed lambda or singleton method object which returns non-Result type' do
expect do
Gitlab::Fp::Result.ok(1).map(->(a) { Gitlab::Fp::Result.ok(a + 1) })
end.to raise_error(TypeError, /expects .* which returns an unwrapped value, not a 'Result'/)
end.to raise_error(TypeError, /Result#map expects .* which returns an unwrapped value, not a 'Result'/)
end
end
end
end
describe 'usage of #map_err' do
context 'when passed a proc' do
it 'ignores ok values in successful chain' do
initial_result = Gitlab::Fp::Result.ok(1)
final_result =
initial_result
.map_err(->(value) { value + 1 })
expect(final_result.ok?).to be(true)
expect(final_result.unwrap).to eq(1)
end
it 'returns first err value in failed chain' do
initial_result = Gitlab::Fp::Result.ok(1)
final_result =
initial_result
.and_then(->(value) { Gitlab::Fp::Result.err("invalid: #{value}") })
.map_err(->(value) { "#{value}, with map_err" })
expect(final_result.err?).to be(true)
expect(final_result.unwrap_err).to eq('invalid: 1, with map_err')
end
end
context 'when passed a module or class (singleton) method object' do
module MyModuleNotUsingResult
def self.double(value)
value * 2
end
class MyClassNotUsingResult
def self.triple(value)
value * 3
end
end
end
it 'processes the err value in failed chain' do
initial_result = Gitlab::Fp::Result.err(1)
final_result =
initial_result
.map_err(::MyModuleNotUsingResult.method(:double))
.map_err(::MyModuleNotUsingResult::MyClassNotUsingResult.method(:triple))
expect(final_result.err?).to be(true)
expect(final_result.unwrap_err).to eq(6)
end
end
describe 'type checking validation' do
describe 'enforcement of argument type' do
it 'raises TypeError if passed anything other than a lambda or singleton method object' do
ex = TypeError
msg = /Result#map_err expects a lambda or singleton method object/
# noinspection RubyMismatchedArgumentType -- intentionally passing invalid types
expect { Gitlab::Fp::Result.ok(1).map_err('string') }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).map_err(proc { 1 }) }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).map_err(1.method(:to_s)) }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).map_err(Integer.method(:to_s)) }.to raise_error(ex, msg)
end
end
describe 'enforcement of argument arity' do
it 'raises ArgumentError if passed lambda or singleton method object with an arity other than 1' do
expect do
Gitlab::Fp::Result.ok(1).map_err(->(a, b) { a + b })
end.to raise_error(ArgumentError, /Result#map_err expects .* with a single argument \(arity of 1\)/)
end
end
describe 'enforcement that passed lambda or method does not return a Result type' do
it 'raises TypeError if passed lambda or singleton method object which returns non-Result type' do
expect do
Gitlab::Fp::Result.err(1).map_err(->(a) { Gitlab::Fp::Result.ok(a + 1) })
end.to raise_error(TypeError, /Result#map_err expects .* which returns an unwrapped value, not a 'Result'/)
end
end
end
end
describe 'usage of #inspect_ok' do
let(:logger) { instance_double(Logger, :info) }
context 'when passed a proc' do
it 'returns last ok value in successful chain and performs side effect' do
expect(logger).to receive(:info)
initial_result = Gitlab::Fp::Result.ok({ logger: logger })
final_result =
initial_result
.inspect_ok(->(context) { context[:logger].info })
expect(final_result.ok?).to be(true)
expect(final_result.unwrap).to eq({ logger: logger })
end
it 'returns first err value in failed chain and does not perform side effect' do
expect(logger).not_to receive(:info)
initial_result = Gitlab::Fp::Result.ok({ logger: logger })
final_result =
initial_result
.and_then(->(value) { Gitlab::Fp::Result.err("invalid: #{value}") })
.inspect_ok(->(context) { context[:logger].info })
expect(final_result.err?).to be(true)
expect(final_result.unwrap_err).to match(/invalid:.*logger.*:info/)
end
it 'cannot modify the Result passed along the chain', :unlimited_max_formatted_output_length do
initial_result = Gitlab::Fp::Result.ok({ logger: logger })
expect do
initial_result.inspect_ok(->(context) { context[:logger] = nil })
end.to raise_error(
RuntimeError, /Proc:.*must not modify the passed value.*because it was invoked via Result#inspect_ok/
)
end
end
context 'when passed a module or class (singleton) method object' do
module MyModuleNotUsingResult
def self.observe(context)
context[:logger].info
end
def self.modify!(context)
context[:logger] = "MODIFIED VALUE"
nil
end
end
it 'returns last ok value in successful chain and performs side effect' do
expect(logger).to receive(:info)
initial_result = Gitlab::Fp::Result.ok({ logger: logger })
final_result =
initial_result
.inspect_ok(::MyModuleNotUsingResult.method(:observe))
expect(final_result.ok?).to be(true)
expect(final_result.unwrap).to eq({ logger: logger })
end
it 'returns first err value in failed chain and does not perform side effect' do
expect(logger).not_to receive(:info)
initial_result = Gitlab::Fp::Result.ok({ logger: logger })
final_result =
initial_result
.and_then(->(value) { Gitlab::Fp::Result.err("invalid: #{value}") })
.inspect_ok(::MyModuleNotUsingResult.method(:observe))
expect(final_result.err?).to be(true)
expect(final_result.unwrap_err).to match(/invalid:.*logger.*:info/)
end
it 'cannot modify the Result passed along the chain', :unlimited_max_formatted_output_length do
initial_result = Gitlab::Fp::Result.ok({ logger: logger })
expect do
initial_result.inspect_ok(::MyModuleNotUsingResult.method(:modify!))
end.to raise_error(
RuntimeError,
/Method: MyModuleNotUsingResult.modify!\(context\).*not modify the.*value.*invoked via Result#inspect_ok/
)
end
end
describe 'type checking validation' do
describe 'enforcement of argument type' do
it 'raises TypeError if passed anything other than a lambda or singleton method object',
:unlimited_max_formatted_output_length do
ex = TypeError
msg = /Result#inspect_ok expects a lambda or singleton method object/
# noinspection RubyMismatchedArgumentType -- intentionally passing invalid types
expect { Gitlab::Fp::Result.ok(1).inspect_ok('string') }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).inspect_ok(proc { 1 }) }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).inspect_ok(1.method(:to_s)) }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).inspect_ok(Integer.method(:to_s)) }.to raise_error(ex, msg)
end
end
describe 'enforcement of argument arity' do
it 'raises ArgumentError if passed lambda or singleton method object with an arity other than 1' do
expect do
Gitlab::Fp::Result.ok(1).inspect_ok(->(a, b) { a + b })
end.to raise_error(ArgumentError, /Result#inspect_ok expects .* with a single argument \(arity of 1\)/)
end
end
describe 'enforcement that passed lambda or method returns nil (void)' do
it 'raises TypeError if passed lambda or singleton method object which does not return nil' do
expect do
Gitlab::Fp::Result.ok(1).inspect_ok(->(_) { "not nil" })
end.to raise_error(TypeError, /Result#inspect_ok.*must always return 'nil'/)
end
end
end
end
describe 'usage of #inspect_err' do
let(:logger) { instance_double(Logger, :info) }
context 'when passed a proc' do
it 'returns last ok value in successful chain and does not performs side effect' do
expect(logger).not_to receive(:info)
initial_result = Gitlab::Fp::Result.ok({ logger: logger })
final_result =
initial_result
.inspect_err(->(context) { context[:logger].info })
expect(final_result.ok?).to be(true)
expect(final_result.unwrap).to eq({ logger: logger })
end
it 'returns first err value in failed chain and performs side effect' do
expect(logger).to receive(:info)
initial_result = Gitlab::Fp::Result.err({ logger: logger })
final_result =
initial_result
.inspect_err(->(context) { context[:logger].info })
expect(final_result.err?).to be(true)
expect(final_result.unwrap_err).to eq({ logger: logger })
end
it 'cannot modify the Result passed along the chain', :unlimited_max_formatted_output_length do
initial_result = Gitlab::Fp::Result.err({ logger: logger })
expect do
initial_result.inspect_err(->(context) { context[:logger] = nil })
end.to raise_error(
RuntimeError, /Proc:.*must not modify the passed value.*because it was invoked via Result.inspect_err/
)
end
end
context 'when passed a module or class (singleton) method object' do
module MyModuleNotUsingResult
def self.observe(context)
context[:logger].info
end
end
it 'returns last ok value in successful chain and does not perform side effect' do
expect(logger).not_to receive(:info)
initial_result = Gitlab::Fp::Result.ok({ logger: logger })
final_result =
initial_result
.inspect_err(::MyModuleNotUsingResult.method(:observe))
expect(final_result.ok?).to be(true)
expect(final_result.unwrap).to eq({ logger: logger })
end
it 'returns first err value in failed chain and performs side effect' do
expect(logger).to receive(:info)
initial_result = Gitlab::Fp::Result.ok({ logger: logger })
final_result =
initial_result
.and_then(->(value) { Gitlab::Fp::Result.err(value) })
.inspect_err(::MyModuleNotUsingResult.method(:observe))
expect(final_result.err?).to be(true)
expect(final_result.unwrap_err).to eq({ logger: logger })
end
it 'cannot modify the Result passed along the chain', :unlimited_max_formatted_output_length do
initial_result = Gitlab::Fp::Result.err({ logger: logger })
expect do
initial_result.inspect_err(::MyModuleNotUsingResult.method(:modify!))
end.to raise_error(
RuntimeError,
/Method: MyModuleNotUsingResult.modify!\(context\).*not modify the.*value.*invoked via Result#inspect_err/
)
end
end
describe 'type checking validation' do
describe 'enforcement of argument type' do
it 'raises TypeError if passed anything other than a lambda or singleton method object' do
ex = TypeError
msg = /Result#inspect_err expects a lambda or singleton method object/
# noinspection RubyMismatchedArgumentType -- intentionally passing invalid types
expect { Gitlab::Fp::Result.ok(1).inspect_err('str') }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).inspect_err(proc { 1 }) }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).inspect_err(1.method(:to_s)) }.to raise_error(ex, msg)
expect { Gitlab::Fp::Result.ok(1).inspect_err(Integer.method(:to_s)) }.to raise_error(ex, msg)
end
end
describe 'enforcement of argument arity' do
it 'raises ArgumentError if passed lambda or singleton method object with an arity other than 1' do
expect do
Gitlab::Fp::Result.err(1).inspect_err(->(a, b) { a + b })
end.to raise_error(ArgumentError, /Result#inspect_err expects .* with a single argument \(arity of 1\)/)
end
end
describe 'enforcement that passed lambda or method returns nil (void)' do
it 'raises TypeError if passed lambda or singleton method object which does not return nil' do
expect do
Gitlab::Fp::Result.err(1).inspect_err(->(_) { "not nil" })
end.to raise_error(TypeError, /Result#inspect_err.*must always return 'nil'/)
end
end
end

View File

@ -8,30 +8,10 @@ RSpec.describe QueueBackfillExternalGroupAuditEventDestinationsFixed,
feature_category: :audit_events do
let(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
it 'is a no-op migration' do
# Simply verify that up and down do nothing
expect { migrate! }.not_to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :audit_events_external_audit_event_destinations,
column_name: :id,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
gitlab_schema: :gitlab_main
)
}
end
end
it 'removes scheduled migration when rolling back' do
disable_migrations_output do
migrate!
schema_migrate_down!
end
expect(batched_migration).not_to have_scheduled_batched_migration
expect { schema_migrate_down! }.not_to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }
end
end

View File

@ -8,30 +8,10 @@ RSpec.describe QueueBackfillExternalInstanceAuditEventDestinationsFixed,
feature_category: :audit_events do
let(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
it 'is a no-op migration' do
# Simply verify that up and down do nothing
expect { migrate! }.not_to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :audit_events_instance_external_audit_event_destinations,
column_name: :id,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
gitlab_schema: :gitlab_main
)
}
end
end
it 'removes scheduled migration when rolling back' do
disable_migrations_output do
migrate!
schema_migrate_down!
end
expect(batched_migration).not_to have_scheduled_batched_migration
expect { schema_migrate_down! }.not_to change { Gitlab::Database::BackgroundMigration::BatchedMigration.count }
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueFixIncompleteExternalAuditDestinations,
migration: :gitlab_main,
feature_category: :audit_events do
let(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :audit_events_external_audit_event_destinations,
column_name: :id,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
gitlab_schema: :gitlab_main
)
}
end
end
it 'removes scheduled migration when rolling back' do
disable_migrations_output do
migrate!
schema_migrate_down!
end
expect(batched_migration).not_to have_scheduled_batched_migration
end
end

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueFixIncompleteInstanceExternalAuditDestinations,
migration: :gitlab_main,
feature_category: :audit_events do
let(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :audit_events_instance_external_audit_event_destinations,
column_name: :id,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE,
gitlab_schema: :gitlab_main
)
}
end
end
it 'removes scheduled migration when rolling back' do
disable_migrations_output do
migrate!
schema_migrate_down!
end
expect(batched_migration).not_to have_scheduled_batched_migration
end
end

View File

@ -64,9 +64,9 @@ module InvokeRopSteps
"but was a #{step_action.class}"
end
unless [:map, :and_then].freeze.include?(step_action)
raise "'invoke_rop_steps' argument array entry second element ':#{step_action}' must be either " \
":map or :and_then, but was :#{step_action}"
unless [:and_then, :map, :map_err, :inspect_ok, :inspect_err].freeze.include?(step_action)
raise "'invoke_rop_steps' argument array entry second element ':#{step_action}' must be one of " \
":and_then, :map, :map_err, :inspect_ok, or :inspect_err, but was :#{step_action}"
end
end
end
@ -132,8 +132,10 @@ module InvokeRopSteps
expected_rop_step[:returned_object] = ok_results_for_steps[step_class]
elsif step_action == :and_then
expected_rop_step[:returned_object] = Gitlab::Fp::Result.ok(context_passed_along_steps)
elsif step_action == :map
elsif [:map, :map_err].freeze.include?(step_action)
expected_rop_step[:returned_object] = context_passed_along_steps
elsif [:inspect_ok, :inspect_err].freeze.include?(step_action)
expected_rop_step[:returned_object] = nil
else
raise "Unexpected internal error when building expected ROP steps: step_action '#{step_action}' is invalid"
end
@ -149,7 +151,7 @@ module InvokeRopSteps
step => {
step_class: Class => step_class,
step_class_method: Symbol => step_class_method,
returned_object: Gitlab::Fp::Result | Hash => returned_object
returned_object: Gitlab::Fp::Result | Hash | nil => returned_object
}
set_up_step_class_expectation(