Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-10 03:07:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-10 03:07:13 +0300
commitec4891efa777d951afdbff95557bbcf5fda00188 (patch)
treed3e1ab6e1e05c5f3a2c11d8c5cb3acb7fc9fe423
parent617fb6c2b44c248443110a3a7101fcfca0eb68fe (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/layout/first_hash_element_indentation.yml1
-rw-r--r--.rubocop_todo/rspec/context_wording.yml1
-rw-r--r--.rubocop_todo/rspec/expect_change.yml1
-rw-r--r--.rubocop_todo/rspec/instance_variable.yml1
-rw-r--r--.rubocop_todo/rspec/named_subject.yml2
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION2
-rw-r--r--app/serializers/activity_pub/activity_serializer.rb40
-rw-r--r--app/serializers/activity_pub/activity_streams_serializer.rb90
-rw-r--r--app/serializers/activity_pub/actor_serializer.rb39
-rw-r--r--app/serializers/activity_pub/collection_serializer.rb68
-rw-r--r--app/serializers/activity_pub/object_serializer.rb35
-rw-r--r--app/serializers/activity_pub/publish_release_activity_serializer.rb7
-rw-r--r--app/serializers/activity_pub/releases_actor_serializer.rb2
-rw-r--r--app/serializers/activity_pub/releases_outbox_serializer.rb4
-rw-r--r--app/services/merge_requests/request_review_service.rb5
-rw-r--r--app/services/packages/npm/create_package_service.rb25
-rw-r--r--app/services/system_note_service.rb4
-rw-r--r--app/services/system_notes/issuables_service.rb6
-rw-r--r--config/feature_flags/development/manage_project_access_tokens.yml1
-rw-r--r--doc/api/graphql/reference/index.md6
-rw-r--r--doc/ci/testing/code_quality.md61
-rw-r--r--doc/development/gems.md4
-rw-r--r--doc/development/internal_analytics/service_ping/troubleshooting.md34
-rw-r--r--doc/development/permissions/custom_roles.md16
-rw-r--r--doc/user/application_security/dependency_scanning/index.md22
-rw-r--r--doc/user/application_security/secret_detection/pre_receive.md4
-rw-r--r--doc/user/custom_roles/abilities.md64
-rw-r--r--lib/api/npm_project_packages.rb18
-rw-r--r--spec/serializers/activity_pub/activity_serializer_spec.rb138
-rw-r--r--spec/serializers/activity_pub/activity_streams_serializer_spec.rb157
-rw-r--r--spec/serializers/activity_pub/publish_release_activity_serializer_spec.rb13
-rw-r--r--spec/serializers/activity_pub/releases_actor_serializer_spec.rb2
-rw-r--r--spec/services/merge_requests/request_review_service_spec.rb8
-rw-r--r--spec/services/packages/npm/create_package_service_spec.rb59
-rw-r--r--spec/services/system_note_service_spec.rb12
-rw-r--r--spec/services/system_notes/issuables_service_spec.rb15
-rw-r--r--tooling/custom_roles/docs/templates/custom_abilities.md.erb54
37 files changed, 620 insertions, 401 deletions
diff --git a/.rubocop_todo/layout/first_hash_element_indentation.yml b/.rubocop_todo/layout/first_hash_element_indentation.yml
index 05c0cfee47c..4a8ddfdbdf5 100644
--- a/.rubocop_todo/layout/first_hash_element_indentation.yml
+++ b/.rubocop_todo/layout/first_hash_element_indentation.yml
@@ -115,7 +115,6 @@ Layout/FirstHashElementIndentation:
- 'ee/spec/services/external_status_checks/destroy_service_spec.rb'
- 'ee/spec/services/groups/destroy_service_spec.rb'
- 'ee/spec/services/iterations/create_service_spec.rb'
- - 'ee/spec/services/projects/create_service_spec.rb'
- 'ee/spec/services/projects/disable_deploy_key_service_spec.rb'
- 'ee/spec/services/projects/enable_deploy_key_service_spec.rb'
- 'ee/spec/services/projects/group_links/create_service_spec.rb'
diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index 2238a9b722c..de893e11a73 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -749,7 +749,6 @@ RSpec/ContextWording:
- 'ee/spec/services/personal_access_tokens/rotation_verifier_service_spec.rb'
- 'ee/spec/services/projects/alerting/notify_service_spec.rb'
- 'ee/spec/services/projects/create_from_template_service_spec.rb'
- - 'ee/spec/services/projects/create_service_spec.rb'
- 'ee/spec/services/projects/destroy_service_spec.rb'
- 'ee/spec/services/projects/gitlab_projects_import_service_spec.rb'
- 'ee/spec/services/projects/group_links/create_service_spec.rb'
diff --git a/.rubocop_todo/rspec/expect_change.yml b/.rubocop_todo/rspec/expect_change.yml
index c5e3556defd..2b178901b39 100644
--- a/.rubocop_todo/rspec/expect_change.yml
+++ b/.rubocop_todo/rspec/expect_change.yml
@@ -133,7 +133,6 @@ RSpec/ExpectChange:
- 'ee/spec/services/iterations/delete_service_spec.rb'
- 'ee/spec/services/iterations/roll_over_issues_service_spec.rb'
- 'ee/spec/services/projects/alerting/notify_service_spec.rb'
- - 'ee/spec/services/projects/create_service_spec.rb'
- 'ee/spec/services/projects/destroy_service_spec.rb'
- 'ee/spec/services/projects/hashed_storage/migrate_attachments_service_spec.rb'
- 'ee/spec/services/projects/transfer_service_spec.rb'
diff --git a/.rubocop_todo/rspec/instance_variable.yml b/.rubocop_todo/rspec/instance_variable.yml
index aca1a6d2d97..9f268a71bd3 100644
--- a/.rubocop_todo/rspec/instance_variable.yml
+++ b/.rubocop_todo/rspec/instance_variable.yml
@@ -26,7 +26,6 @@ RSpec/InstanceVariable:
- 'ee/spec/services/geo/metrics_update_service_spec.rb'
- 'ee/spec/services/groups/participants_service_spec.rb'
- 'ee/spec/services/projects/create_from_template_service_spec.rb'
- - 'ee/spec/services/projects/create_service_spec.rb'
- 'ee/spec/support/shared_examples/views/subscription_shared_examples.rb'
- 'ee/spec/tasks/geo_rake_spec.rb'
- 'ee/spec/views/projects/security/corpus_management/show.html.haml_spec.rb'
diff --git a/.rubocop_todo/rspec/named_subject.yml b/.rubocop_todo/rspec/named_subject.yml
index 02da5b59f7f..58076addbcf 100644
--- a/.rubocop_todo/rspec/named_subject.yml
+++ b/.rubocop_todo/rspec/named_subject.yml
@@ -1100,7 +1100,6 @@ RSpec/NamedSubject:
- 'ee/spec/services/product_analytics/initialize_stack_service_spec.rb'
- 'ee/spec/services/projects/alerting/notify_service_spec.rb'
- 'ee/spec/services/projects/create_from_template_service_spec.rb'
- - 'ee/spec/services/projects/create_service_spec.rb'
- 'ee/spec/services/projects/destroy_service_spec.rb'
- 'ee/spec/services/projects/disable_legacy_inactive_projects_service_spec.rb'
- 'ee/spec/services/projects/fork_service_spec.rb'
@@ -3018,7 +3017,6 @@ RSpec/NamedSubject:
- 'spec/serializers/accessibility_error_entity_spec.rb'
- 'spec/serializers/accessibility_reports_comparer_entity_spec.rb'
- 'spec/serializers/accessibility_reports_comparer_serializer_spec.rb'
- - 'spec/serializers/activity_pub/activity_streams_serializer_spec.rb'
- 'spec/serializers/activity_pub/project_entity_spec.rb'
- 'spec/serializers/activity_pub/release_entity_spec.rb'
- 'spec/serializers/activity_pub/releases_actor_entity_spec.rb'
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index a84947d6ffe..6016e8addc4 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-4.5.0
+4.6.0
diff --git a/app/serializers/activity_pub/activity_serializer.rb b/app/serializers/activity_pub/activity_serializer.rb
new file mode 100644
index 00000000000..71a1bfece6b
--- /dev/null
+++ b/app/serializers/activity_pub/activity_serializer.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module ActivityPub
+ # Serializer for the `Activity` ActivityStreams model.
+ # Reference: https://www.w3.org/TR/activitystreams-core/#activities
+ class ActivitySerializer < ObjectSerializer
+ MissingActorError = Class.new(StandardError)
+ MissingObjectError = Class.new(StandardError)
+ IntransitiveWithObjectError = Class.new(StandardError)
+
+ private
+
+ def validate_response(serialized, opts)
+ response = super(serialized, opts)
+
+ unless response[:actor].present?
+ raise MissingActorError, "The serializer does not provide the mandatory 'actor' field."
+ end
+
+ if opts[:intransitive] && response[:object].present?
+ raise IntransitiveWithObjectError, <<~ERROR
+ The serializer does provide both the 'object' field and the :intransitive option.
+ Intransitive activities are meant precisely for when no object is available.
+ Please remove either of those.
+ See https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
+ ERROR
+ end
+
+ unless opts[:intransitive] || response[:object].present?
+ raise MissingObjectError, <<~ERROR
+ The serializer does not provide the mandatory 'object' field.
+ Pass the :intransitive option to #represent if this is an intransitive activity.
+ See https://www.w3.org/TR/activitystreams-vocabulary/#activity-types
+ ERROR
+ end
+
+ response
+ end
+ end
+end
diff --git a/app/serializers/activity_pub/activity_streams_serializer.rb b/app/serializers/activity_pub/activity_streams_serializer.rb
deleted file mode 100644
index 39caa4a6d10..00000000000
--- a/app/serializers/activity_pub/activity_streams_serializer.rb
+++ /dev/null
@@ -1,90 +0,0 @@
-# frozen_string_literal: true
-
-module ActivityPub
- class ActivityStreamsSerializer < ::BaseSerializer
- MissingIdentifierError = Class.new(StandardError)
- MissingTypeError = Class.new(StandardError)
- MissingOutboxError = Class.new(StandardError)
-
- alias_method :base_represent, :represent
-
- def represent(resource, opts = {}, entity_class = nil)
- response = if respond_to?(:paginated?) && paginated?
- represent_paginated(resource, opts, entity_class)
- else
- represent_whole(resource, opts, entity_class)
- end
-
- validate_response(HashWithIndifferentAccess.new(response))
- end
-
- private
-
- def validate_response(response)
- unless response[:id].present?
- raise MissingIdentifierError, "The serializer does not provide the mandatory 'id' field."
- end
-
- unless response[:type].present?
- raise MissingTypeError, "The serializer does not provide the mandatory 'type' field."
- end
-
- response
- end
-
- def represent_whole(resource, opts, entity_class)
- raise MissingOutboxError, 'Please provide an :outbox option for this actor' unless opts[:outbox].present?
-
- serialized = base_represent(resource, opts, entity_class)
-
- {
- :@context => "https://www.w3.org/ns/activitystreams",
- inbox: opts[:inbox],
- outbox: opts[:outbox]
- }.merge(serialized)
- end
-
- def represent_paginated(resources, opts, entity_class)
- if paginator.params['page'].present?
- represent_page(resources, resources.current_page, opts, entity_class)
- else
- represent_pagination_index(resources)
- end
- end
-
- def represent_page(resources, page, opts, entity_class)
- opts[:page] = page
- serialized = base_represent(resources, opts, entity_class)
-
- {
- :@context => 'https://www.w3.org/ns/activitystreams',
- type: 'OrderedCollectionPage',
- id: collection_url(page),
- prev: page > 1 ? collection_url(page - 1) : nil,
- next: page < resources.total_pages ? collection_url(page + 1) : nil,
- partOf: collection_url,
- orderedItems: serialized
- }
- end
-
- def represent_pagination_index(resources)
- {
- :@context => 'https://www.w3.org/ns/activitystreams',
- type: 'OrderedCollection',
- id: collection_url,
- totalItems: resources.total_count,
- first: collection_url(1),
- last: collection_url(resources.total_pages)
- }
- end
-
- def collection_url(page = nil)
- uri = URI.parse(paginator.request.url)
- uri.query ||= ""
- parts = uri.query.split('&').reject { |part| part =~ /^page=/ }
- parts << "page=#{page}" if page
- uri.query = parts.join('&')
- uri.to_s.sub(/\?$/, '')
- end
- end
-end
diff --git a/app/serializers/activity_pub/actor_serializer.rb b/app/serializers/activity_pub/actor_serializer.rb
new file mode 100644
index 00000000000..14ab43666ec
--- /dev/null
+++ b/app/serializers/activity_pub/actor_serializer.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module ActivityPub
+ # Serializer for the `Actor` ActivityStreams model.
+ # Reference: https://www.w3.org/TR/activitystreams-core/#actors
+ class ActorSerializer < ObjectSerializer
+ MissingOutboxError = Class.new(StandardError)
+
+ def represent(resource, opts = {}, entity_class = nil)
+ raise MissingInboxError, 'Please provide an :inbox option for this actor' unless opts[:inbox].present?
+ raise MissingOutboxError, 'Please provide an :outbox option for this actor' unless opts[:outbox].present?
+
+ super
+ end
+
+ private
+
+ def validate_response(response, _opts)
+ unless response[:id].present?
+ raise MissingIdentifierError, "The serializer does not provide the mandatory 'id' field."
+ end
+
+ unless response[:type].present?
+ raise MissingTypeError, "The serializer does not provide the mandatory 'type' field."
+ end
+
+ response
+ end
+
+ def wrap(serialized, opts)
+ parent_value = super(serialized, opts)
+
+ {
+ inbox: opts[:inbox],
+ outbox: opts[:outbox]
+ }.merge(parent_value)
+ end
+ end
+end
diff --git a/app/serializers/activity_pub/collection_serializer.rb b/app/serializers/activity_pub/collection_serializer.rb
new file mode 100644
index 00000000000..16c78eb1b7d
--- /dev/null
+++ b/app/serializers/activity_pub/collection_serializer.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module ActivityPub
+ # Serializer for the `Collection` ActivityStreams model.
+ # Reference: https://www.w3.org/TR/activitystreams-core/#collections
+ class CollectionSerializer < ::BaseSerializer
+ include WithPagination
+
+ NotPaginatedError = Class.new(StandardError)
+
+ alias_method :base_represent, :represent
+
+ def represent(resources, opts = {})
+ unless respond_to?(:paginated?) && paginated?
+ raise NotPaginatedError, 'Pass #with_pagination to the serializer or use ActivityPub::ObjectSerializer instead'
+ end
+
+ response = if paginator.params['page'].present?
+ represent_page(resources, paginator.params['page'].to_i, opts)
+ else
+ represent_pagination_index(resources)
+ end
+
+ HashWithIndifferentAccess.new(response)
+ end
+
+ private
+
+ def represent_page(resources, page, opts)
+ resources = paginator.paginate(resources)
+ opts[:page] = page
+ serialized = base_represent(resources, opts)
+
+ {
+ :@context => 'https://www.w3.org/ns/activitystreams',
+ type: 'OrderedCollectionPage',
+ id: collection_url(page),
+ prev: page > 1 ? collection_url(page - 1) : nil,
+ next: page < resources.total_pages ? collection_url(page + 1) : nil,
+ partOf: collection_url,
+ orderedItems: serialized
+ }
+ end
+
+ def represent_pagination_index(resources)
+ paginator.params['page'] = 1
+ resources = paginator.paginate(resources)
+
+ {
+ :@context => 'https://www.w3.org/ns/activitystreams',
+ type: 'OrderedCollection',
+ id: collection_url,
+ totalItems: resources.total_count,
+ first: collection_url(1),
+ last: collection_url(resources.total_pages)
+ }
+ end
+
+ def collection_url(page = nil)
+ uri = URI.parse(paginator.request.url)
+ uri.query ||= ""
+ parts = uri.query.split('&').reject { |part| part =~ /^page=/ }
+ parts << "page=#{page}" if page
+ uri.query = parts.join('&')
+ uri.to_s.sub(/\?$/, '')
+ end
+ end
+end
diff --git a/app/serializers/activity_pub/object_serializer.rb b/app/serializers/activity_pub/object_serializer.rb
new file mode 100644
index 00000000000..cdcef59cc41
--- /dev/null
+++ b/app/serializers/activity_pub/object_serializer.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module ActivityPub
+ # Serializer for the `Object` ActivityStreams model.
+ # Reference: https://www.w3.org/TR/activitystreams-core/#object
+ class ObjectSerializer < ::BaseSerializer
+ MissingIdentifierError = Class.new(StandardError)
+ MissingTypeError = Class.new(StandardError)
+
+ def represent(resource, opts = {}, entity_class = nil)
+ serialized = super(resource, opts, entity_class)
+ response = wrap(serialized, opts)
+
+ validate_response(HashWithIndifferentAccess.new(response), opts)
+ end
+
+ private
+
+ def wrap(serialized, _opts)
+ { :@context => "https://www.w3.org/ns/activitystreams" }.merge(serialized)
+ end
+
+ def validate_response(response, _opts)
+ unless response[:id].present?
+ raise MissingIdentifierError, "The serializer does not provide the mandatory 'id' field."
+ end
+
+ unless response[:type].present?
+ raise MissingTypeError, "The serializer does not provide the mandatory 'type' field."
+ end
+
+ response
+ end
+ end
+end
diff --git a/app/serializers/activity_pub/publish_release_activity_serializer.rb b/app/serializers/activity_pub/publish_release_activity_serializer.rb
new file mode 100644
index 00000000000..b70ff470af5
--- /dev/null
+++ b/app/serializers/activity_pub/publish_release_activity_serializer.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module ActivityPub
+ class PublishReleaseActivitySerializer < ActivitySerializer
+ entity ReleaseEntity
+ end
+end
diff --git a/app/serializers/activity_pub/releases_actor_serializer.rb b/app/serializers/activity_pub/releases_actor_serializer.rb
index 5bae83f2dc7..f4b33e25393 100644
--- a/app/serializers/activity_pub/releases_actor_serializer.rb
+++ b/app/serializers/activity_pub/releases_actor_serializer.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module ActivityPub
- class ReleasesActorSerializer < ActivityStreamsSerializer
+ class ReleasesActorSerializer < ActorSerializer
entity ReleasesActorEntity
end
end
diff --git a/app/serializers/activity_pub/releases_outbox_serializer.rb b/app/serializers/activity_pub/releases_outbox_serializer.rb
index b6d4e633fb0..6087e713e64 100644
--- a/app/serializers/activity_pub/releases_outbox_serializer.rb
+++ b/app/serializers/activity_pub/releases_outbox_serializer.rb
@@ -1,9 +1,7 @@
# frozen_string_literal: true
module ActivityPub
- class ReleasesOutboxSerializer < ActivityStreamsSerializer
- include WithPagination
-
+ class ReleasesOutboxSerializer < CollectionSerializer
entity ReleaseEntity
end
end
diff --git a/app/services/merge_requests/request_review_service.rb b/app/services/merge_requests/request_review_service.rb
index ebbae98352b..87b00aa088c 100644
--- a/app/services/merge_requests/request_review_service.rb
+++ b/app/services/merge_requests/request_review_service.rb
@@ -12,6 +12,7 @@ module MergeRequests
notify_reviewer(merge_request, user)
trigger_merge_request_reviewers_updated(merge_request)
+ create_system_note(merge_request, user)
success
else
@@ -25,5 +26,9 @@ module MergeRequests
notification_service.async.review_requested_of_merge_request(merge_request, current_user, reviewer)
todo_service.create_request_review_todo(merge_request, current_user, reviewer)
end
+
+ def create_system_note(merge_request, user)
+ ::SystemNoteService.request_review(merge_request, merge_request.project, current_user, user)
+ end
end
end
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
index 0f0dc297e9a..a27f059036c 100644
--- a/app/services/packages/npm/create_package_service.rb
+++ b/app/services/packages/npm/create_package_service.rb
@@ -8,24 +8,35 @@ module Packages
PACKAGE_JSON_NOT_ALLOWED_FIELDS = %w[readme readmeFilename licenseText contributors exports].freeze
DEFAULT_LEASE_TIMEOUT = 1.hour.to_i
+ ERROR_REASON_INVALID_PARAMETER = :invalid_parameter
+ ERROR_REASON_PACKAGE_EXISTS = :package_already_exists
+ ERROR_REASON_PACKAGE_LEASE_TAKEN = :package_lease_taken
+ ERROR_REASON_PACKAGE_PROTECTED = :package_attachment_data_empty
+
def execute
- return error('Version is empty.', 400) if version.blank?
- return error('Attachment data is empty.', 400) if attachment['data'].blank?
- return error('Package already exists.', 403) if current_package_exists?
- return error('Package protected.', 403) if current_package_protected?
- return error('File is too large.', 400) if file_size_exceeded?
+ return error('Version is empty.', ERROR_REASON_INVALID_PARAMETER) if version.blank?
+ return error('Attachment data is empty.', ERROR_REASON_INVALID_PARAMETER) if attachment['data'].blank?
+ return error('Package already exists.', ERROR_REASON_PACKAGE_EXISTS) if current_package_exists?
+ return error('Package protected.', ERROR_REASON_PACKAGE_PROTECTED) if current_package_protected?
+ return error('File is too large.', ERROR_REASON_INVALID_PARAMETER) if file_size_exceeded?
package = try_obtain_lease do
ApplicationRecord.transaction { create_npm_package! }
end
- return error('Could not obtain package lease. Please try again.', 400) unless package
+ unless package
+ return error('Could not obtain package lease. Please try again.', ERROR_REASON_PACKAGE_LEASE_TAKEN)
+ end
- package
+ ServiceResponse.success(payload: { package: package })
end
private
+ def error(message, reason)
+ ServiceResponse.error(message: message, reason: reason)
+ end
+
def create_npm_package!
package = create_package!(:npm, name: name, version: version)
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 9175d91119e..fc27303792b 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -45,6 +45,10 @@ module SystemNoteService
::SystemNotes::IssuablesService.new(noteable: issuable, project: project, author: author).change_issuable_reviewers(old_reviewers)
end
+ def request_review(issuable, project, author, user)
+ ::SystemNotes::IssuablesService.new(noteable: issuable, project: project, author: author).request_review(user)
+ end
+
def change_issuable_contacts(issuable, project, author, added_count, removed_count)
::SystemNotes::IssuablesService.new(noteable: issuable, project: project, author: author).change_issuable_contacts(added_count, removed_count)
end
diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb
index 7857bf20c8f..3f96ca9cefb 100644
--- a/app/services/system_notes/issuables_service.rb
+++ b/app/services/system_notes/issuables_service.rb
@@ -133,6 +133,12 @@ module SystemNotes
create_note(NoteSummary.new(noteable, project, author, body, action: 'reviewer'))
end
+ def request_review(user)
+ body = "#{self.class.issuable_events[:review_requested]} #{user.to_reference}"
+
+ create_note(NoteSummary.new(noteable, project, author, body, action: 'reviewer'))
+ end
+
# Called when the contacts of an issuable are changed or removed
# We intend to reference the contacts but for security we are just
# going to state how many were added/removed for now. See discussion:
diff --git a/config/feature_flags/development/manage_project_access_tokens.yml b/config/feature_flags/development/manage_project_access_tokens.yml
index a6cf2cf4f9f..7c979257515 100644
--- a/config/feature_flags/development/manage_project_access_tokens.yml
+++ b/config/feature_flags/development/manage_project_access_tokens.yml
@@ -6,3 +6,4 @@ milestone: '16.5'
type: development
group: group::authorization
default_enabled: false
+feature_flag: manage_project_access_tokens
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index fed954b7199..ca5664df80a 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -30857,11 +30857,11 @@ Member role permission.
| Value | Description |
| ----- | ----------- |
-| <a id="memberrolepermissionadmin_group_member"></a>`ADMIN_GROUP_MEMBER` | Allows to admin group members. |
-| <a id="memberrolepermissionadmin_merge_request"></a>`ADMIN_MERGE_REQUEST` | Allows to approve merge requests. |
+| <a id="memberrolepermissionadmin_group_member"></a>`ADMIN_GROUP_MEMBER` | Allows admin of group members. |
+| <a id="memberrolepermissionadmin_merge_request"></a>`ADMIN_MERGE_REQUEST` | Allows approval of merge requests. |
| <a id="memberrolepermissionadmin_terraform_state"></a>`ADMIN_TERRAFORM_STATE` | Allows to admin terraform state. |
| <a id="memberrolepermissionadmin_vulnerability"></a>`ADMIN_VULNERABILITY` | Allows admin access to the vulnerability reports. |
-| <a id="memberrolepermissionarchive_project"></a>`ARCHIVE_PROJECT` | Allows to archive projects. |
+| <a id="memberrolepermissionarchive_project"></a>`ARCHIVE_PROJECT` | Allows archiving of projects. |
| <a id="memberrolepermissionmanage_group_access_tokens"></a>`MANAGE_GROUP_ACCESS_TOKENS` | Allows manage access to the group access tokens. |
| <a id="memberrolepermissionmanage_project_access_tokens"></a>`MANAGE_PROJECT_ACCESS_TOKENS` | Allows manage access to the project access tokens. |
| <a id="memberrolepermissionread_code"></a>`READ_CODE` | Allows read-only access to the source code. |
diff --git a/doc/ci/testing/code_quality.md b/doc/ci/testing/code_quality.md
index ddea9181e65..9e6c409a0d3 100644
--- a/doc/ci/testing/code_quality.md
+++ b/doc/ci/testing/code_quality.md
@@ -220,7 +220,7 @@ To configure the Code Quality job:
1. Declare a job with the same name as the Code Quality job, after the template's inclusion.
1. Specify additional keys in the job's stanza.
-For an example, see [Download output in JSON format](#download-output-in-json-format).
+For an example, see [Download output in HTML format](#output-in-only-html-format).
## Available CI/CD variables
@@ -243,41 +243,19 @@ Code Quality can be customized by defining available CI/CD variables:
## Output
-Code Quality creates a file named `gl-code-quality-report.json`. The content of this file is
-processed internally and the results shown in the UI. To see the raw results, you can
-configure the Code Quality job to allow download of this file. Format options are JSON format, HTML
-format, or both. Use the HTML format to view the report in a more human-readable
-format. For example, you could publish the HTML format file on GitLab Pages for even easier
+Code Quality outputs a report containing details of issues found. The content of this report is
+processed internally and the results shown in the UI. The report is also output as a job artifact of
+the `code_quality` job, named `gl-code-quality-report.json`. You can optionally output the report in
+HTML format. For example, you could publish the HTML format file on GitLab Pages for even easier
reviewing.
-### Download output in JSON format
+### Output in JSON and HTML format
-To be able to download the Code Quality report in JSON format, declare the
-`gl-code-quality-report.json` file as an artifact of the `code_quality` job:
+To output the Code Quality report in JSON and HTML format, you create an additional job. This requires
+Code Quality to be run twice, once each for file format.
-```yaml
-include:
- - template: Code-Quality.gitlab-ci.yml
-
-code_quality:
- artifacts:
- paths: [gl-code-quality-report.json]
-```
-
-The full JSON file is available as a
-[downloadable artifact](../jobs/job_artifacts.md#download-job-artifacts) of the `code_quality`
-job.
-
-### Download output in JSON and HTML format
-
-> HTML report format [introduced](https://gitlab.com/gitlab-org/ci-cd/codequality/-/issues/10) in GitLab 13.6.
-
-NOTE:
-To create the HTML format file, the Code Quality job must be run twice, once for each format.
-In this configuration, the JSON format file is created but it is only processed internally.
-
-To be able to download the Code Quality report in both JSON and HTML format, add another job to your
-template by using `extends: code_quality`:
+To output the Code Quality report in HTML format, add another job to your template by using
+`extends: code_quality`:
```yaml
include:
@@ -291,18 +269,17 @@ code_quality_html:
paths: [gl-code-quality-report.html]
```
-Both the JSON and HTML files are available as
-[downloadable artifacts](../jobs/job_artifacts.md#download-job-artifacts) of the `code_quality`
-job.
+Both the JSON and HTML files are output as job artifacts. The HTML file is contained in the
+`artifacts.zip` job artifact.
-### Download output in only HTML format
+### Output in only HTML format
-To download the Code Quality report in _only_ an HTML format file, set `REPORT_FORMAT` to `html` in
-the existing job.
+To download the Code Quality report in _only_ HTML format, set `REPORT_FORMAT` to `html`, overriding
+the default definition of the `code_quality` job.
NOTE:
-This does not create a JSON format file, so Code Quality results are not shown in the
-merge request widget, pipeline report, or changes view.
+This does not create a JSON format file, so Code Quality results are not shown in the merge request
+widget, pipeline report, or changes view.
```yaml
include:
@@ -315,9 +292,7 @@ code_quality:
paths: [gl-code-quality-report.html]
```
-The HTML file is available as a
-[downloadable artifact](../jobs/job_artifacts.md#download-job-artifacts) of the `code_quality`
-job.
+The HTML file is output as a job artifact.
## Use Code Quality with merge request pipelines
diff --git a/doc/development/gems.md b/doc/development/gems.md
index 476ed8f916b..6b3e2cedc75 100644
--- a/doc/development/gems.md
+++ b/doc/development/gems.md
@@ -254,12 +254,12 @@ The project for a new Gem should always be created in [`gitlab-org/ruby/gems` na
1. Create a project in the [`gitlab-org/ruby/gems` group](https://gitlab.com/gitlab-org/ruby/gems/) (or in a subgroup of it):
1. Follow the [instructions for new projects](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#creating-a-new-project).
1. Follow the instructions for setting up a [CI/CD configuration](https://about.gitlab.com/handbook/engineering/gitlab-repositories/#cicd-configuration).
- 1. Use the [gem-release CI component](https://gitlab.com/gitlab-org/quality/pipeline-common/-/tree/master/gem-release)
+ 1. Use the [`gem-release` CI component](https://gitlab.com/gitlab-org/components/gem-release)
to release and publish new gem versions by adding the following to their `.gitlab-ci.yml`:
```yaml
include:
- - component: gitlab.com/gitlab-org/quality/pipeline-common/gem-release@<REPLACE WITH LATEST TAG FROM https://gitlab.com/gitlab-org/quality/pipeline-common/-/releases>
+ - component: gitlab.com/gitlab-org/components/gem-release/gem-release@~latest
```
This job will handle building and publishing the gem (it uses a `gitlab_rubygems` Rubygems.org
diff --git a/doc/development/internal_analytics/service_ping/troubleshooting.md b/doc/development/internal_analytics/service_ping/troubleshooting.md
index 1b74921fb2f..637aec384c0 100644
--- a/doc/development/internal_analytics/service_ping/troubleshooting.md
+++ b/doc/development/internal_analytics/service_ping/troubleshooting.md
@@ -90,39 +90,7 @@ However, it has the following limitations:
always runs as a process alongside other GitLab components on any given node. For Service Ping, none of the node data would therefore
appear to be associated to any of the services running, because they all appear to be running on different hosts. To alleviate this problem, the `node_exporter` in GCK was arbitrarily "assigned" to the `web` service, meaning only for this service `node_*` metrics appears in Service Ping.
-## Service Ping Payload drop
-
-### Symptoms
-
-You will be alerted by the [Data team](https://about.gitlab.com/handbook/business-technology/data-team/) and their [Monte Carlo alerting](https://about.gitlab.com/handbook/business-technology/data-team/platform/monte-carlo/).
-
-### Locating the problem
-
-First you need to identify at which stage in Service Ping data pipeline the drop is occurring.
-
-Start at [Service Ping Health Dashboard](https://app.periscopedata.com/app/gitlab/968489) on Sisense.
-
-You can use [this query](https://gitlab.com/gitlab-org/gitlab/-/issues/347298#note_836685350) as an example, to start detecting when the drop started.
-
-### Troubleshoot the GitLab application layer
-
-We conducted an investigation into an unexpected drop in Service ping Payload events volume.
-GitLab team members can view more information in this confidential issue:
-`https://gitlab.com/gitlab-data/analytics/-/issues/11071`
-
-### Troubleshoot VersionApp layer
-
-Check if the [export jobs](https://gitlab.com/gitlab-org/gitlab-services/version.gitlab.com/-/tree/main/#data-export-using-pipeline-schedules) are successful.
-
-Check [Service Ping errors](https://app.periscopedata.com/app/gitlab/968489?widget=14609989&udv=0) in the [Service Ping Health Dashboard](https://app.periscopedata.com/app/gitlab/968489).
-
-### Troubleshoot Google Storage layer
-
-Check if the files are present in [Google Storage](https://console.cloud.google.com/storage/browser/cloudsql-gs-production-efd5e8-cloudsql-exports;tab=objects?project=gs-production-efd5e8&prefix=&forceOnObjectsSortingFiltering=false).
-
-### Troubleshoot the data warehouse layer
-
-Reach out to the [Data team](https://about.gitlab.com/handbook/business-technology/data-team/) to ask about current state of data warehouse. On their handbook page there is a [section with contact details](https://about.gitlab.com/handbook/business-technology/data-team/#how-to-connect-with-us).
+## Troubleshooting
### Cannot disable Service Ping with the configuration file
diff --git a/doc/development/permissions/custom_roles.md b/doc/development/permissions/custom_roles.md
index 7c2e847c2bb..d2986aa3a59 100644
--- a/doc/development/permissions/custom_roles.md
+++ b/doc/development/permissions/custom_roles.md
@@ -169,7 +169,7 @@ For example, you see in `GroupPolicy` that there is an ability called
than adding a row to the `member_roles` table for each ability, consider
renaming them to `read_security_dashboard` and adding `read_security_dashboard`
to the `member_roles` table. This is more expected because it means that
-enabling `read_security_dashboard` on the parent group will enable the custom
+enabling `read_security_dashboard` on the parent group will enable the custom role.
For example, `GroupPolicy` has an ability called `read_group_security_dashboard` and `ProjectPolicy` has an ability
called `read_project_security_dashboard`. If you would like to make both customizable, rather than adding a row to the
`member_roles` table for each ability, consider renaming them to `read_security_dashboard` and adding
@@ -185,7 +185,9 @@ To add a new ability to a custom role:
- Generate YAML file by running `./ee/bin/custom-ability` generator
- Add a new column to `member_roles` table, either manually or by running `custom_roles:code` generator, eg. by running `rails generate gitlab:custom_roles:code --ability new_ability_name`. The ability parameter is case sensitive and has to exactly match the permission name from the YAML file.
- Add the ability to the respective Policy for example in [this change in merge request 114734](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114734/diffs#diff-content-edcbe28bdecbd848d4d9efdc5b5e9bddd2a7299e).
-- Update the specs. Don't forget to add a spec to `ee/spec/requests/custom_roles` - the spec template file was generated if you used the code generator
+- Update the specs. Don't forget to add a spec to `ee/spec/requests/custom_roles` - the spec template file was pre-generated if you used the code generator
+- Compile the documentation by running `bundle exec rake gitlab:custom_roles:compile_docs`
+- Update the GraphQL documentation by running `bundle exec rake gitlab:graphql:compile_docs`
Examples of merge requests adding new abilities to custom roles:
@@ -193,9 +195,15 @@ Examples of merge requests adding new abilities to custom roles:
- [Read vulnerability](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114734)
- [Admin vulnerability](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121534)
-The above merge request don't use YAML files and code generators. Some of the changes are not needed anymore. We will update the documentation once we have a permission implemented using the generators.
+The above merge requests don't use YAML files and code generators. Some of the changes are not needed anymore. We will update the documentation once we have a permission implemented using the generators.
-You should make sure a new custom roles ability is under a feature flag.
+If you have any concerns, put the new ability behind a feature flag.
+
+#### Documenting handling the feature flag
+
+- When you introduce a new custom ability under a feature flag, add the `feature_flag` attribute to the appropriate ability YAML file.
+- When you enable the ability by default, add the `feature_flag_enabled_milestone` and `feature_flag_enabled_mr` attributes to the appropriate ability YAML file and regenerate the documentation.
+- You do not have to include these attributes in the YAML file if the feature flag is enabled by default in the same release as the ability is introduced.
## Custom abilities definition
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 0f18a1252d0..de66aba57a8 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -818,15 +818,20 @@ The following variables allow configuration of global dependency scanning settin
The following variables configure the behavior of specific dependency scanning analyzers.
| CI/CD variable | Analyzer | Default | Description |
-|--------------------------------------| ------------------ | ---------------------------- |------------ |
+|--------------------------------------|--------------------|------------------------------|-------------|
| `GEMNASIUM_DB_LOCAL_PATH` | `gemnasium` | `/gemnasium-db` | Path to local Gemnasium database. |
-| `GEMNASIUM_DB_UPDATE_DISABLED` | `gemnasium` | `"false"` | Disable automatic updates for the `gemnasium-db` advisory database (For usage see: [examples](#hosting-a-copy-of-the-gemnasium_db-advisory-database))|
+| `GEMNASIUM_DB_UPDATE_DISABLED` | `gemnasium` | `"false"` | Disable automatic updates for the `gemnasium-db` advisory database. For usage see [Hosting a copy of the Gemnasium advisory database](#hosting-a-copy-of-the-gemnasium_db-advisory-database). |
| `GEMNASIUM_DB_REMOTE_URL` | `gemnasium` | `https://gitlab.com/gitlab-org/security-products/gemnasium-db.git` | Repository URL for fetching the Gemnasium database. |
| `GEMNASIUM_DB_REF_NAME` | `gemnasium` | `master` | Branch name for remote repository database. `GEMNASIUM_DB_REMOTE_URL` is required. |
| `DS_REMEDIATE` | `gemnasium` | `"true"`, `"false"` in FIPS mode | Enable automatic remediation of vulnerable dependencies. Not supported in FIPS mode. |
-| `DS_REMEDIATE_TIMEOUT` | `gemnasium` | `5m` | Timeout for auto-remediation. |
+| `DS_REMEDIATE_TIMEOUT` | `gemnasium` | `5m` | Timeout for auto-remediation. |
| `GEMNASIUM_LIBRARY_SCAN_ENABLED` | `gemnasium` | `"true"` | Enable detecting vulnerabilities in vendored JavaScript libraries. For now, `gemnasium` leverages [`Retire.js`](https://github.com/RetireJS/retire.js) to do this job. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/350512) in GitLab 14.8. |
-| `DS_JAVA_VERSION` | `gemnasium-maven` | `17` | Version of Java. Available versions: `8`, `11`, `17`, `21` |
+| `DS_INCLUDE_DEV_DEPENDENCIES` | `gemnasium` | `"true"` | When set to `"false"`, development dependencies and their vulnerabilities are not reported. Only projects using Composer, npm, pnpm, Pipenv or Poetry are supported. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227861) in GitLab 15.1. |
+| `GOOS` | `gemnasium` | `"linux"` | The operating system for which to compile Go code. |
+| `GOARCH` | `gemnasium` | `"amd64"` | The architecture of the processor for which to compile Go code. |
+| `GOFLAGS` | `gemnasium` | | The flags passed to the `go build` tool. |
+| `GOPRIVATE` | `gemnasium` | | A list of glob patterns and prefixes to be fetched from source. For more information, see the Go private modules [documentation](https://go.dev/ref/mod#private-modules). |
+| `DS_JAVA_VERSION` | `gemnasium-maven` | `17` | Version of Java. Available versions: `8`, `11`, `17`, `21`. |
| `MAVEN_CLI_OPTS` | `gemnasium-maven` | `"-DskipTests --batch-mode"` | List of command line arguments that are passed to `maven` by the analyzer. See an example for [using private repositories](../index.md#using-private-maven-repositories). |
| `GRADLE_CLI_OPTS` | `gemnasium-maven` | | List of command line arguments that are passed to `gradle` by the analyzer. |
| `SBT_CLI_OPTS` | `gemnasium-maven` | | List of command-line arguments that the analyzer passes to `sbt`. |
@@ -834,13 +839,8 @@ The following variables configure the behavior of specific dependency scanning a
| `PIP_EXTRA_INDEX_URL` | `gemnasium-python` | | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma-separated. **Warning:** Read [the following security consideration](#python-projects) when using this environment variable. |
| `PIP_REQUIREMENTS_FILE` | `gemnasium-python` | | Pip requirements file to be scanned. This is a filename and not a path. When this environment variable is set only the specified file is scanned. |
| `PIPENV_PYPI_MIRROR` | `gemnasium-python` | | If set, overrides the PyPi index used by Pipenv with a [mirror](https://github.com/pypa/pipenv/blob/v2022.1.8/pipenv/environments.py#L263). |
-| `DS_PIP_VERSION` | `gemnasium-python` | | Force the install of a specific pip version (example: `"19.3"`), otherwise the pip installed in the Docker image is used. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12811) in GitLab 12.7) |
-| `DS_PIP_DEPENDENCY_PATH` | `gemnasium-python` | | Path to load Python pip dependencies from. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12412) in GitLab 12.2) |
-| `DS_INCLUDE_DEV_DEPENDENCIES` | `gemnasium` | `"true"` | When set to `"false"`, development dependencies and their vulnerabilities are not reported. Only projects using Composer, npm, pnpm, Pipenv or Poetry are supported. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227861) in GitLab 15.1. |
-| `GOOS` | `gemnasium` | `"linux"` | The operating system for which to compile Go code. |
-| `GOARCH` | `gemnasium` | `"amd64"` | The architecture of the processor for which to compile Go code. |
-| `GOFLAGS` | `gemnasium` | | The flags passed to the `go build` tool. |
-| `GOPRIVATE` | `gemnasium` | | A list of glob patterns and prefixes to be fetched from source. Read the Go private modules [documentation](https://go.dev/ref/mod#private-modules) for more information. |
+| `DS_PIP_VERSION` | `gemnasium-python` | | Force the install of a specific pip version (example: `"19.3"`), otherwise the pip installed in the Docker image is used. |
+| `DS_PIP_DEPENDENCY_PATH` | `gemnasium-python` | | Path to load Python pip dependencies from. |
#### Other variables
diff --git a/doc/user/application_security/secret_detection/pre_receive.md b/doc/user/application_security/secret_detection/pre_receive.md
index f69e804a6f3..8bb56644926 100644
--- a/doc/user/application_security/secret_detection/pre_receive.md
+++ b/doc/user/application_security/secret_detection/pre_receive.md
@@ -29,7 +29,9 @@ Prerequisites:
## Limitations
-This feature only scans non-binary blobs under 1 MiB in size. Binary blobs and blobs larger than 1 MiB are not scanned.
+- This feature only scans non-binary blobs under 1 MiB in size. Binary blobs and blobs larger than 1 MiB are not scanned.
+- The scan does not analyze the content of a commit if it is identical to the content of another file already present in the source code.
+- The scan skips analyzing files that are renamed, deleted, or moved, unless their content is modified in the same commit.
## Resolve a blocked push
diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md
index 3e74fcc5a7d..d117a495798 100644
--- a/doc/user/custom_roles/abilities.md
+++ b/doc/user/custom_roles/abilities.md
@@ -1,9 +1,19 @@
---
stage: Govern
group: Authorization
-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
+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"
---
+<!---
+ This documentation is auto generated by a Rake task.
+
+ Please do not edit this file directly. To update this file, run:
+ bundle exec rake gitlab:custom_roles:compile_docs
+
+ To make changes to the output of the Rake task,
+ edit `tooling/custom_roles/docs/templates/custom_abilities.md.erb`.
+--->
+
# Available custom abilities
The following abilities are available. You can add these abilities in any combination
@@ -13,15 +23,43 @@ Some abilities require having other abilities enabled first. For example, admini
These requirements are documented in the `Required ability` column in the following table.
-| Ability | Version | Required ability | Description |
-| ------------------------------- | -----------------------| -------------------- | ----------- |
-| `read_code` | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106256) in GitLab 15.7 [with a flag](../../administration/feature_flags.md) named `customizable_roles`. [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114524) in GitLab 15.10.| Not applicable | View project code. Does not include the ability to pull code. |
-| `read_vulnerability` | [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/10160) in GitLab 16.1 [with a flag](../../administration/feature_flags.md) named `custom_roles_vulnerability`. [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124049) in GitLab 16.2. | Not applicable | View [vulnerability reports](../application_security/vulnerability_report/index.md). |
-| `admin_vulnerability` | [Introduced in GitLab 16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/412536). | `read_vulnerability` | Change the [status of vulnerabilities](../application_security/vulnerabilities/index.md#vulnerability-status-values). |
-| `read_dependency` | [Introduced in GitLab 16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/415255). | Not applicable | View [project dependencies](../application_security/dependency_list/index.md). |
-| `admin_merge_request` | [Introduced in GitLab 16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/412708). | Not applicable | View and approve [merge requests](../project/merge_requests/index.md), revoke merge request approval, and view the associated merge request code. <br> Does not allow users to view or change merge request approval rules. |
-| `manage_project_access_tokens` | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/421778) in GitLab 16.5 [with a flag](../../administration/feature_flags.md) named `manage_project_access_tokens` | Not applicable | Create, delete, and list [project access tokens](../project/settings/project_access_tokens.md). |
-| `admin_group_member` | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17364) in GitLab 16.5 | Not applicable | Add or remove [group members](../group/manage.md). |
-| `archive_project` | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/425957) in GitLab 16.7 | Not applicable | [Archive and unarchive projects](../project/settings/migrate_projects.md#archive-a-project). |
-| `remove_project` | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/425959) in GitLab 16.8 | Not applicable | [Delete projects](../project/working_with_projects.md#delete-a-project). |
-| `manage_group_access_tokens` | [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/428353) in GitLab 16.8 | Not applicable | [Create, delete, and list group access tokens](../group/settings/group_access_tokens.md). |
+## Code review workflow
+
+| Name | Required permission | Description | Introduced in | Feature flag | Enabled in |
+|:-----|:------------|:------------------|:---------|:--------------|:---------|
+| [`admin_merge_request`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128302) | | Allows approval of merge requests. | GitLab [16.4](https://gitlab.com/gitlab-org/gitlab/-/issues/412708) | | |
+| [`read_code`](https://gitlab.com/gitlab-org/gitlab/-/issues/376180) | | Allows read-only access to the source code. | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/20277) | `customizable_roles` | GitLab [15.9](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110810) |
+
+## Group and projects
+
+| Name | Required permission | Description | Introduced in | Feature flag | Enabled in |
+|:-----|:------------|:------------------|:---------|:--------------|:---------|
+| [`admin_group_member`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131914) | | Allows admin of group members. | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/17364) | `admin_group_member` | GitLab [16.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136247) |
+
+## Groups and projects
+
+| Name | Required permission | Description | Introduced in | Feature flag | Enabled in |
+|:-----|:------------|:------------------|:---------|:--------------|:---------|
+| [`archive_project`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134998) | | Allows archiving of projects. | GitLab [16.6](https://gitlab.com/gitlab-org/gitlab/-/issues/425957) | `archive_project` | GitLab [16.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139260) |
+| [`remove_project`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139696) | | Allows deletion of projects. | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/425959) | | |
+
+## Infrastructure as code
+
+| Name | Required permission | Description | Introduced in | Feature flag | Enabled in |
+|:-----|:------------|:------------------|:---------|:--------------|:---------|
+| [`admin_terraform_state`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140759) | | Allows to admin terraform state | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/421789) | | |
+
+## System access
+
+| Name | Required permission | Description | Introduced in | Feature flag | Enabled in |
+|:-----|:------------|:------------------|:---------|:--------------|:---------|
+| [`manage_group_access_tokens`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140115) | | Allows manage access to the group access tokens. | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/issues/428353) | | |
+| [`manage_project_access_tokens`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132342) | | Allows manage access to the project access tokens. | GitLab [16.5](https://gitlab.com/gitlab-org/gitlab/-/issues/421778) | `manage_project_access_tokens` | GitLab [16.8](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/141294) |
+
+## Vulnerability management
+
+| Name | Required permission | Description | Introduced in | Feature flag | Enabled in |
+|:-----|:------------|:------------------|:---------|:--------------|:---------|
+| [`admin_vulnerability`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121534) | read_vulnerability | Allows admin access to the vulnerability reports. | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/412536) | | |
+| [`read_dependency`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126247) | | Allows read-only access to the dependencies. | GitLab [16.3](https://gitlab.com/gitlab-org/gitlab/-/issues/415255) | | |
+| [`read_vulnerability`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/120704) | | Allows read-only access to the vulnerability reports. | GitLab [16.1](https://gitlab.com/gitlab-org/gitlab/-/issues/399119) | | |
diff --git a/lib/api/npm_project_packages.rb b/lib/api/npm_project_packages.rb
index e1d0455b1e2..c6c99944ca0 100644
--- a/lib/api/npm_project_packages.rb
+++ b/lib/api/npm_project_packages.rb
@@ -1,6 +1,13 @@
# frozen_string_literal: true
module API
class NpmProjectPackages < ::API::Base
+ ERROR_REASON_TO_HTTP_STATUS_MAPPTING = {
+ ::Packages::Npm::CreatePackageService::ERROR_REASON_INVALID_PARAMETER => 400,
+ ::Packages::Npm::CreatePackageService::ERROR_REASON_PACKAGE_LEASE_TAKEN => 400,
+ ::Packages::Npm::CreatePackageService::ERROR_REASON_PACKAGE_EXISTS => 403,
+ ::Packages::Npm::CreatePackageService::ERROR_REASON_PACKAGE_PROTECTED => 403
+ }.freeze
+
helpers ::API::Helpers::Packages::Npm
feature_category :package_registry
@@ -14,6 +21,10 @@ module API
def endpoint_scope
:project
end
+
+ def error_reason_to_http_status(reason)
+ ERROR_REASON_TO_HTTP_STATUS_MAPPTING.fetch(reason, 400)
+ end
end
params do
@@ -74,12 +85,13 @@ module API
else
authorize_create_package!(project)
- created_package = ::Packages::Npm::CreatePackageService
+ service_response = ::Packages::Npm::CreatePackageService
.new(project, current_user, params.merge(build: current_authenticated_job)).execute
- if created_package[:status] == :error
- render_structured_api_error!({ message: created_package[:message], error: created_package[:message] }, created_package[:http_status])
+ if service_response.error?
+ render_structured_api_error!({ message: service_response.message, error: service_response.message }, error_reason_to_http_status(service_response.reason))
else
+ created_package = service_response[:package]
enqueue_sync_metadata_cache_worker(project, created_package.name)
track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, namespace: project.namespace)
created_package
diff --git a/spec/serializers/activity_pub/activity_serializer_spec.rb b/spec/serializers/activity_pub/activity_serializer_spec.rb
new file mode 100644
index 00000000000..93b52614490
--- /dev/null
+++ b/spec/serializers/activity_pub/activity_serializer_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ActivityPub::ActivitySerializer, feature_category: :integrations do
+ let(:implementer_class) do
+ Class.new(described_class)
+ end
+
+ let(:serializer) { implementer_class.new.represent(resource) }
+
+ let(:resource) { build_stubbed(:release) }
+
+ let(:transitive_entity_class) do
+ Class.new(Grape::Entity) do
+ expose :id do |*|
+ 'https://example.com/unique/url'
+ end
+
+ expose :type do |*|
+ 'Follow'
+ end
+
+ expose :actor do |*|
+ 'https://example.com/actor/alice'
+ end
+
+ expose :object do |*|
+ 'https://example.com/actor/bob'
+ end
+ end
+ end
+
+ let(:intransitive_entity_class) do
+ Class.new(Grape::Entity) do
+ expose :id do |*|
+ 'https://example.com/unique/url'
+ end
+
+ expose :type do |*|
+ 'Question'
+ end
+
+ expose :actor do |*|
+ 'https://example.com/actor/alice'
+ end
+
+ expose :content do |*|
+ "What's up?"
+ end
+ end
+ end
+
+ let(:entity_class) { transitive_entity_class }
+
+ shared_examples_for 'activity document' do
+ it 'belongs to the ActivityStreams namespace' do
+ expect(serializer['@context']).to eq 'https://www.w3.org/ns/activitystreams'
+ end
+
+ it 'has a unique identifier' do
+ expect(serializer).to have_key 'id'
+ end
+
+ it 'has a type' do
+ expect(serializer).to have_key 'type'
+ end
+
+ it 'has an actor' do
+ expect(serializer['actor']).to eq 'https://example.com/actor/alice'
+ end
+ end
+
+ before do
+ implementer_class.entity entity_class
+ end
+
+ context 'with a valid represented entity' do
+ it_behaves_like 'activity document'
+ end
+
+ context 'when the represented entity provides no identifier' do
+ before do
+ allow(entity_class).to receive(:represent).and_return({ type: 'Person', actor: 'http://something/' })
+ end
+
+ it 'raises an exception' do
+ expect { serializer }.to raise_error(ActivityPub::ActivitySerializer::MissingIdentifierError)
+ end
+ end
+
+ context 'when the represented entity provides no type' do
+ before do
+ allow(entity_class).to receive(:represent).and_return({
+ id: 'http://something/',
+ actor: 'http://something-else/'
+ })
+ end
+
+ it 'raises an exception' do
+ expect { serializer }.to raise_error(ActivityPub::ActivitySerializer::MissingTypeError)
+ end
+ end
+
+ context 'when the represented entity provides no actor' do
+ before do
+ allow(entity_class).to receive(:represent).and_return({ id: 'http://something/', type: 'Person' })
+ end
+
+ it 'raises an exception' do
+ expect { serializer }.to raise_error(ActivityPub::ActivitySerializer::MissingActorError)
+ end
+ end
+
+ context 'when the represented entity provides no object' do
+ let(:entity_class) { intransitive_entity_class }
+
+ context 'when the caller provides the :intransitive option' do
+ let(:serializer) { implementer_class.new.represent(resource, intransitive: true) }
+
+ it_behaves_like 'activity document'
+ end
+
+ context 'when the caller does not provide the :intransitive option' do
+ it 'raises an exception' do
+ expect { serializer }.to raise_error(ActivityPub::ActivitySerializer::MissingObjectError)
+ end
+ end
+ end
+
+ context 'when the caller does provide the :intransitive option and an object' do
+ let(:serializer) { implementer_class.new.represent(resource, intransitive: true) }
+
+ it 'raises an exception' do
+ expect { serializer }.to raise_error(ActivityPub::ActivitySerializer::IntransitiveWithObjectError)
+ end
+ end
+end
diff --git a/spec/serializers/activity_pub/activity_streams_serializer_spec.rb b/spec/serializers/activity_pub/activity_streams_serializer_spec.rb
deleted file mode 100644
index c74beba7a81..00000000000
--- a/spec/serializers/activity_pub/activity_streams_serializer_spec.rb
+++ /dev/null
@@ -1,157 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe ActivityPub::ActivityStreamsSerializer, feature_category: :integrations do
- let(:implementer_class) do
- Class.new(described_class) do
- include WithPagination
- end
- end
-
- let(:entity_class) do
- Class.new(Grape::Entity) do
- expose :id do |*|
- 'https://example.com/unique/url'
- end
-
- expose :type do |*|
- 'Person'
- end
-
- expose :name do |*|
- 'Alice'
- end
- end
- end
-
- shared_examples_for 'ActivityStreams document' do
- it 'belongs to the ActivityStreams namespace' do
- expect(subject['@context']).to eq 'https://www.w3.org/ns/activitystreams'
- end
-
- it 'has a unique identifier' do
- expect(subject).to have_key 'id'
- end
-
- it 'has a type' do
- expect(subject).to have_key 'type'
- end
- end
-
- before do
- implementer_class.entity entity_class
- end
-
- context 'when the serializer is not paginated' do
- let(:resource) { build_stubbed(:release) }
- let(:outbox_url) { 'https://example.com/unique/url/outbox' }
-
- context 'with a valid represented entity' do
- subject { implementer_class.new.represent(resource, outbox: outbox_url) }
-
- it_behaves_like 'ActivityStreams document'
-
- it 'exposes an outbox' do
- expect(subject['outbox']).to eq 'https://example.com/unique/url/outbox'
- end
-
- it 'includes serialized data' do
- expect(subject['name']).to eq 'Alice'
- end
- end
-
- context 'when the represented entity provides no identifier' do
- subject { implementer_class.new.represent(resource, outbox: outbox_url) }
-
- before do
- allow(entity_class).to receive(:represent).and_return({ type: 'Person' })
- end
-
- it 'raises an exception' do
- expect { subject }.to raise_error(ActivityPub::ActivityStreamsSerializer::MissingIdentifierError)
- end
- end
-
- context 'when the represented entity provides no type' do
- subject { implementer_class.new.represent(resource, outbox: outbox_url) }
-
- before do
- allow(entity_class).to receive(:represent).and_return({ id: 'https://example.com/unique/url' })
- end
-
- it 'raises an exception' do
- expect { subject }.to raise_error(ActivityPub::ActivityStreamsSerializer::MissingTypeError)
- end
- end
-
- context 'when the caller provides no outbox parameter' do
- subject { implementer_class.new.represent(resource) }
-
- it 'raises an exception' do
- expect { subject }.to raise_error(ActivityPub::ActivityStreamsSerializer::MissingOutboxError)
- end
- end
- end
-
- context 'when the serializer is paginated' do
- let(:resources) { build_stubbed_list(:release, 3) }
- let(:request) { ActionDispatch::Request.new(request_data) }
- let(:response) { ActionDispatch::Response.new }
- let(:url) { 'https://example.com/resource/url' }
- let(:decorated) { implementer_class.new.with_pagination(request, response) }
-
- before do
- allow(resources).to receive(:page).and_return(resources)
- allow(resources).to receive(:per).and_return(resources)
- allow(resources).to receive(:current_page).and_return(2)
- allow(resources).to receive(:total_pages).and_return(3)
- allow(resources).to receive(:total_count).and_return(10)
- allow(decorated.paginator).to receive(:paginate).and_return(resources)
- end
-
- context 'when no page parameter is provided' do
- subject { decorated.represent(resources) }
-
- let(:request_data) do
- { "rack.url_scheme" => "https", "HTTP_HOST" => "example.com", "PATH_INFO" => '/resource/url' }
- end
-
- it_behaves_like 'ActivityStreams document'
-
- it 'is an index document for the pagination' do
- expect(subject['type']).to eq 'OrderedCollection'
- end
-
- it 'contains the total amount of items' do
- expect(subject['totalItems']).to eq 10
- end
-
- it 'contains links to first and last page' do
- expect(subject['first']).to eq "#{url}?page=1"
- expect(subject['last']).to eq "#{url}?page=3"
- end
- end
-
- context 'when a page parameter is provided' do
- subject { decorated.represent(resources) }
-
- let(:request_data) do
- { 'rack.url_scheme' => 'https', 'HTTP_HOST' => 'example.com', 'PATH_INFO' => '/resource/url',
- 'QUERY_STRING' => 'page=2&per_page=1' }
- end
-
- it_behaves_like 'ActivityStreams document'
-
- it 'is a page document' do
- expect(subject['type']).to eq 'OrderedCollectionPage'
- end
-
- it 'contains navigation links' do
- expect(subject['prev']).to be_present
- expect(subject['next']).to be_present
- expect(subject['partOf']).to be_present
- end
- end
- end
-end
diff --git a/spec/serializers/activity_pub/publish_release_activity_serializer_spec.rb b/spec/serializers/activity_pub/publish_release_activity_serializer_spec.rb
new file mode 100644
index 00000000000..287b806bb35
--- /dev/null
+++ b/spec/serializers/activity_pub/publish_release_activity_serializer_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ActivityPub::PublishReleaseActivitySerializer, feature_category: :release_orchestration do
+ let(:release) { build_stubbed(:release) }
+
+ let(:serializer) { described_class.new.represent(release) }
+
+ it 'serializes the activity attributes' do
+ expect(serializer).to include(:id, :type, :actor, :object)
+ end
+end
diff --git a/spec/serializers/activity_pub/releases_actor_serializer_spec.rb b/spec/serializers/activity_pub/releases_actor_serializer_spec.rb
index bc754eabe5c..47a170a04f5 100644
--- a/spec/serializers/activity_pub/releases_actor_serializer_spec.rb
+++ b/spec/serializers/activity_pub/releases_actor_serializer_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe ActivityPub::ReleasesActorSerializer, feature_category: :groups_a
let(:releases) { build_stubbed_list(:release, 3, project: project) }
context 'when there is a single object provided' do
- subject { described_class.new.represent(project, outbox: '/outbox') }
+ subject { described_class.new.represent(project, outbox: '/outbox', inbox: '/inbox') }
it 'serializes the actor attributes' do
expect(subject).to include(:id, :type, :preferredUsername, :name, :content, :context)
diff --git a/spec/services/merge_requests/request_review_service_spec.rb b/spec/services/merge_requests/request_review_service_spec.rb
index ef96bf11e0b..a5f0d5b5c5a 100644
--- a/spec/services/merge_requests/request_review_service_spec.rb
+++ b/spec/services/merge_requests/request_review_service_spec.rb
@@ -71,6 +71,14 @@ RSpec.describe MergeRequests::RequestReviewService, feature_category: :code_revi
service.execute(merge_request, user)
end
+ it 'creates a sytem note' do
+ expect(SystemNoteService)
+ .to receive(:request_review)
+ .with(merge_request, project, current_user, user)
+
+ service.execute(merge_request, user)
+ end
+
it_behaves_like 'triggers GraphQL subscription mergeRequestReviewersUpdated' do
let(:action) { result }
end
diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb
index 0d46d897b5b..7a91fdfc5b9 100644
--- a/spec/services/packages/npm/create_package_service_spec.rb
+++ b/spec/services/packages/npm/create_package_service_spec.rb
@@ -25,7 +25,13 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
let(:version_data) { params.dig('versions', version) }
let(:lease_key) { "packages:npm:create_package_service:packages:#{project.id}_#{package_name}_#{version}" }
+ shared_examples 'valid service response' do
+ it { is_expected.to be_success }
+ end
+
shared_examples 'valid package' do
+ let(:package) { subject[:package] }
+
it 'creates a package' do
expect { subject }
.to change { Packages::Package.count }.by(1)
@@ -34,21 +40,27 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
.and change { Packages::Npm::Metadatum.count }.by(1)
end
- it_behaves_like 'assigns the package creator' do
- let(:package) { subject }
- end
+ it_behaves_like 'assigns the package creator'
- it { is_expected.to be_valid }
- it { is_expected.to have_attributes name: package_name, version: version }
+ it 'returns a valid package' do
+ subject
- it { expect(subject.npm_metadatum.package_json).to eq(version_data) }
+ expect(package).to be_valid
+ .and have_attributes name: package_name, version: version
+ expect(package.npm_metadatum.package_json).to eq(version_data)
+ end
context 'with build info' do
let_it_be(:job) { create(:ci_build, user: user) }
let(:params) { super().merge(build: job) }
- it_behaves_like 'assigns build to package'
- it_behaves_like 'assigns status to package'
+ it_behaves_like 'assigns build to package' do
+ subject { super().payload.fetch(:package) }
+ end
+
+ it_behaves_like 'assigns status to package' do
+ subject { super().payload.fetch(:package) }
+ end
it 'creates a package file build info' do
expect { subject }.to change { Packages::PackageFileBuildInfo.count }.by(1)
@@ -154,31 +166,35 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
.and change { Packages::Package.npm.count }.by(1)
.and change { Packages::Tag.count }.by(1)
.and change { Packages::Npm::Metadatum.count }.by(1)
- expect(subject.npm_metadatum.package_json[field]).to be_blank
+ expect(package.npm_metadatum.package_json[field]).to be_blank
end
end
end
end
context 'scoped package' do
+ it_behaves_like 'valid service response'
it_behaves_like 'valid package'
end
context 'when user is no project member' do
let_it_be(:user) { create(:user) }
+ it_behaves_like 'valid service response'
it_behaves_like 'valid package'
end
context 'scoped package not following the naming convention' do
let(:package_name) { '@any-scope/package' }
+ it_behaves_like 'valid service response'
it_behaves_like 'valid package'
end
context 'unscoped package' do
let(:package_name) { 'unscoped-package' }
+ it_behaves_like 'valid service response'
it_behaves_like 'valid package'
end
@@ -186,8 +202,8 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
let(:package_name) { "@#{namespace.path}/my_package" }
let!(:existing_package) { create(:npm_package, project: project, name: package_name, version: '1.0.1') }
- it { expect(subject[:http_status]).to eq 403 }
- it { expect(subject[:message]).to be 'Package already exists.' }
+ it { is_expected.to be_error }
+ it { is_expected.to have_attributes message: 'Package already exists.', reason: ::Packages::Npm::CreatePackageService::ERROR_REASON_PACKAGE_EXISTS }
context 'marked as pending_destruction' do
before do
@@ -208,10 +224,8 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
let(:max_file_size) { 5.bytes }
shared_examples_for 'max file size validation failure' do
- it 'returns a 400 error', :aggregate_failures do
- expect(subject[:http_status]).to eq 400
- expect(subject[:message]).to be 'File is too large.'
- end
+ it { is_expected.to be_error }
+ it { is_expected.to have_attributes message: 'File is too large.', reason: ::Packages::Npm::CreatePackageService::ERROR_REASON_INVALID_PARAMETER }
end
before do
@@ -271,8 +285,8 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
context 'with empty versions' do
let(:params) { super().merge!({ versions: {} }) }
- it { expect(subject[:http_status]).to eq 400 }
- it { expect(subject[:message]).to eq 'Version is empty.' }
+ it { is_expected.to be_error }
+ it { is_expected.to have_attributes message: 'Version is empty.', reason: ::Packages::Npm::CreatePackageService::ERROR_REASON_INVALID_PARAMETER }
end
context 'with invalid versions' do
@@ -294,8 +308,8 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
context 'with empty attachment data' do
let(:params) { super().merge({ _attachments: { "#{package_name}-#{version}.tgz" => { data: '' } } }) }
- it { expect(subject[:http_status]).to eq 400 }
- it { expect(subject[:message]).to eq 'Attachment data is empty.' }
+ it { is_expected.to be_error }
+ it { is_expected.to have_attributes message: 'Attachment data is empty.', reason: ::Packages::Npm::CreatePackageService::ERROR_REASON_INVALID_PARAMETER }
end
it 'obtains a lease to create a new package' do
@@ -309,8 +323,8 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
stub_exclusive_lease_taken(lease_key, timeout: described_class::DEFAULT_LEASE_TIMEOUT)
end
- it { expect(subject[:http_status]).to eq 400 }
- it { expect(subject[:message]).to eq 'Could not obtain package lease. Please try again.' }
+ it { is_expected.to be_error }
+ it { is_expected.to have_attributes message: 'Could not obtain package lease. Please try again.', reason: ::Packages::Npm::CreatePackageService::ERROR_REASON_PACKAGE_LEASE_TAKEN }
end
context 'when feature flag :packages_protected_packages disabled' do
@@ -355,7 +369,8 @@ RSpec.describe Packages::Npm::CreatePackageService, feature_category: :package_r
let(:service) { described_class.new(project, current_user, params) }
shared_examples 'protected package' do
- it { is_expected.to include http_status: 403, message: 'Package protected.' }
+ it { is_expected.to be_error }
+ it { is_expected.to have_attributes message: 'Package protected.', reason: ::Packages::Npm::CreatePackageService::ERROR_REASON_PACKAGE_PROTECTED }
it 'does not create any npm-related package records' do
expect { subject }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 8c11270d6fd..2e5545b610a 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -77,6 +77,18 @@ RSpec.describe SystemNoteService, feature_category: :shared do
end
end
+ describe '.request_review' do
+ let(:reviewer) { double }
+
+ it 'calls IssuableService' do
+ expect_next_instance_of(::SystemNotes::IssuablesService) do |service|
+ expect(service).to receive(:request_review).with(reviewer)
+ end
+
+ described_class.request_review(noteable, project, author, reviewer)
+ end
+ end
+
describe '.change_issuable_contacts' do
let(:added_count) { 5 }
let(:removed_count) { 3 }
diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb
index 2d7cbd30db8..2b48b24b2b4 100644
--- a/spec/services/system_notes/issuables_service_spec.rb
+++ b/spec/services/system_notes/issuables_service_spec.rb
@@ -213,6 +213,21 @@ RSpec.describe ::SystemNotes::IssuablesService, feature_category: :team_planning
end
end
+ describe '#request_review' do
+ subject(:request_review) { service.request_review(reviewer) }
+
+ let_it_be(:reviewer) { create(:user) }
+ let_it_be(:noteable) { create(:merge_request, :simple, source_project: project, reviewers: [reviewer]) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'reviewer' }
+ end
+
+ it 'builds a correct phrase when a reviewer has been requested from a reviewer' do
+ expect(request_review.note).to eq "requested review from #{reviewer.to_reference}"
+ end
+ end
+
describe '#change_issuable_contacts' do
subject { service.change_issuable_contacts(1, 1) }
diff --git a/tooling/custom_roles/docs/templates/custom_abilities.md.erb b/tooling/custom_roles/docs/templates/custom_abilities.md.erb
new file mode 100644
index 00000000000..b6175d0d44f
--- /dev/null
+++ b/tooling/custom_roles/docs/templates/custom_abilities.md.erb
@@ -0,0 +1,54 @@
+<% custom_abilities_by_feature_category = MemberRole.all_customizable_permissions.group_by { |_k, definition| definition[:feature_category] } %>
+<% def humanize(feature_category) %>
+<% case feature_category %>
+<% when 'mlops' %>
+<% "MLOps" %>
+<% when 'not_owned' %>
+<% "Not categorized" %>
+<% else %>
+<% "#{feature_category.humanize}" %>
+<% end %>
+<% end %>
+<% def enabled_link(ability) %>
+<% return unless ability[:feature_flag_enabled_milestone] || ability[:feature_flag_enabled_mr] %>
+<% return "GitLab #{ability[:feature_flag_enabled_milestone]}" unless ability[:feature_flag_enabled_mr] %>
+<% "GitLab [#{ability[:feature_flag_enabled_milestone]}](#{ability[:feature_flag_enabled_mr]})" %>
+<% end %>
+<% def feature_flag(ability) %>
+<% return unless ability[:feature_flag] %>
+<% "`#{ability[:feature_flag]}`" %>
+<% end %>
+---
+stage: Govern
+group: Authorization
+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"
+---
+
+<!---
+ This documentation is auto generated by a Rake task.
+
+ Please do not edit this file directly. To update this file, run:
+ bundle exec rake gitlab:custom_roles:compile_docs
+
+ To make changes to the output of the Rake task,
+ edit `tooling/custom_roles/docs/templates/custom_abilities.md.erb`.
+--->
+
+# Available custom abilities
+
+The following abilities are available. You can add these abilities in any combination
+to a base role to create a custom role.
+
+Some abilities require having other abilities enabled first. For example, administration of vulnerabilities (`admin_vulnerability`) can only be enabled if reading vulnerabilities (`read_vulnerability`) is also enabled.
+
+These requirements are documented in the `Required ability` column in the following table.
+<% custom_abilities_by_feature_category.sort.each do |category, abilities| %>
+
+## <%= "#{humanize(category)}" %>
+
+| Name | Required permission | Description | Introduced in | Feature flag | Enabled in |
+|:-----|:------------|:------------------|:---------|:--------------|:---------|
+<% abilities.each do |name, ability| %>
+| <%= "[`#{name}`](#{ability[:introduced_by_mr]})" %> | <%= ability[:requirement] %> | <%= ability[:description] %> | GitLab <%= "[#{ability[:milestone]}](#{ability[:introduced_by_issue]})" %> | <%= feature_flag(ability) %> | <%= enabled_link(ability) %> |
+<% end %>
+<% end %>