mirror of
https://github.com/gitlabhq/gitlabhq.git
synced 2025-07-29 12:48:15 +00:00
Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
@ -1 +1 @@
|
||||
5ce562f1608201580a29260861728a6e0a9bd087
|
||||
f32d3d9029112f6740e784a031d4b9b60f49aa48
|
||||
|
@ -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);
|
||||
|
||||
|
100
data/whats_new/202505150001_18_0.yml
Normal file
100
data/whats_new/202505150001_18_0.yml
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
1
db/schema_migrations/20250429171748
Normal file
1
db/schema_migrations/20250429171748
Normal file
@ -0,0 +1 @@
|
||||
d30e225a8dfbe92bd8dcb7b3fb1483bf0b1689633aac799f25bef9492362e928
|
1
db/schema_migrations/20250429171801
Normal file
1
db/schema_migrations/20250429171801
Normal file
@ -0,0 +1 @@
|
||||
f65a4a840431a711e8f8ad0e1e2628942fdbfd09318bba87147c03a8c0866277
|
@ -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. |
|
||||
|
@ -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 -->
|
||||
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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', () => {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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(
|
||||
|
Reference in New Issue
Block a user