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>2023-07-19 17:16:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-19 17:16:28 +0300
commite4384360a16dd9a19d4d2d25d0ef1f2b862ed2a6 (patch)
tree2fcdfa7dcdb9db8f5208b2562f4b4e803d671243 /app/services
parentffda4e7bcac36987f936b4ba515995a6698698f0 (diff)
Add latest changes from gitlab-org/gitlab@16-2-stable-eev16.2.0-rc42
Diffstat (limited to 'app/services')
-rw-r--r--app/services/admin/plan_limits/update_service.rb14
-rw-r--r--app/services/application_settings/update_service.rb14
-rw-r--r--app/services/auto_merge/merge_when_pipeline_succeeds_service.rb14
-rw-r--r--app/services/award_emojis/add_service.rb2
-rw-r--r--app/services/award_emojis/base_service.rb7
-rw-r--r--app/services/award_emojis/destroy_service.rb1
-rw-r--r--app/services/boards/base_items_list_service.rb19
-rw-r--r--app/services/bulk_imports/create_service.rb4
-rw-r--r--app/services/bulk_imports/relation_export_service.rb11
-rw-r--r--app/services/ci/create_pipeline_schedule_service.rb1
-rw-r--r--app/services/ci/create_pipeline_service.rb30
-rw-r--r--app/services/ci/destroy_pipeline_service.rb2
-rw-r--r--app/services/ci/pipeline_processing/atomic_processing_service.rb14
-rw-r--r--app/services/ci/pipeline_schedules/create_service.rb47
-rw-r--r--app/services/ci/pipeline_schedules/update_service.rb19
-rw-r--r--app/services/clusters/agent_tokens/create_service.rb2
-rw-r--r--app/services/clusters/agents/authorize_proxy_user_service.rb21
-rw-r--r--app/services/clusters/cleanup/project_namespace_service.rb2
-rw-r--r--app/services/clusters/cleanup/service_account_service.rb2
-rw-r--r--app/services/clusters/integrations/prometheus_health_check_service.rb101
-rw-r--r--app/services/concerns/integrations/project_test_data.rb15
-rw-r--r--app/services/concerns/projects/remove_refs.rb24
-rw-r--r--app/services/draft_notes/create_service.rb3
-rw-r--r--app/services/draft_notes/publish_service.rb7
-rw-r--r--app/services/environments/create_service.rb2
-rw-r--r--app/services/environments/update_service.rb2
-rw-r--r--app/services/git/base_hooks_service.rb19
-rw-r--r--app/services/groups/participants_service.rb12
-rw-r--r--app/services/groups/transfer_service.rb5
-rw-r--r--app/services/groups/update_service.rb1
-rw-r--r--app/services/groups/update_shared_runners_service.rb42
-rw-r--r--app/services/import/github_service.rb11
-rw-r--r--app/services/import_csv/base_service.rb2
-rw-r--r--app/services/import_csv/preprocess_milestones_service.rb35
-rw-r--r--app/services/integrations/group_mention_service.rb59
-rw-r--r--app/services/integrations/test/project_service.rb2
-rw-r--r--app/services/issuable/import_csv/base_service.rb18
-rw-r--r--app/services/issues/base_service.rb19
-rw-r--r--app/services/issues/build_service.rb25
-rw-r--r--app/services/issues/create_service.rb30
-rw-r--r--app/services/issues/export_csv_service.rb10
-rw-r--r--app/services/issues/update_service.rb15
-rw-r--r--app/services/members/creator_service.rb50
-rw-r--r--app/services/members/groups/creator_service.rb2
-rw-r--r--app/services/merge_requests/base_service.rb19
-rw-r--r--app/services/merge_requests/cleanup_refs_service.rb5
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb10
-rw-r--r--app/services/merge_requests/mergeability_check_batch_service.rb20
-rw-r--r--app/services/merge_requests/refresh_service.rb6
-rw-r--r--app/services/milestones/create_service.rb8
-rw-r--r--app/services/milestones/update_service.rb13
-rw-r--r--app/services/namespace_settings/update_service.rb17
-rw-r--r--app/services/notes/post_process_service.rb11
-rw-r--r--app/services/packages/debian/find_or_create_package_service.rb42
-rw-r--r--app/services/packages/debian/process_changes_service.rb113
-rw-r--r--app/services/packages/npm/create_metadata_cache_service.rb2
-rw-r--r--app/services/packages/npm/create_package_service.rb4
-rw-r--r--app/services/packages/npm/deprecate_package_service.rb2
-rw-r--r--app/services/packages/npm/generate_metadata_service.rb2
-rw-r--r--app/services/packages/nuget/extract_metadata_content_service.rb85
-rw-r--r--app/services/packages/nuget/extract_metadata_file_service.rb62
-rw-r--r--app/services/packages/nuget/metadata_extraction_service.rb115
-rw-r--r--app/services/packages/nuget/update_package_from_metadata_service.rb2
-rw-r--r--app/services/personal_access_tokens/last_used_service.rb7
-rw-r--r--app/services/personal_access_tokens/revoke_token_family_service.rb36
-rw-r--r--app/services/personal_access_tokens/rotate_service.rb1
-rw-r--r--app/services/projects/destroy_service.rb4
-rw-r--r--app/services/projects/download_service.rb4
-rw-r--r--app/services/projects/participants_service.rb2
-rw-r--r--app/services/projects/prometheus/alerts/notify_service.rb4
-rw-r--r--app/services/projects/update_remote_mirror_service.rb2
-rw-r--r--app/services/quick_actions/interpret_service.rb3
-rw-r--r--app/services/search/project_service.rb23
-rw-r--r--app/services/search_service.rb10
-rw-r--r--app/services/service_desk/custom_emails/base_service.rb41
-rw-r--r--app/services/service_desk/custom_emails/create_service.rb85
-rw-r--r--app/services/service_desk/custom_emails/destroy_service.rb26
-rw-r--r--app/services/service_desk_settings/update_service.rb4
-rw-r--r--app/services/service_response.rb4
-rw-r--r--app/services/spam/spam_verdict_service.rb2
-rw-r--r--app/services/system_notes/merge_requests_service.rb2
-rw-r--r--app/services/system_notes/time_tracking_service.rb4
-rw-r--r--app/services/test_hooks/project_service.rb2
-rw-r--r--app/services/todo_service.rb2
-rw-r--r--app/services/users/allow_possible_spam_service.rb18
-rw-r--r--app/services/users/ban_service.rb7
-rw-r--r--app/services/users/banned_user_base_service.rb4
-rw-r--r--app/services/users/disallow_possible_spam_service.rb13
-rw-r--r--app/services/web_hook_service.rb1
-rw-r--r--app/services/work_items/export_csv_service.rb2
90 files changed, 1027 insertions, 561 deletions
diff --git a/app/services/admin/plan_limits/update_service.rb b/app/services/admin/plan_limits/update_service.rb
index a71d1f14112..cda9a7e7f8c 100644
--- a/app/services/admin/plan_limits/update_service.rb
+++ b/app/services/admin/plan_limits/update_service.rb
@@ -7,26 +7,34 @@ module Admin
@current_user = current_user
@params = params
@plan = plan
+ @plan_limits = plan.actual_limits
end
def execute
return error(_('Access denied'), :forbidden) unless can_update?
- if plan.actual_limits.update(parsed_params)
+ add_history_to_params!
+
+ if plan_limits.update(parsed_params)
success
else
- error(plan.actual_limits.errors.full_messages, :bad_request)
+ error(plan_limits.errors.full_messages, :bad_request)
end
end
private
- attr_accessor :current_user, :params, :plan
+ attr_accessor :current_user, :params, :plan, :plan_limits
def can_update?
current_user.can_admin_all_resources?
end
+ def add_history_to_params!
+ formatted_limits_history = plan_limits.format_limits_history(current_user, parsed_params)
+ parsed_params.merge!(limits_history: formatted_limits_history) unless formatted_limits_history.empty?
+ end
+
# Overridden in EE
def parsed_params
params
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb
index 7728982779e..6d484c4fa22 100644
--- a/app/services/application_settings/update_service.rb
+++ b/app/services/application_settings/update_service.rb
@@ -26,6 +26,7 @@ module ApplicationSettings
end
update_terms(@params.delete(:terms))
+ update_default_branch_protection_defaults(@params[:default_branch_protection])
add_to_outbound_local_requests_whitelist(@params.delete(:add_to_outbound_local_requests_whitelist))
@@ -77,6 +78,19 @@ module ApplicationSettings
@application_setting.reset_memoized_terms
end
+ def update_default_branch_protection_defaults(default_branch_protection)
+ return unless default_branch_protection.present?
+
+ # We are migrating default_branch_protection from an integer
+ # column to a jsonb column. While completing the rest of the
+ # work, we want to start translating the updates sent to the
+ # existing column into the json. Eventually, we will be updating
+ # the jsonb column directly and deprecating the original update
+ # path. Until then, we want to sync up both columns.
+ protection = Gitlab::Access::BranchProtection.new(default_branch_protection.to_i)
+ @application_setting.default_branch_protection_defaults = protection.to_hash
+ end
+
def process_performance_bar_allowed_group_id
group_full_path = params.delete(:performance_bar_allowed_group_path)
enable_param_on = Gitlab::Utils.to_boolean(params.delete(:performance_bar_enabled))
diff --git a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
index 2bbb8f925a4..cb8e531f0e1 100644
--- a/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
+++ b/app/services/auto_merge/merge_when_pipeline_succeeds_service.rb
@@ -4,9 +4,7 @@ module AutoMerge
class MergeWhenPipelineSucceedsService < AutoMerge::BaseService
def execute(merge_request)
super do
- if merge_request.saved_change_to_auto_merge_enabled?
- SystemNoteService.merge_when_pipeline_succeeds(merge_request, project, current_user, merge_request.actual_head_pipeline.sha)
- end
+ add_system_note(merge_request)
end
end
@@ -36,12 +34,20 @@ module AutoMerge
def available_for?(merge_request)
super do
- merge_request.actual_head_pipeline&.active?
+ check_availability(merge_request)
end
end
private
+ def add_system_note(merge_request)
+ SystemNoteService.merge_when_pipeline_succeeds(merge_request, project, current_user, merge_request.actual_head_pipeline.sha) if merge_request.saved_change_to_auto_merge_enabled?
+ end
+
+ def check_availability(merge_request)
+ merge_request.actual_head_pipeline&.active?
+ end
+
def notify(merge_request)
notification_service.async.merge_when_pipeline_succeeds(merge_request, current_user) if merge_request.saved_change_to_auto_merge_enabled?
end
diff --git a/app/services/award_emojis/add_service.rb b/app/services/award_emojis/add_service.rb
index f45a4330c09..065ef9dc708 100644
--- a/app/services/award_emojis/add_service.rb
+++ b/app/services/award_emojis/add_service.rb
@@ -27,6 +27,8 @@ module AwardEmojis
def after_create(award)
TodoService.new.new_award_emoji(todoable, current_user) if todoable
+
+ execute_hooks(award, 'award')
end
def todoable
diff --git a/app/services/award_emojis/base_service.rb b/app/services/award_emojis/base_service.rb
index 626e26d63b5..274c528acf2 100644
--- a/app/services/award_emojis/base_service.rb
+++ b/app/services/award_emojis/base_service.rb
@@ -11,6 +11,13 @@ module AwardEmojis
super(awardable.project, current_user)
end
+ def execute_hooks(award_emoji, action)
+ return unless awardable.project&.has_active_hooks?(:emoji_hooks)
+
+ hook_data = Gitlab::DataBuilder::Emoji.build(award_emoji, current_user, action)
+ awardable.project.execute_hooks(hook_data, :emoji_hooks)
+ end
+
private
def normalize_name(name)
diff --git a/app/services/award_emojis/destroy_service.rb b/app/services/award_emojis/destroy_service.rb
index 47dc8418e07..b7146d69bf0 100644
--- a/app/services/award_emojis/destroy_service.rb
+++ b/app/services/award_emojis/destroy_service.rb
@@ -22,6 +22,7 @@ module AwardEmojis
private
def after_destroy(award)
+ execute_hooks(award, 'revoke')
end
end
end
diff --git a/app/services/boards/base_items_list_service.rb b/app/services/boards/base_items_list_service.rb
index bf68aee2c1f..a4b1be1e599 100644
--- a/app/services/boards/base_items_list_service.rb
+++ b/app/services/boards/base_items_list_service.rb
@@ -13,14 +13,17 @@ module Boards
# rubocop: disable CodeReuse/ActiveRecord
def metadata(required_fields = [:issue_count, :total_issue_weight])
- fields = metadata_fields(required_fields)
- keys = fields.keys
- # TODO: eliminate need for SQL literal fragment
- columns = Arel.sql(fields.values_at(*keys).join(', '))
- results = item_model.where(id: collection_ids)
- results = results.select(columns)
-
- Hash[keys.zip(results.pluck(columns).flatten)]
+ # Failing tests in spec/requests/api/graphql/boards/board_lists_query_spec.rb
+ ::Gitlab::Database.allow_cross_joins_across_databases(url: "https://gitlab.com/gitlab-org/gitlab/-/issues/417465") do
+ fields = metadata_fields(required_fields)
+ keys = fields.keys
+ # TODO: eliminate need for SQL literal fragment
+ columns = Arel.sql(fields.values_at(*keys).join(', '))
+ results = item_model.where(id: collection_ids)
+ results = results.select(columns)
+
+ Hash[keys.zip(results.pluck(columns).flatten)]
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/bulk_imports/create_service.rb b/app/services/bulk_imports/create_service.rb
index 636c636255f..7fc3511a253 100644
--- a/app/services/bulk_imports/create_service.rb
+++ b/app/services/bulk_imports/create_service.rb
@@ -105,7 +105,7 @@ module BulkImports
def validate_setting_enabled!
source_full_path, source_type = Array.wrap(params)[0].values_at(:source_full_path, :source_type)
entity_type = ENTITY_TYPES_MAPPING.fetch(source_type)
- if source_full_path =~ /^[0-9]+$/
+ if /^[0-9]+$/.match?(source_full_path)
query = query_type(entity_type)
response = graphql_client.execute(
graphql_client.parse(query.to_s),
@@ -154,7 +154,7 @@ module BulkImports
end
def validate_destination_slug(destination_slug)
- return if destination_slug =~ Gitlab::Regex.oci_repository_path_regex
+ return if Gitlab::Regex.oci_repository_path_regex.match?(destination_slug)
raise BulkImports::Error.destination_slug_validation_failure
end
diff --git a/app/services/bulk_imports/relation_export_service.rb b/app/services/bulk_imports/relation_export_service.rb
index 142bc48efe3..ed71c09420b 100644
--- a/app/services/bulk_imports/relation_export_service.rb
+++ b/app/services/bulk_imports/relation_export_service.rb
@@ -16,7 +16,7 @@ module BulkImports
def execute
find_or_create_export! do |export|
- remove_existing_export_file!(export)
+ export.remove_existing_upload!
export_service.execute
compress_exported_relation
upload_compressed_file(export)
@@ -45,15 +45,6 @@ module BulkImports
fail_export!(export, e)
end
- def remove_existing_export_file!(export)
- upload = export.upload
-
- return unless upload&.export_file&.file
-
- upload.remove_export_file!
- upload.save!
- end
-
def export_service
@export_service ||= if config.tree_relation?(relation) || config.self_relation?(relation)
TreeExportService.new(portable, export_path, relation, user)
diff --git a/app/services/ci/create_pipeline_schedule_service.rb b/app/services/ci/create_pipeline_schedule_service.rb
index 0d5f50c26a1..4fdd65bcdb4 100644
--- a/app/services/ci/create_pipeline_schedule_service.rb
+++ b/app/services/ci/create_pipeline_schedule_service.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
module Ci
+ # This class is deprecated and will be removed with the FF ci_refactoring_pipeline_schedule_create_service
class CreatePipelineScheduleService < BaseService
def execute
project.pipeline_schedules.create(pipeline_schedule_params)
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index a8da83e84a1..fe0e842f542 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -40,22 +40,22 @@ module Ci
# Create a new pipeline in the specified project.
#
- # @param [Symbol] source What event (Ci::Pipeline.sources) triggers the pipeline
- # creation.
- # @param [Boolean] ignore_skip_ci Whether skipping a pipeline creation when `[skip ci]` comment
- # is present in the commit body
- # @param [Boolean] save_on_errors Whether persisting an invalid pipeline when it encounters an
- # error during creation (e.g. invalid yaml)
- # @param [Ci::TriggerRequest] trigger_request The pipeline trigger triggers the pipeline creation.
- # @param [Ci::PipelineSchedule] schedule The pipeline schedule triggers the pipeline creation.
- # @param [MergeRequest] merge_request The merge request triggers the pipeline creation.
- # @param [ExternalPullRequest] external_pull_request The external pull request triggers the pipeline creation.
- # @param [Ci::Bridge] bridge The bridge job that triggers the downstream pipeline creation.
- # @param [String] content The content of .gitlab-ci.yml to override the default config
- # contents (e.g. .gitlab-ci.yml in repostiry). Mainly used for
- # generating a dangling pipeline.
+ # @param [Symbol] source What event (Ci::Pipeline.sources) triggers the pipeline
+ # creation.
+ # @param [Boolean] ignore_skip_ci Whether skipping a pipeline creation when `[skip ci]` comment
+ # is present in the commit body
+ # @param [Boolean] save_on_errors Whether persisting an invalid pipeline when it encounters an
+ # error during creation (e.g. invalid yaml)
+ # @param [Ci::TriggerRequest] trigger_request The pipeline trigger triggers the pipeline creation.
+ # @param [Ci::PipelineSchedule] schedule The pipeline schedule triggers the pipeline creation.
+ # @param [MergeRequest] merge_request The merge request triggers the pipeline creation.
+ # @param [Ci::ExternalPullRequest] external_pull_request The external pull request triggers the pipeline creation.
+ # @param [Ci::Bridge] bridge The bridge job that triggers the downstream pipeline creation.
+ # @param [String] content The content of .gitlab-ci.yml to override the default config
+ # contents (e.g. .gitlab-ci.yml in repostiry). Mainly used for
+ # generating a dangling pipeline.
#
- # @return [Ci::Pipeline] The created Ci::Pipeline object.
+ # @return [Ci::Pipeline] The created Ci::Pipeline object.
# rubocop: disable Metrics/ParameterLists
def execute(source, ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, schedule: nil, merge_request: nil, external_pull_request: nil, bridge: nil, **options, &block)
@logger = build_logger
diff --git a/app/services/ci/destroy_pipeline_service.rb b/app/services/ci/destroy_pipeline_service.rb
index bdec13f98a7..a9d2e17657e 100644
--- a/app/services/ci/destroy_pipeline_service.rb
+++ b/app/services/ci/destroy_pipeline_service.rb
@@ -7,7 +7,7 @@ module Ci
Ci::ExpirePipelineCacheService.new.execute(pipeline, delete: true)
- # ensure cancellation happens sync so we accumulate compute credits successfully
+ # ensure cancellation happens sync so we accumulate compute minutes successfully
# before deleting the pipeline.
::Ci::CancelPipelineService.new(
pipeline: pipeline,
diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb
index c0ffbb401f6..8211507fb95 100644
--- a/app/services/ci/pipeline_processing/atomic_processing_service.rb
+++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb
@@ -23,14 +23,12 @@ module Ci
success = try_obtain_lease { process! }
if success
- if ::Feature.enabled?(:ci_reset_skipped_jobs_in_atomic_processing, project)
- # If any jobs changed from stopped to alive status during pipeline processing, we must
- # re-reset their dependent jobs; see https://gitlab.com/gitlab-org/gitlab/-/issues/388539.
- new_alive_jobs.group_by(&:user).each do |user, jobs|
- log_running_reset_skipped_jobs_service(jobs)
-
- ResetSkippedJobsService.new(project, user).execute(jobs)
- end
+ # If any jobs changed from stopped to alive status during pipeline processing, we must
+ # re-reset their dependent jobs; see https://gitlab.com/gitlab-org/gitlab/-/issues/388539.
+ new_alive_jobs.group_by(&:user).each do |user, jobs|
+ log_running_reset_skipped_jobs_service(jobs)
+
+ ResetSkippedJobsService.new(project, user).execute(jobs)
end
# Re-schedule if we need further processing
diff --git a/app/services/ci/pipeline_schedules/create_service.rb b/app/services/ci/pipeline_schedules/create_service.rb
new file mode 100644
index 00000000000..c1825865bc0
--- /dev/null
+++ b/app/services/ci/pipeline_schedules/create_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Ci
+ module PipelineSchedules
+ class CreateService
+ def initialize(project, user, params)
+ @project = project
+ @user = user
+ @params = params
+
+ @schedule = project.pipeline_schedules.new
+ end
+
+ def execute
+ return forbidden unless allowed?
+
+ schedule.assign_attributes(params.merge(owner: user))
+
+ if schedule.save
+ ServiceResponse.success(payload: schedule)
+ else
+ ServiceResponse.error(payload: schedule, message: schedule.errors.full_messages)
+ end
+ end
+
+ private
+
+ attr_reader :project, :user, :params, :schedule
+
+ def allowed?
+ user.can?(:create_pipeline_schedule, schedule)
+ end
+
+ def forbidden
+ # We add the error to the base object too
+ # because model errors are used in the API responses and the `form_errors` helper.
+ schedule.errors.add(:base, forbidden_message)
+
+ ServiceResponse.error(payload: schedule, message: [forbidden_message], reason: :forbidden)
+ end
+
+ def forbidden_message
+ _('The current user is not authorized to create the pipeline schedule')
+ end
+ end
+ end
+end
diff --git a/app/services/ci/pipeline_schedules/update_service.rb b/app/services/ci/pipeline_schedules/update_service.rb
index 2412b5cbd81..28c22e0a868 100644
--- a/app/services/ci/pipeline_schedules/update_service.rb
+++ b/app/services/ci/pipeline_schedules/update_service.rb
@@ -12,7 +12,9 @@ module Ci
def execute
return forbidden unless allowed?
- if schedule.update(@params)
+ schedule.assign_attributes(params)
+
+ if schedule.save
ServiceResponse.success(payload: schedule)
else
ServiceResponse.error(message: schedule.errors.full_messages)
@@ -21,17 +23,22 @@ module Ci
private
- attr_reader :schedule, :user
+ attr_reader :schedule, :user, :params
def allowed?
user.can?(:update_pipeline_schedule, schedule)
end
def forbidden
- ServiceResponse.error(
- message: _('The current user is not authorized to update the pipeline schedule'),
- reason: :forbidden
- )
+ # We add the error to the base object too
+ # because model errors are used in the API responses and the `form_errors` helper.
+ schedule.errors.add(:base, forbidden_message)
+
+ ServiceResponse.error(message: [forbidden_message], reason: :forbidden)
+ end
+
+ def forbidden_message
+ _('The current user is not authorized to update the pipeline schedule')
end
end
end
diff --git a/app/services/clusters/agent_tokens/create_service.rb b/app/services/clusters/agent_tokens/create_service.rb
index efa9716d2c8..136afd108e7 100644
--- a/app/services/clusters/agent_tokens/create_service.rb
+++ b/app/services/clusters/agent_tokens/create_service.rb
@@ -40,8 +40,6 @@ module Clusters
end
def active_tokens_limit_reached?
- return false unless Feature.enabled?(:cluster_agents_limit_tokens_created)
-
::Clusters::AgentTokensFinder.new(agent, current_user, status: :active).execute.count >= ACTIVE_TOKENS_LIMIT
end
diff --git a/app/services/clusters/agents/authorize_proxy_user_service.rb b/app/services/clusters/agents/authorize_proxy_user_service.rb
index fbcf25153c1..abf451ed350 100644
--- a/app/services/clusters/agents/authorize_proxy_user_service.rb
+++ b/app/services/clusters/agents/authorize_proxy_user_service.rb
@@ -11,17 +11,14 @@ module Clusters
end
def execute
- return forbidden unless user_access_config.present?
+ return forbidden('`user_access` keyword is not found in agent config file.') unless user_access_config.present?
access_as = user_access_config['access_as']
- return forbidden unless access_as.present?
- return forbidden if access_as.size != 1
- if payload = handle_access(access_as)
- return success(payload: payload)
- end
+ return forbidden('`access_as` is not found under the `user_access` keyword.') unless access_as.present?
+ return forbidden('`access_as` must exist only once under the `user_access` keyword.') if access_as.size != 1
- forbidden
+ handle_access(access_as)
end
private
@@ -52,9 +49,11 @@ module Clusters
end
def access_as_agent
- return if authorizations.empty?
+ if authorizations.empty?
+ return forbidden('You must be a member of `projects` or `groups` under the `user_access` keyword.')
+ end
- response_base.merge(access_as: { agent: {} })
+ success(payload: response_base.merge(access_as: { agent: {} }))
end
def user_access_config
@@ -64,8 +63,8 @@ module Clusters
delegate :success, to: ServiceResponse, private: true
- def forbidden
- ServiceResponse.error(reason: :forbidden, message: '403 Forbidden')
+ def forbidden(message)
+ ServiceResponse.error(reason: :forbidden, message: message)
end
end
end
diff --git a/app/services/clusters/cleanup/project_namespace_service.rb b/app/services/clusters/cleanup/project_namespace_service.rb
index 80192aa14ab..f6ac06d0594 100644
--- a/app/services/clusters/cleanup/project_namespace_service.rb
+++ b/app/services/clusters/cleanup/project_namespace_service.rb
@@ -29,7 +29,7 @@ module Clusters
rescue Kubeclient::HttpError => e
# unauthorized, forbidden: GitLab's access has been revoked
# certificate verify failed: Cluster is probably gone forever
- raise unless e.message =~ /unauthorized|forbidden|certificate verify failed/i
+ raise unless /unauthorized|forbidden|certificate verify failed/i.match?(e.message)
end
kubernetes_namespace.destroy!
diff --git a/app/services/clusters/cleanup/service_account_service.rb b/app/services/clusters/cleanup/service_account_service.rb
index dce41d2a39c..0ce4bf9bb9c 100644
--- a/app/services/clusters/cleanup/service_account_service.rb
+++ b/app/services/clusters/cleanup/service_account_service.rb
@@ -27,7 +27,7 @@ module Clusters
rescue Kubeclient::HttpError => e
# unauthorized, forbidden: GitLab's access has been revoked
# certificate verify failed: Cluster is probably gone forever
- raise unless e.message =~ /unauthorized|forbidden|certificate verify failed/i
+ raise unless /unauthorized|forbidden|certificate verify failed/i.match?(e.message)
end
end
end
diff --git a/app/services/clusters/integrations/prometheus_health_check_service.rb b/app/services/clusters/integrations/prometheus_health_check_service.rb
deleted file mode 100644
index cd06e59449c..00000000000
--- a/app/services/clusters/integrations/prometheus_health_check_service.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- module Integrations
- class PrometheusHealthCheckService
- include Gitlab::Utils::StrongMemoize
- include Gitlab::Routing
-
- def initialize(cluster)
- @cluster = cluster
- @logger = Gitlab::AppJsonLogger.build
- end
-
- def execute
- raise 'Invalid cluster type. Only project types are allowed.' unless @cluster.project_type?
-
- return unless prometheus_integration.enabled
-
- project = @cluster.clusterable
-
- @logger.info(
- message: 'Prometheus health check',
- cluster_id: @cluster.id,
- newly_unhealthy: became_unhealthy?,
- currently_healthy: currently_healthy?,
- was_healthy: was_healthy?
- )
-
- send_notification(project) if became_unhealthy?
-
- prometheus_integration.update_columns(health_status: current_health_status) if health_changed?
- end
-
- private
-
- def prometheus_integration
- strong_memoize(:prometheus_integration) do
- @cluster.integration_prometheus
- end
- end
-
- def current_health_status
- if currently_healthy?
- :healthy
- else
- :unhealthy
- end
- end
-
- def currently_healthy?
- strong_memoize(:currently_healthy) do
- prometheus_integration.prometheus_client.healthy?
- end
- end
-
- def became_unhealthy?
- strong_memoize(:became_unhealthy) do
- (was_healthy? || was_unknown?) && !currently_healthy?
- end
- end
-
- def was_healthy?
- strong_memoize(:was_healthy) do
- prometheus_integration.healthy?
- end
- end
-
- def was_unknown?
- strong_memoize(:was_unknown) do
- prometheus_integration.unknown?
- end
- end
-
- def health_changed?
- was_healthy? != currently_healthy?
- end
-
- def send_notification(project)
- notification_payload = build_notification_payload(project)
- integration = project.alert_management_http_integrations.active.first
-
- Projects::Alerting::NotifyService.new(project, notification_payload).execute(integration&.token, integration)
-
- @logger.info(message: 'Successfully notified of Prometheus newly unhealthy', cluster_id: @cluster.id, project_id: project.id)
- end
-
- def build_notification_payload(project)
- cluster_path = namespace_project_cluster_path(
- project_id: project.path,
- namespace_id: project.namespace.path,
- id: @cluster.id
- )
-
- {
- title: "Prometheus is Unhealthy. Cluster Name: #{@cluster.name}",
- description: "Prometheus is unhealthy for the cluster: [#{@cluster.name}](#{cluster_path}) attached to project #{project.name}."
- }
- end
- end
- end
-end
diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb
index b3427697052..fcef22a8cab 100644
--- a/app/services/concerns/integrations/project_test_data.rb
+++ b/app/services/concerns/integrations/project_test_data.rb
@@ -77,5 +77,20 @@ module Integrations
release.to_hook_data('create')
end
+
+ def emoji_events_data
+ no_data_error(s_('TestHooks|Ensure the project has notes.')) unless project.notes.any?
+
+ award_emoji = AwardEmoji.new(
+ id: 1,
+ name: 'thumbsup',
+ user: current_user,
+ awardable: project.notes.last,
+ created_at: Time.zone.now,
+ updated_at: Time.zone.now
+ )
+
+ Gitlab::DataBuilder::Emoji.build(award_emoji, current_user, 'award')
+ end
end
end
diff --git a/app/services/concerns/projects/remove_refs.rb b/app/services/concerns/projects/remove_refs.rb
new file mode 100644
index 00000000000..d133aa0ced6
--- /dev/null
+++ b/app/services/concerns/projects/remove_refs.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Projects
+ module RemoveRefs
+ extend ActiveSupport::Concern
+ include Gitlab::ExclusiveLeaseHelpers
+
+ LOCK_RETRY = 3
+ LOCK_TTL = 5.minutes
+ LOCK_SLEEP = 0.5.seconds
+
+ def serialized_remove_refs(project_id, &blk)
+ in_lock("projects/#{project_id}/serialized_remove_refs", **lock_params, &blk)
+ end
+
+ def lock_params
+ {
+ ttl: LOCK_TTL,
+ retries: LOCK_RETRY,
+ sleep_sec: LOCK_SLEEP
+ }
+ end
+ end
+end
diff --git a/app/services/draft_notes/create_service.rb b/app/services/draft_notes/create_service.rb
index 5ff971b66c1..e5a070e9db7 100644
--- a/app/services/draft_notes/create_service.rb
+++ b/app/services/draft_notes/create_service.rb
@@ -25,7 +25,8 @@ module DraftNotes
draft_note = DraftNote.new(params)
draft_note.merge_request = merge_request
draft_note.author = current_user
- draft_note.save
+
+ return draft_note unless draft_note.save
if in_reply_to_discussion_id.blank? && draft_note.diff_file&.unfolded?
merge_request.diffs.clear_cache
diff --git a/app/services/draft_notes/publish_service.rb b/app/services/draft_notes/publish_service.rb
index 9e1e381c568..a7a2ad63c1c 100644
--- a/app/services/draft_notes/publish_service.rb
+++ b/app/services/draft_notes/publish_service.rb
@@ -49,6 +49,7 @@ module DraftNotes
notification_service.async.new_review(review)
MergeRequests::ResolvedDiscussionNotificationService.new(project: project, current_user: current_user).execute(merge_request)
GraphqlTriggers.merge_request_merge_status_updated(merge_request)
+ after_publish(review)
end
def create_note_from_draft(draft, skip_capture_diff_note_position: false, skip_keep_around_commits: false, skip_merge_status_trigger: false)
@@ -108,5 +109,11 @@ module DraftNotes
project.repository.keep_around(*shas)
end
end
+
+ def after_publish(review)
+ # Overridden in EE
+ end
end
end
+
+DraftNotes::PublishService.prepend_mod
diff --git a/app/services/environments/create_service.rb b/app/services/environments/create_service.rb
index 760c8a6e306..fd78a886e29 100644
--- a/app/services/environments/create_service.rb
+++ b/app/services/environments/create_service.rb
@@ -2,7 +2,7 @@
module Environments
class CreateService < BaseService
- ALLOWED_ATTRIBUTES = %i[name external_url tier cluster_agent].freeze
+ ALLOWED_ATTRIBUTES = %i[name external_url tier cluster_agent kubernetes_namespace].freeze
def execute
unless can?(current_user, :create_environment, project)
diff --git a/app/services/environments/update_service.rb b/app/services/environments/update_service.rb
index 5eb4880ec4b..52f6198bada 100644
--- a/app/services/environments/update_service.rb
+++ b/app/services/environments/update_service.rb
@@ -2,7 +2,7 @@
module Environments
class UpdateService < BaseService
- ALLOWED_ATTRIBUTES = %i[external_url tier cluster_agent].freeze
+ ALLOWED_ATTRIBUTES = %i[external_url tier cluster_agent kubernetes_namespace].freeze
def execute(environment)
unless can?(current_user, :update_environment, environment)
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index acf54dec51b..f9280be7ee2 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -67,7 +67,10 @@ module Git
# Creating push_data invokes one CommitDelta RPC per commit. Only
# build this data if we actually need it.
project.execute_hooks(push_data, hook_name) if project.has_active_hooks?(hook_name)
- project.execute_integrations(push_data, hook_name) if project.has_active_integrations?(hook_name)
+
+ return unless project.has_active_integrations?(hook_name)
+
+ project.execute_integrations(push_data, hook_name, skip_ci: integration_push_options&.fetch(:skip_ci).present?)
end
def enqueue_invalidate_cache
@@ -101,7 +104,19 @@ module Git
def ci_variables_from_push_options
strong_memoize(:ci_variables_from_push_options) do
- params[:push_options]&.deep_symbolize_keys&.dig(:ci, :variable)
+ push_options&.dig(:ci, :variable)
+ end
+ end
+
+ def integration_push_options
+ strong_memoize(:integration_push_options) do
+ push_options&.dig(:integrations)
+ end
+ end
+
+ def push_options
+ strong_memoize(:push_options) do
+ params[:push_options]&.deep_symbolize_keys
end
end
diff --git a/app/services/groups/participants_service.rb b/app/services/groups/participants_service.rb
index 1de2b3c5a2e..e939d27d464 100644
--- a/app/services/groups/participants_service.rb
+++ b/app/services/groups/participants_service.rb
@@ -2,6 +2,7 @@
module Groups
class ParticipantsService < Groups::BaseService
+ include Gitlab::Utils::StrongMemoize
include Users::ParticipableService
def execute(noteable)
@@ -17,15 +18,20 @@ module Groups
render_participants_as_hash(participants.uniq)
end
+ private
+
def all_members
- count = group_members.count
- [{ username: "all", name: "All Group Members", count: count }]
+ return [] if group.nil? || Feature.enabled?(:disable_all_mention)
+
+ [{ username: "all", name: "All Group Members", count: group.users_count }]
end
def group_members
return [] unless group
- @group_members ||= sorted(group.direct_and_indirect_users)
+ sorted(
+ group.direct_and_indirect_users(share_with_groups: group.member?(current_user))
+ )
end
end
end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index 16454360ee2..81d4dfddaab 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -197,6 +197,11 @@ module Groups
return if @new_parent_group
return unless @group.owners.empty?
+ add_owner_on_transferred_group
+ end
+
+ # Overridden in EE
+ def add_owner_on_transferred_group
@group.add_owner(current_user)
end
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 925a2acbb58..df6ede87ef9 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -117,6 +117,7 @@ module Groups
def handle_settings_update
settings_params = params.slice(*allowed_settings_params)
+ settings_params.merge!({ default_branch_protection: params[:default_branch_protection] }.compact)
allowed_settings_params.each { |param| params.delete(param) }
::NamespaceSettings::UpdateService.new(current_user, group, settings_params).execute
diff --git a/app/services/groups/update_shared_runners_service.rb b/app/services/groups/update_shared_runners_service.rb
index c09dce0761f..08b43037c4c 100644
--- a/app/services/groups/update_shared_runners_service.rb
+++ b/app/services/groups/update_shared_runners_service.rb
@@ -25,7 +25,14 @@ module Groups
end
def update_shared_runners
- group.update_shared_runners_setting!(params[:shared_runners_setting])
+ case params[:shared_runners_setting]
+ when Namespace::SR_DISABLED_AND_UNOVERRIDABLE
+ set_shared_runners_enabled!(false)
+ when Namespace::SR_DISABLED_WITH_OVERRIDE, Namespace::SR_DISABLED_AND_OVERRIDABLE
+ disable_shared_runners_and_allow_override!
+ when Namespace::SR_ENABLED
+ set_shared_runners_enabled!(true)
+ end
end
def update_pending_builds?
@@ -41,5 +48,38 @@ module Groups
::Ci::UpdatePendingBuildService.new(group, pending_builds_params).execute
end
end
+
+ def set_shared_runners_enabled!(enabled)
+ group.update!(
+ shared_runners_enabled: enabled,
+ allow_descendants_override_disabled_shared_runners: false)
+
+ group_ids = group.descendants
+ unless group_ids.empty?
+ Group.by_id(group_ids).update_all(
+ shared_runners_enabled: enabled,
+ allow_descendants_override_disabled_shared_runners: false)
+ end
+
+ group.all_projects.update_all(shared_runners_enabled: enabled)
+ end
+
+ def disable_shared_runners_and_allow_override!
+ # enabled -> disabled_and_overridable
+ if group.shared_runners_enabled?
+ group.update!(
+ shared_runners_enabled: false,
+ allow_descendants_override_disabled_shared_runners: true)
+
+ group_ids = group.descendants
+ Group.by_id(group_ids).update_all(shared_runners_enabled: false) unless group_ids.empty?
+
+ group.all_projects.update_all(shared_runners_enabled: false)
+
+ # disabled_and_unoverridable -> disabled_and_overridable
+ else
+ group.update!(allow_descendants_override_disabled_shared_runners: true)
+ end
+ end
end
end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
index 7e7f7ea9810..df255a7ae24 100644
--- a/app/services/import/github_service.rb
+++ b/app/services/import/github_service.rb
@@ -16,7 +16,7 @@ module Import
track_access_level('github')
if project.persisted?
- store_import_settings(project)
+ store_import_settings(project, access_params)
success(project)
elsif project.errors[:import_source_disabled].present?
error(project.errors[:import_source_disabled], :forbidden)
@@ -134,8 +134,13 @@ module Import
error(translated_message, http_status)
end
- def store_import_settings(project)
- Gitlab::GithubImport::Settings.new(project).write(params[:optional_stages])
+ def store_import_settings(project, access_params)
+ Gitlab::GithubImport::Settings
+ .new(project)
+ .write(
+ optional_stages: params[:optional_stages],
+ additional_access_tokens: access_params[:additional_access_tokens]
+ )
end
end
end
diff --git a/app/services/import_csv/base_service.rb b/app/services/import_csv/base_service.rb
index cfaf3e831eb..9c1bad9e7da 100644
--- a/app/services/import_csv/base_service.rb
+++ b/app/services/import_csv/base_service.rb
@@ -8,7 +8,7 @@ module ImportCsv
@user = user
@project = project
@csv_io = csv_io
- @results = { success: 0, error_lines: [], parse_error: false }
+ @results = { success: 0, error_lines: [], parse_error: false, preprocess_errors: {} }
end
PreprocessError = Class.new(StandardError)
diff --git a/app/services/import_csv/preprocess_milestones_service.rb b/app/services/import_csv/preprocess_milestones_service.rb
new file mode 100644
index 00000000000..97fb381c58e
--- /dev/null
+++ b/app/services/import_csv/preprocess_milestones_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module ImportCsv
+ class PreprocessMilestonesService < BaseService
+ def initialize(user, project, provided_titles)
+ @user = user
+ @project = project
+ @provided_titles = provided_titles
+
+ @results = { success: 0, errors: nil }
+ @milestone_errors = { missing: { header: {}, titles: [] } }
+ end
+
+ attr_reader :user, :project, :provided_titles, :results, :milestone_errors
+
+ def execute
+ available_milestones = find_milestones_by_titles
+ return ServiceResponse.success if provided_titles.sort == available_milestones.sort
+
+ milestone_errors[:missing][:header] = 'Milestone'
+ milestone_errors[:missing][:titles] = provided_titles.difference(available_milestones) || []
+ ServiceResponse.error(message: "", payload: milestone_errors)
+ end
+
+ def find_milestones_by_titles
+ # Find if these milestones exist in the project or its group and group ancestors
+ finder_params = {
+ project_ids: [project.id],
+ title: provided_titles
+ }
+ finder_params[:group_ids] = project.group.self_and_ancestors.select(:id) if project.group
+ MilestonesFinder.new(finder_params).execute.map(&:title).uniq
+ end
+ end
+end
diff --git a/app/services/integrations/group_mention_service.rb b/app/services/integrations/group_mention_service.rb
new file mode 100644
index 00000000000..2389bf33432
--- /dev/null
+++ b/app/services/integrations/group_mention_service.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+# GroupMentionService class
+#
+# Used for sending group mention notifications
+#
+# Ex.
+# Integrations::GroupMentionService.new(mentionable, hook_data: data, is_confidential: true).execute
+#
+module Integrations
+ class GroupMentionService
+ def initialize(mentionable, hook_data:, is_confidential:)
+ @mentionable = mentionable
+ @hook_data = hook_data
+ @is_confidential = is_confidential
+ end
+
+ def execute
+ return ServiceResponse.success if mentionable.nil? || hook_data.nil?
+
+ @hook_data = hook_data.clone
+ # Fake a "group_mention" object kind so integrations can handle this as a separate class of event
+ hook_data[:object_attributes][:object_kind] = hook_data[:object_kind]
+ hook_data[:object_kind] = 'group_mention'
+
+ if confidential?
+ hook_data[:event_type] = 'group_confidential_mention'
+ hook_scope = :group_confidential_mention_hooks
+ else
+ hook_data[:event_type] = 'group_mention'
+ hook_scope = :group_mention_hooks
+ end
+
+ groups = mentionable.referenced_groups(mentionable.author)
+ groups.each do |group|
+ group_hook_data = hook_data.merge(
+ mentioned: {
+ object_kind: 'group',
+ name: group.full_path,
+ url: group.web_url
+ }
+ )
+ group.execute_integrations(group_hook_data, hook_scope)
+ end
+
+ ServiceResponse.success
+ end
+
+ private
+
+ attr_reader :mentionable, :hook_data, :is_confidential
+
+ def confidential?
+ return is_confidential if is_confidential.present?
+
+ mentionable.project.visibility_level != Gitlab::VisibilityLevel::PUBLIC
+ end
+ end
+end
diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb
index 31c8f02c7b6..48240f297fe 100644
--- a/app/services/integrations/test/project_service.rb
+++ b/app/services/integrations/test/project_service.rb
@@ -35,6 +35,8 @@ module Integrations
deployment_events_data
when 'release'
releases_events_data
+ when 'award_emoji'
+ emoji_events_data
end
end
end
diff --git a/app/services/issuable/import_csv/base_service.rb b/app/services/issuable/import_csv/base_service.rb
index 9ef9fb76e3c..95338374ca6 100644
--- a/app/services/issuable/import_csv/base_service.rb
+++ b/app/services/issuable/import_csv/base_service.rb
@@ -23,6 +23,24 @@ module Issuable
raise CSV::MalformedCSVError.new('Invalid CSV format - missing required headers.', 1)
end
+
+ def preprocess!
+ preprocess_milestones!
+
+ raise PreprocessError if results[:preprocess_errors].any?
+ end
+
+ def preprocess_milestones!
+ # Pre-Process Milestone if header is present
+ return unless csv_data.lines.first.downcase.include?('milestone')
+
+ provided_titles = with_csv_lines.filter_map { |row| row[:milestone]&.strip&.downcase }.uniq
+ result = ::ImportCsv::PreprocessMilestonesService.new(user, project, provided_titles).execute
+ return if result.success?
+
+ # collate errors here and throw errors
+ results[:preprocess_errors][:milestone_errors] = result.payload
+ end
end
end
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index f982d66eb08..b9b7cd08b68 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -111,6 +111,10 @@ module Issues
issue.namespace.execute_integrations(issue_data, hooks_scope)
execute_incident_hooks(issue, issue_data) if issue.work_item_type&.incident?
+
+ return unless Feature.enabled?(:group_mentions, issue.project)
+
+ execute_group_mention_hooks(issue, issue_data) if action == 'open'
end
# We can remove this code after proposal in
@@ -121,6 +125,21 @@ module Issues
issue.namespace.execute_integrations(issue_data, :incident_hooks)
end
+ def execute_group_mention_hooks(issue, issue_data)
+ return unless issue.instance_of?(Issue)
+
+ args = {
+ mentionable_type: 'Issue',
+ mentionable_id: issue.id,
+ hook_data: issue_data,
+ is_confidential: issue.confidential?
+ }
+
+ issue.run_after_commit_or_now do
+ Integrations::GroupMentionWorker.perform_async(args)
+ end
+ end
+
def update_project_counter_caches?(issue)
super || issue.confidential_changed?
end
diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb
index a65fc0c7c87..63cad593936 100644
--- a/app/services/issues/build_service.rb
+++ b/app/services/issues/build_service.rb
@@ -83,18 +83,17 @@ module Issues
params.delete(:work_item_type)
end
- base_type = work_item_type&.base_type
-
- if create_issue_type_allowed?(container, base_type)
- issue.work_item_type = work_item_type
- # Up to this point issue_type might be set to the default, so we need to sync if a work item type is provided
- issue.issue_type = base_type
- else
- # If no work item type was provided or not allowed, we need to set it to issue_type,
- # and that includes the column default
- issue_type = issue_params[:issue_type] || ::Issue::DEFAULT_ISSUE_TYPE
- issue.work_item_type = WorkItems::Type.default_by_type(issue_type)
- end
+ # We need to support the legacy input params[:issue_type] even if we don't have the issue_type column anymore.
+ # In the future only params[:work_item_type] should be provided
+ base_type = work_item_type&.base_type || params[:issue_type]
+
+ issue.work_item_type = if create_issue_type_allowed?(container, base_type)
+ work_item_type || WorkItems::Type.default_by_type(base_type)
+ else
+ # If no work item type was provided or not allowed, we need to set it to
+ # the default issue_type
+ WorkItems::Type.default_by_type(::Issue::DEFAULT_ISSUE_TYPE)
+ end
end
def model_klass
@@ -109,8 +108,6 @@ module Issues
:confidential
]
- public_issue_params << :issue_type if create_issue_type_allowed?(container, params[:issue_type])
-
params.slice(*public_issue_params)
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 17b6866773e..e1ddfe47439 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -51,6 +51,7 @@ module Issues
# current_user (defined in BaseService) is not available within run_after_commit block
user = current_user
+ assign_description_from_template(issue)
issue.run_after_commit do
NewIssueWorker.perform_async(issue.id, user.id, issue.class.to_s)
Issues::PlacementWorker.perform_async(nil, issue.project_id)
@@ -127,6 +128,35 @@ module Issues
set_crm_contacts(issue, contacts)
end
+
+ def assign_description_from_template(issue)
+ return if issue.description.present?
+
+ # Find the exact name for the default template (if the project has one).
+ # Since there are multiple possibilities regarding the capitalization(s) that the
+ # default template file name can have, getting the exact template name here will
+ # allow us to extract the contents later, and bail early if the project does not have
+ # a default template
+ templates = TemplateFinder.all_template_names(project, :issues)
+ template = templates.values.flatten.find { |tmpl| tmpl[:name].casecmp?('default') }
+
+ return unless template
+
+ begin
+ default_template = TemplateFinder.build(
+ :issues,
+ issue.project,
+ {
+ name: template[:name],
+ source_template_project_id: issue.project.id
+ }
+ ).execute
+ rescue ::Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
+ nil
+ end
+
+ issue.description = default_template.content if default_template.present?
+ end
end
end
diff --git a/app/services/issues/export_csv_service.rb b/app/services/issues/export_csv_service.rb
index 9e524d90505..99c0e9f1a37 100644
--- a/app/services/issues/export_csv_service.rb
+++ b/app/services/issues/export_csv_service.rb
@@ -34,14 +34,14 @@ module Issues
'Assignee Username' => -> (issue) { issue.assignees.map(&:username).join(', ') },
'Confidential' => -> (issue) { issue.confidential? ? 'Yes' : 'No' },
'Locked' => -> (issue) { issue.discussion_locked? ? 'Yes' : 'No' },
- 'Due Date' => -> (issue) { issue.due_date&.to_s(:csv) },
- 'Created At (UTC)' => -> (issue) { issue.created_at&.to_s(:csv) },
- 'Updated At (UTC)' => -> (issue) { issue.updated_at&.to_s(:csv) },
- 'Closed At (UTC)' => -> (issue) { issue.closed_at&.to_s(:csv) },
+ 'Due Date' => -> (issue) { issue.due_date&.to_fs(:csv) },
+ 'Created At (UTC)' => -> (issue) { issue.created_at&.to_fs(:csv) },
+ 'Updated At (UTC)' => -> (issue) { issue.updated_at&.to_fs(:csv) },
+ 'Closed At (UTC)' => -> (issue) { issue.closed_at&.to_fs(:csv) },
'Milestone' => -> (issue) { issue.milestone&.title },
'Weight' => -> (issue) { issue.weight },
'Labels' => -> (issue) { issue_labels(issue) },
- 'Time Estimate' => ->(issue) { issue.time_estimate.to_s(:csv) },
+ 'Time Estimate' => ->(issue) { issue.time_estimate.to_fs(:csv) },
'Time Spent' => -> (issue) { issue_time_spent(issue) }
}
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 7ad56d5a755..839d0e664a4 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -32,10 +32,9 @@ module Issues
end
def change_work_item_type(issue)
- return unless issue.changed_attributes['issue_type']
+ return unless params[:issue_type].present?
- issue_type = params[:issue_type] || ::Issue::DEFAULT_ISSUE_TYPE
- type_id = find_work_item_type_id(issue_type)
+ type_id = find_work_item_type_id(params[:issue_type])
issue.work_item_type_id = type_id
end
@@ -180,16 +179,22 @@ module Issues
end
def handle_issue_type_change(issue)
- return unless issue.previous_changes.include?('issue_type')
+ return unless issue.previous_changes.include?('work_item_type_id')
do_handle_issue_type_change(issue)
end
def do_handle_issue_type_change(issue)
- SystemNoteService.change_issue_type(issue, current_user, issue.issue_type_before_last_save)
+ old_work_item_type = ::WorkItems::Type.find(issue.work_item_type_id_before_last_save).base_type
+ SystemNoteService.change_issue_type(issue, current_user, old_work_item_type)
::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute if issue.supports_escalation?
end
+
+ override :allowed_update_params
+ def allowed_update_params(params)
+ super.except(:issue_type)
+ end
end
end
diff --git a/app/services/members/creator_service.rb b/app/services/members/creator_service.rb
index 699c5b94c53..a6fff3003ac 100644
--- a/app/services/members/creator_service.rb
+++ b/app/services/members/creator_service.rb
@@ -34,16 +34,7 @@ module Members
# @param sources [Group, Project, Array<Group>, Array<Project>, Group::ActiveRecord_Relation,
# Project::ActiveRecord_Relation] - Can't be an array of source ids because we don't know the type of source.
# @return Array<Member>
- def add_members(
- sources,
- invitees,
- access_level,
- current_user: nil,
- expires_at: nil,
- tasks_to_be_done: [],
- tasks_project_id: nil,
- ldap: nil
- ) # rubocop:disable Metrics/ParameterLists
+ def add_members(sources, invitees, access_level, **args)
return [] unless invitees.present?
sources = Array.wrap(sources) if sources.is_a?(ApplicationRecord) # For single source
@@ -51,7 +42,9 @@ module Members
Member.transaction do
sources.flat_map do |source|
# If this user is attempting to manage Owner members and doesn't have permission, do not allow
- next [] if managing_owners?(current_user, access_level) && cannot_manage_owners?(source, current_user)
+ if managing_owners?(args[:current_user], access_level) && cannot_manage_owners?(source, args[:current_user])
+ next []
+ end
emails, users, existing_members = parse_users_list(source, invitees)
@@ -59,12 +52,8 @@ module Members
source: source,
access_level: access_level,
existing_members: existing_members,
- current_user: current_user,
- expires_at: expires_at,
- tasks_to_be_done: tasks_to_be_done,
- tasks_project_id: tasks_project_id,
- ldap: ldap
- }
+ tasks_to_be_done: args[:tasks_to_be_done] || []
+ }.merge(parsed_args(args))
members = emails.map do |email|
new(invitee: email, builder: InviteMemberBuilder, **common_arguments).execute
@@ -79,26 +68,21 @@ module Members
end
end
- def add_member(
- source,
- invitee,
- access_level,
- current_user: nil,
- expires_at: nil,
- ldap: nil
- ) # rubocop:disable Metrics/ParameterLists
- add_members(
- source,
- [invitee],
- access_level,
- current_user: current_user,
- expires_at: expires_at,
- ldap: ldap
- ).first
+ def add_member(source, invitee, access_level, **args)
+ add_members(source, [invitee], access_level, **args).first
end
private
+ def parsed_args(args)
+ {
+ current_user: args[:current_user],
+ expires_at: args[:expires_at],
+ tasks_project_id: args[:tasks_project_id],
+ ldap: args[:ldap]
+ }
+ end
+
def managing_owners?(current_user, access_level)
current_user && Gitlab::Access.sym_options_with_owner[access_level] == Gitlab::Access::OWNER
end
diff --git a/app/services/members/groups/creator_service.rb b/app/services/members/groups/creator_service.rb
index dd3d44e4d96..864be01a96d 100644
--- a/app/services/members/groups/creator_service.rb
+++ b/app/services/members/groups/creator_service.rb
@@ -21,3 +21,5 @@ module Members
end
end
end
+
+Members::Groups::CreatorService.prepend_mod_with('Members::Groups::CreatorService')
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index ec8a17162ca..aaa91548d19 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -36,6 +36,10 @@ module MergeRequests
execute_external_hooks(merge_request, merge_data)
+ if action == 'open' && Feature.enabled?(:group_mentions, merge_request.project)
+ execute_group_mention_hooks(merge_request, merge_data)
+ end
+
enqueue_jira_connect_messages_for(merge_request)
end
@@ -43,6 +47,21 @@ module MergeRequests
# Implemented in EE
end
+ def execute_group_mention_hooks(merge_request, merge_data)
+ return unless merge_request.instance_of?(MergeRequest)
+
+ args = {
+ mentionable_type: 'MergeRequest',
+ mentionable_id: merge_request.id,
+ hook_data: merge_data,
+ is_confidential: false
+ }
+
+ merge_request.run_after_commit_or_now do
+ Integrations::GroupMentionWorker.perform_async(args)
+ end
+ end
+
def handle_changes(merge_request, options)
old_associations = options.fetch(:old_associations, {})
old_assignees = old_associations.fetch(:assignees, [])
diff --git a/app/services/merge_requests/cleanup_refs_service.rb b/app/services/merge_requests/cleanup_refs_service.rb
index 2094ea00160..5081655601b 100644
--- a/app/services/merge_requests/cleanup_refs_service.rb
+++ b/app/services/merge_requests/cleanup_refs_service.rb
@@ -16,7 +16,6 @@ module MergeRequests
@merge_request = merge_request
@repository = merge_request.project.repository
@ref_path = merge_request.ref_path
- @merge_ref_path = merge_request.merge_ref_path
@ref_head_sha = @repository.commit(merge_request.ref_path)&.id
@merge_ref_sha = merge_request.merge_ref_head&.id
end
@@ -42,7 +41,7 @@ module MergeRequests
private
- attr_reader :repository, :ref_path, :merge_ref_path, :ref_head_sha, :merge_ref_sha
+ attr_reader :repository, :ref_path, :ref_head_sha, :merge_ref_sha
def scheduled?
merge_request.cleanup_schedule.present? && merge_request.cleanup_schedule.scheduled_at <= Time.current
@@ -79,7 +78,7 @@ module MergeRequests
end
def delete_refs
- repository.delete_refs(ref_path, merge_ref_path)
+ merge_request.schedule_cleanup_refs
end
def update_schedule
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
index 1bd26f06e41..acd3bc36e1d 100644
--- a/app/services/merge_requests/merge_to_ref_service.rb
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -56,13 +56,6 @@ module MergeRequests
params[:first_parent_ref] || merge_request.target_branch_ref
end
- ##
- # The parameter `allow_conflicts` is a flag whether merge conflicts should be merged into diff
- # Default is false
- def allow_conflicts
- params[:allow_conflicts] || false
- end
-
def commit(cache_merge_to_ref_calls = false)
if cache_merge_to_ref_calls
Rails.cache.fetch(cache_key, expires_in: 1.day) do
@@ -79,8 +72,7 @@ module MergeRequests
branch: merge_request.target_branch,
target_ref: target_ref,
message: commit_message,
- first_parent_ref: first_parent_ref,
- allow_conflicts: allow_conflicts)
+ first_parent_ref: first_parent_ref)
rescue Gitlab::Git::PreReceiveError, Gitlab::Git::CommandError => error
raise MergeError, error.message
end
diff --git a/app/services/merge_requests/mergeability_check_batch_service.rb b/app/services/merge_requests/mergeability_check_batch_service.rb
new file mode 100644
index 00000000000..7697b596a83
--- /dev/null
+++ b/app/services/merge_requests/mergeability_check_batch_service.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class MergeabilityCheckBatchService
+ def initialize(merge_requests, user)
+ @merge_requests = merge_requests
+ @user = user
+ end
+
+ def execute
+ return unless merge_requests.present?
+
+ MergeRequests::MergeabilityCheckBatchWorker.perform_async(merge_requests.map(&:id), user&.id)
+ end
+
+ private
+
+ attr_reader :merge_requests, :user
+ end
+end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
index d6740cdf1ac..447f4f9428c 100644
--- a/app/services/merge_requests/refresh_service.rb
+++ b/app/services/merge_requests/refresh_service.rb
@@ -169,7 +169,13 @@ module MergeRequests
@outdate_service ||= Suggestions::OutdateService.new
end
+ def abort_auto_merges?(merge_request)
+ merge_request.merge_params.with_indifferent_access[:sha] != @push.newrev
+ end
+
def abort_auto_merges(merge_request)
+ return unless abort_auto_merges?(merge_request)
+
abort_auto_merge(merge_request, 'source branch was updated')
end
diff --git a/app/services/milestones/create_service.rb b/app/services/milestones/create_service.rb
index 6c3edd2e147..e8a14adc10d 100644
--- a/app/services/milestones/create_service.rb
+++ b/app/services/milestones/create_service.rb
@@ -5,11 +5,19 @@ module Milestones
def execute
milestone = parent.milestones.new(params)
+ before_create(milestone)
+
if milestone.save && milestone.project_milestone?
event_service.open_milestone(milestone, current_user)
end
milestone
end
+
+ private
+
+ def before_create(milestone)
+ milestone.check_for_spam(user: current_user, action: :create)
+ end
end
end
diff --git a/app/services/milestones/update_service.rb b/app/services/milestones/update_service.rb
index b9a12a35d31..90cb8ea9f5c 100644
--- a/app/services/milestones/update_service.rb
+++ b/app/services/milestones/update_service.rb
@@ -13,11 +13,22 @@ module Milestones
end
if params.present?
- milestone.update(params.except(:state_event))
+ milestone.assign_attributes(params.except(:state_event))
end
+ if milestone.changed?
+ before_update(milestone)
+ end
+
+ milestone.save
milestone
end
+
+ private
+
+ def before_update(milestone)
+ milestone.check_for_spam(user: current_user, action: :update)
+ end
end
end
diff --git a/app/services/namespace_settings/update_service.rb b/app/services/namespace_settings/update_service.rb
index 25525265e1c..c391320db5e 100644
--- a/app/services/namespace_settings/update_service.rb
+++ b/app/services/namespace_settings/update_service.rb
@@ -23,6 +23,12 @@ module NamespaceSettings
param_key: :new_user_signups_cap,
user_policy: :change_new_user_signups_cap
)
+ validate_settings_param_for_root_group(
+ param_key: :default_branch_protection,
+ user_policy: :update_default_branch_protection
+ )
+
+ handle_default_branch_protection unless settings_params[:default_branch_protection].blank?
if group.namespace_settings
group.namespace_settings.attributes = settings_params
@@ -33,6 +39,17 @@ module NamespaceSettings
private
+ def handle_default_branch_protection
+ # We are migrating default_branch_protection from an integer
+ # column to a jsonb column. While completing the rest of the
+ # work, we want to start translating the updates sent to the
+ # existing column into the json. Eventually, we will be updating
+ # the jsonb column directly and deprecating the original update
+ # path. Until then, we want to sync up both columns.
+ protection = Gitlab::Access::BranchProtection.new(settings_params.delete(:default_branch_protection).to_i)
+ settings_params[:default_branch_protection_defaults] = protection.to_hash
+ end
+
def validate_resource_access_token_creation_allowed_param
return if settings_params[:resource_access_token_creation_allowed].nil?
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index c9375fe14a1..9465b5218b0 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -36,10 +36,19 @@ module Notes
return unless note.project
note_data = hook_data
- hooks_scope = note.confidential?(include_noteable: true) ? :confidential_note_hooks : :note_hooks
+ is_confidential = note.confidential?(include_noteable: true)
+ hooks_scope = is_confidential ? :confidential_note_hooks : :note_hooks
note.project.execute_hooks(note_data, hooks_scope)
note.project.execute_integrations(note_data, hooks_scope)
+
+ return unless Feature.enabled?(:group_mentions, note.project)
+
+ execute_group_mention_hooks(note, note_data, is_confidential)
+ end
+
+ def execute_group_mention_hooks(note, note_data, is_confidential)
+ Integrations::GroupMentionService.new(note, hook_data: note_data, is_confidential: is_confidential).execute
end
end
end
diff --git a/app/services/packages/debian/find_or_create_package_service.rb b/app/services/packages/debian/find_or_create_package_service.rb
deleted file mode 100644
index a9481504d2b..00000000000
--- a/app/services/packages/debian/find_or_create_package_service.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# frozen_string_literal: true
-
-module Packages
- module Debian
- class FindOrCreatePackageService < ::Packages::CreatePackageService
- include Gitlab::Utils::StrongMemoize
-
- def execute
- packages = project.packages
- .existing_debian_packages_with(name: params[:name], version: params[:version])
-
- package = packages.with_debian_codename_or_suite(params[:distribution_name]).first
-
- unless package
- package_in_other_distribution = packages.first
-
- if package_in_other_distribution
- raise ArgumentError, "Debian package #{params[:name]} #{params[:version]} exists " \
- "in distribution #{package_in_other_distribution.debian_distribution.codename}"
- end
- end
-
- package ||= create_package!(
- :debian,
- debian_publication_attributes: { distribution_id: distribution.id }
- )
-
- ServiceResponse.success(payload: { package: package })
- end
-
- private
-
- def distribution
- Packages::Debian::DistributionsFinder.new(
- project,
- codename_or_suite: params[:distribution_name]
- ).execute.last!
- end
- strong_memoize_attr :distribution
- end
- end
-end
diff --git a/app/services/packages/debian/process_changes_service.rb b/app/services/packages/debian/process_changes_service.rb
deleted file mode 100644
index eb88e7c9b59..00000000000
--- a/app/services/packages/debian/process_changes_service.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-# frozen_string_literal: true
-
-module Packages
- module Debian
- class ProcessChangesService
- include ExclusiveLeaseGuard
- include Gitlab::Utils::StrongMemoize
-
- # used by ExclusiveLeaseGuard
- DEFAULT_LEASE_TIMEOUT = 1.hour.to_i.freeze
-
- def initialize(package_file, creator)
- @package_file = package_file
- @creator = creator
- end
-
- def execute
- # return if changes file has already been processed
- return if package_file.debian_file_metadatum&.changes?
-
- validate!
-
- try_obtain_lease do
- package_file.transaction do
- update_files_metadata
- update_changes_metadata
- end
-
- ::Packages::Debian::GenerateDistributionWorker.perform_async(:project, package.debian_distribution.id)
- end
- end
-
- private
-
- attr_reader :package_file, :creator
-
- def validate!
- raise ArgumentError, 'invalid package file' unless package_file.debian_file_metadatum
- raise ArgumentError, 'invalid package file' unless package_file.debian_file_metadatum.unknown?
- raise ArgumentError, 'invalid package file' unless metadata[:file_type] == :changes
- raise ArgumentError, 'missing Source field' unless metadata.dig(:fields, 'Source').present?
- raise ArgumentError, 'missing Version field' unless metadata.dig(:fields, 'Version').present?
- raise ArgumentError, 'missing Distribution field' unless metadata.dig(:fields, 'Distribution').present?
- end
-
- def update_files_metadata
- files.each do |filename, entry|
- file_metadata = ::Packages::Debian::ExtractMetadataService.new(entry.package_file).execute
-
- ::Packages::UpdatePackageFileService.new(entry.package_file, package_id: package.id)
- .execute
-
- # Force reload from database, as package has changed
- entry.package_file.reload_package
-
- entry.package_file.debian_file_metadatum.update!(
- file_type: file_metadata[:file_type],
- component: files[filename].component,
- architecture: file_metadata[:architecture],
- fields: file_metadata[:fields]
- )
- end
- end
-
- def update_changes_metadata
- ::Packages::UpdatePackageFileService.new(package_file, package_id: package.id)
- .execute
-
- # Force reload from database, as package has changed
- package_file.reload_package
-
- package_file.debian_file_metadatum.update!(
- file_type: metadata[:file_type],
- fields: metadata[:fields]
- )
- end
-
- def metadata
- ::Packages::Debian::ExtractChangesMetadataService.new(package_file).execute
- end
- strong_memoize_attr :metadata
-
- def files
- metadata[:files]
- end
-
- def project
- package_file.package.project
- end
-
- def package
- params = {
- name: metadata[:fields]['Source'],
- version: metadata[:fields]['Version'],
- distribution_name: metadata[:fields]['Distribution']
- }
- response = Packages::Debian::FindOrCreatePackageService.new(project, creator, params).execute
- response.payload[:package]
- end
- strong_memoize_attr :package
-
- # used by ExclusiveLeaseGuard
- def lease_key
- "packages:debian:process_changes_service:package_file:#{package_file.id}"
- end
-
- # used by ExclusiveLeaseGuard
- def lease_timeout
- DEFAULT_LEASE_TIMEOUT
- end
- end
- end
-end
diff --git a/app/services/packages/npm/create_metadata_cache_service.rb b/app/services/packages/npm/create_metadata_cache_service.rb
index 75cff5c5453..f470b9f1202 100644
--- a/app/services/packages/npm/create_metadata_cache_service.rb
+++ b/app/services/packages/npm/create_metadata_cache_service.rb
@@ -30,7 +30,7 @@ module Packages
attr_reader :package_name, :project
def metadata_content
- metadata.payload.to_json
+ ::API::Entities::NpmPackage.represent(metadata.payload).to_json
end
strong_memoize_attr :metadata_content
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
index 2c578760cc5..f6f2dbb8415 100644
--- a/app/services/packages/npm/create_package_service.rb
+++ b/app/services/packages/npm/create_package_service.rb
@@ -18,7 +18,7 @@ module Packages
ApplicationRecord.transaction { create_npm_package! }
end
- return error('Could not obtain package lease.', 400) unless package
+ return error('Could not obtain package lease. Please try again.', 400) unless package
package
end
@@ -40,7 +40,7 @@ module Packages
def create_npm_metadatum!(package)
package.create_npm_metadatum!(package_json: package_json)
rescue ActiveRecord::RecordInvalid => e
- if package.npm_metadatum && package.npm_metadatum.errors.added?(:package_json, 'structure is too large')
+ if package.npm_metadatum && package.npm_metadatum.errors.where(:package_json, :too_large).any? # rubocop: disable CodeReuse/ActiveRecord
Gitlab::ErrorTracking.track_exception(e, field_sizes: field_sizes_for_error_tracking)
end
diff --git a/app/services/packages/npm/deprecate_package_service.rb b/app/services/packages/npm/deprecate_package_service.rb
index 2633e9f877c..bca81ebe1de 100644
--- a/app/services/packages/npm/deprecate_package_service.rb
+++ b/app/services/packages/npm/deprecate_package_service.rb
@@ -31,7 +31,7 @@ module Packages
def packages
::Packages::Npm::PackageFinder
- .new(params['package_name'], project: project, last_of_each_version: false)
+ .new(params['package_name'], project: project)
.execute
end
diff --git a/app/services/packages/npm/generate_metadata_service.rb b/app/services/packages/npm/generate_metadata_service.rb
index 800c3ce19b4..e1795079513 100644
--- a/app/services/packages/npm/generate_metadata_service.rb
+++ b/app/services/packages/npm/generate_metadata_service.rb
@@ -98,7 +98,7 @@ module Packages
end
def package_tags
- Packages::Tag.for_package_ids(packages.last_of_each_version_ids)
+ Packages::Tag.for_package_ids(packages)
.preload_package
end
diff --git a/app/services/packages/nuget/extract_metadata_content_service.rb b/app/services/packages/nuget/extract_metadata_content_service.rb
new file mode 100644
index 00000000000..28653654018
--- /dev/null
+++ b/app/services/packages/nuget/extract_metadata_content_service.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class ExtractMetadataContentService
+ ROOT_XPATH = '//xmlns:package/xmlns:metadata/xmlns'
+
+ XPATHS = {
+ package_name: "#{ROOT_XPATH}:id",
+ package_version: "#{ROOT_XPATH}:version",
+ authors: "#{ROOT_XPATH}:authors",
+ description: "#{ROOT_XPATH}:description",
+ license_url: "#{ROOT_XPATH}:licenseUrl",
+ project_url: "#{ROOT_XPATH}:projectUrl",
+ icon_url: "#{ROOT_XPATH}:iconUrl"
+ }.freeze
+
+ XPATH_DEPENDENCIES = "#{ROOT_XPATH}:dependencies/xmlns:dependency".freeze
+ XPATH_DEPENDENCY_GROUPS = "#{ROOT_XPATH}:dependencies/xmlns:group".freeze
+ XPATH_TAGS = "#{ROOT_XPATH}:tags".freeze
+ XPATH_PACKAGE_TYPES = "#{ROOT_XPATH}:packageTypes/xmlns:packageType".freeze
+
+ def initialize(nuspec_file_content)
+ @nuspec_file_content = nuspec_file_content
+ end
+
+ def execute
+ ServiceResponse.success(payload: extract_metadata(nuspec_file_content))
+ end
+
+ private
+
+ attr_reader :nuspec_file_content
+
+ def extract_metadata(file)
+ doc = Nokogiri::XML(file)
+
+ XPATHS.transform_values { |query| doc.xpath(query).text.presence }
+ .compact
+ .tap do |metadata|
+ metadata[:package_dependencies] = extract_dependencies(doc)
+ metadata[:package_tags] = extract_tags(doc)
+ metadata[:package_types] = extract_package_types(doc)
+ end
+ end
+
+ def extract_dependencies(doc)
+ dependencies = []
+
+ doc.xpath(XPATH_DEPENDENCIES).each do |node|
+ dependencies << extract_dependency(node)
+ end
+
+ doc.xpath(XPATH_DEPENDENCY_GROUPS).each do |group_node|
+ target_framework = group_node.attr('targetFramework')
+
+ group_node.xpath('xmlns:dependency').each do |node|
+ dependencies << extract_dependency(node).merge(target_framework: target_framework)
+ end
+ end
+
+ dependencies
+ end
+
+ def extract_dependency(node)
+ {
+ name: node.attr('id'),
+ version: node.attr('version')
+ }.compact
+ end
+
+ def extract_tags(doc)
+ tags = doc.xpath(XPATH_TAGS).text
+
+ return [] if tags.blank?
+
+ tags.split(::Packages::Tag::NUGET_TAGS_SEPARATOR)
+ end
+
+ def extract_package_types(doc)
+ doc.xpath(XPATH_PACKAGE_TYPES).map { |node| node.attr('name') }.uniq
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/extract_metadata_file_service.rb b/app/services/packages/nuget/extract_metadata_file_service.rb
new file mode 100644
index 00000000000..61e4892fee7
--- /dev/null
+++ b/app/services/packages/nuget/extract_metadata_file_service.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Packages
+ module Nuget
+ class ExtractMetadataFileService
+ include Gitlab::Utils::StrongMemoize
+
+ ExtractionError = Class.new(StandardError)
+
+ MAX_FILE_SIZE = 4.megabytes.freeze
+
+ def initialize(package_file_id)
+ @package_file_id = package_file_id
+ end
+
+ def execute
+ raise ExtractionError, 'invalid package file' unless valid_package_file?
+
+ ServiceResponse.success(payload: nuspec_file_content)
+ end
+
+ private
+
+ attr_reader :package_file_id
+
+ def package_file
+ ::Packages::PackageFile.find_by_id(package_file_id)
+ end
+ strong_memoize_attr :package_file
+
+ def valid_package_file?
+ package_file &&
+ package_file.package&.nuget? &&
+ package_file.file.size > 0 # rubocop:disable Style/ZeroLengthPredicate
+ end
+
+ def nuspec_file_content
+ with_zip_file do |zip_file|
+ entry = zip_file.glob('*.nuspec').first
+
+ raise ExtractionError, 'nuspec file not found' unless entry
+ raise ExtractionError, 'nuspec file too big' if MAX_FILE_SIZE < entry.size
+
+ Tempfile.open("nuget_extraction_package_file_#{package_file_id}") do |file|
+ entry.extract(file.path) { true } # allow #extract to overwrite the file
+ file.unlink
+ file.read
+ end
+ rescue Zip::EntrySizeError => e
+ raise ExtractionError, "nuspec file has the wrong entry size: #{e.message}"
+ end
+ end
+
+ def with_zip_file
+ package_file.file.use_open_file do |open_file|
+ zip_file = Zip::File.new(open_file, false, true) # rubocop:disable Performance/Rubyzip
+ yield(zip_file)
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/packages/nuget/metadata_extraction_service.rb b/app/services/packages/nuget/metadata_extraction_service.rb
index 5c60a2912ae..e1ee29ef2c6 100644
--- a/app/services/packages/nuget/metadata_extraction_service.rb
+++ b/app/services/packages/nuget/metadata_extraction_service.rb
@@ -3,123 +3,30 @@
module Packages
module Nuget
class MetadataExtractionService
- include Gitlab::Utils::StrongMemoize
-
- ExtractionError = Class.new(StandardError)
-
- ROOT_XPATH = '//xmlns:package/xmlns:metadata/xmlns'
-
- XPATHS = {
- package_name: "#{ROOT_XPATH}:id",
- package_version: "#{ROOT_XPATH}:version",
- authors: "#{ROOT_XPATH}:authors",
- description: "#{ROOT_XPATH}:description",
- license_url: "#{ROOT_XPATH}:licenseUrl",
- project_url: "#{ROOT_XPATH}:projectUrl",
- icon_url: "#{ROOT_XPATH}:iconUrl"
- }.freeze
-
- XPATH_DEPENDENCIES = "#{ROOT_XPATH}:dependencies/xmlns:dependency".freeze
- XPATH_DEPENDENCY_GROUPS = "#{ROOT_XPATH}:dependencies/xmlns:group".freeze
- XPATH_TAGS = "#{ROOT_XPATH}:tags".freeze
- XPATH_PACKAGE_TYPES = "#{ROOT_XPATH}:packageTypes/xmlns:packageType".freeze
-
- MAX_FILE_SIZE = 4.megabytes.freeze
-
def initialize(package_file_id)
@package_file_id = package_file_id
end
def execute
- raise ExtractionError, 'invalid package file' unless valid_package_file?
-
- extract_metadata(nuspec_file_content)
+ ServiceResponse.success(payload: metadata)
end
private
- def package_file
- ::Packages::PackageFile.find_by_id(@package_file_id)
- end
- strong_memoize_attr :package_file
-
- def valid_package_file?
- package_file &&
- package_file.package&.nuget? &&
- package_file.file.size > 0 # rubocop:disable Style/ZeroLengthPredicate
- end
-
- def extract_metadata(file)
- doc = Nokogiri::XML(file)
-
- XPATHS.transform_values { |query| doc.xpath(query).text.presence }
- .compact
- .tap do |metadata|
- metadata[:package_dependencies] = extract_dependencies(doc)
- metadata[:package_tags] = extract_tags(doc)
- metadata[:package_types] = extract_package_types(doc)
- end
- end
-
- def extract_dependencies(doc)
- dependencies = []
-
- doc.xpath(XPATH_DEPENDENCIES).each do |node|
- dependencies << extract_dependency(node)
- end
-
- doc.xpath(XPATH_DEPENDENCY_GROUPS).each do |group_node|
- target_framework = group_node.attr("targetFramework")
-
- group_node.xpath("xmlns:dependency").each do |node|
- dependencies << extract_dependency(node).merge(target_framework: target_framework)
- end
- end
-
- dependencies
- end
-
- def extract_dependency(node)
- {
- name: node.attr('id'),
- version: node.attr('version')
- }.compact
- end
-
- def extract_package_types(doc)
- doc.xpath(XPATH_PACKAGE_TYPES).map { |node| node.attr('name') }.uniq
- end
-
- def extract_tags(doc)
- tags = doc.xpath(XPATH_TAGS).text
-
- return [] if tags.blank?
-
- tags.split(::Packages::Tag::NUGET_TAGS_SEPARATOR)
- end
+ attr_reader :package_file_id
def nuspec_file_content
- with_zip_file do |zip_file|
- entry = zip_file.glob('*.nuspec').first
-
- raise ExtractionError, 'nuspec file not found' unless entry
- raise ExtractionError, 'nuspec file too big' if MAX_FILE_SIZE < entry.size
-
- Tempfile.open("nuget_extraction_package_file_#{@package_file_id}") do |file|
- entry.extract(file.path) { true } # allow #extract to overwrite the file
- file.unlink
- file.read
- end
- rescue Zip::EntrySizeError => e
- raise ExtractionError, "nuspec file has the wrong entry size: #{e.message}"
- end
+ ExtractMetadataFileService
+ .new(package_file_id)
+ .execute
+ .payload
end
- def with_zip_file(&block)
- package_file.file.use_open_file do |open_file|
- zip_file = Zip::File.new(open_file, false, true)
- yield(zip_file)
- end
+ def metadata
+ ExtractMetadataContentService
+ .new(nuspec_file_content)
+ .execute
+ .payload
end
end
end
diff --git a/app/services/packages/nuget/update_package_from_metadata_service.rb b/app/services/packages/nuget/update_package_from_metadata_service.rb
index 8e2679db31b..d82509fff5e 100644
--- a/app/services/packages/nuget/update_package_from_metadata_service.rb
+++ b/app/services/packages/nuget/update_package_from_metadata_service.rb
@@ -145,7 +145,7 @@ module Packages
end
def metadata
- ::Packages::Nuget::MetadataExtractionService.new(@package_file.id).execute
+ ::Packages::Nuget::MetadataExtractionService.new(@package_file.id).execute.payload
end
strong_memoize_attr :metadata
diff --git a/app/services/personal_access_tokens/last_used_service.rb b/app/services/personal_access_tokens/last_used_service.rb
index 6fc3110a70b..3b075364458 100644
--- a/app/services/personal_access_tokens/last_used_service.rb
+++ b/app/services/personal_access_tokens/last_used_service.rb
@@ -24,12 +24,7 @@ module PersonalAccessTokens
return true if last_used.nil?
- if Feature.enabled?(:update_personal_access_token_usage_information_every_10_minutes) &&
- last_used <= 10.minutes.ago
- return true
- end
-
- last_used <= 1.day.ago
+ last_used <= 10.minutes.ago
end
end
end
diff --git a/app/services/personal_access_tokens/revoke_token_family_service.rb b/app/services/personal_access_tokens/revoke_token_family_service.rb
new file mode 100644
index 00000000000..547ba6c3bdc
--- /dev/null
+++ b/app/services/personal_access_tokens/revoke_token_family_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module PersonalAccessTokens
+ class RevokeTokenFamilyService
+ def initialize(token)
+ @token = token
+ end
+
+ def execute
+ # Despite using #update_all, there should only be a single active token.
+ # A token family is a chain of rotated tokens. Once rotated, the
+ # previous token is revoked.
+ pat_family.active.update_all(revoked: true)
+
+ ServiceResponse.success
+ end
+
+ private
+
+ attr_reader :token
+
+ def pat_family
+ # rubocop: disable CodeReuse/ActiveRecord
+ cte = Gitlab::SQL::RecursiveCTE.new(:personal_access_tokens_cte)
+ personal_access_token_table = Arel::Table.new(:personal_access_tokens)
+
+ cte << PersonalAccessToken
+ .where(personal_access_token_table[:previous_personal_access_token_id].eq(token.id))
+ cte << PersonalAccessToken
+ .from([personal_access_token_table, cte.table])
+ .where(personal_access_token_table[:previous_personal_access_token_id].eq(cte.table[:id]))
+ PersonalAccessToken.with.recursive(cte.to_arel).from(cte.alias_to(personal_access_token_table))
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+end
diff --git a/app/services/personal_access_tokens/rotate_service.rb b/app/services/personal_access_tokens/rotate_service.rb
index 64b0c5c98a9..b765aacef68 100644
--- a/app/services/personal_access_tokens/rotate_service.rb
+++ b/app/services/personal_access_tokens/rotate_service.rb
@@ -41,6 +41,7 @@ module PersonalAccessTokens
def create_token_params(token)
{ name: token.name,
+ previous_personal_access_token_id: token.id,
impersonation: token.impersonation,
scopes: token.scopes,
expires_at: Date.today + EXPIRATION_PERIOD }
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 2279ab301dc..a5c12384b59 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -58,6 +58,10 @@ module Projects
unless remove_repository(project.wiki.repository)
raise_error(s_('DeleteProject|Failed to remove wiki repository. Please try again or contact administrator.'))
end
+
+ unless remove_repository(project.design_repository)
+ raise_error(s_('DeleteProject|Failed to remove design repository. Please try again or contact administrator.'))
+ end
end
def trash_relation_repositories!
diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb
index 72cb3997045..22104409199 100644
--- a/app/services/projects/download_service.rb
+++ b/app/services/projects/download_service.rb
@@ -2,7 +2,7 @@
module Projects
class DownloadService < BaseService
- WHITELIST = [
+ ALLOWLIST = [
/^[^.]+\.fogbugz.com$/
].freeze
@@ -33,7 +33,7 @@ module Projects
def valid_domain?(url)
host = URI.parse(url).host
- WHITELIST.any? { |entry| entry === host }
+ ALLOWLIST.any? { |entry| entry === host }
end
end
end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index 8c807e0016b..44cd6e9926f 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -30,6 +30,8 @@ module Projects
end
def all_members
+ return [] if Feature.enabled?(:disable_all_mention)
+
[{ username: "all", name: "All Project and Group Members", count: project_members.count }]
end
diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb
index f1c093c89b7..22a882c4648 100644
--- a/app/services/projects/prometheus/alerts/notify_service.rb
+++ b/app/services/projects/prometheus/alerts/notify_service.rb
@@ -89,7 +89,9 @@ module Projects
# AlertManagement::HttpIntegrations is complete,
# we should use use the HttpIntegration as SSOT.
# Remove with https://gitlab.com/gitlab-org/gitlab/-/issues/409734
- return false if project.alert_management_http_integrations.legacy.prometheus.any?
+ return false if project.alert_management_http_integrations
+ .for_endpoint_identifier('legacy-prometheus')
+ .any?
prometheus = project.find_or_initialize_integration('prometheus')
return false unless prometheus.manual_configuration?
diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb
index b048ec128d8..d5c8e958bbd 100644
--- a/app/services/projects/update_remote_mirror_service.rb
+++ b/app/services/projects/update_remote_mirror_service.rb
@@ -93,7 +93,7 @@ module Projects
# TODO: Support LFS sync over SSH
# https://gitlab.com/gitlab-org/gitlab/-/issues/249587
- return unless remote_mirror.url =~ %r{\Ahttps?://}i
+ return unless %r{\Ahttps?://}i.match?(remote_mirror.url)
return unless remote_mirror.password_auth?
Lfs::PushService.new(
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index b5f6bff756b..d1798ce6fc0 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -188,7 +188,8 @@ module QuickActions
next unless definition
definition.execute(self, arg)
- usage_ping_tracking(definition.name, arg)
+ # summarize_diff will be removed https://gitlab.com/gitlab-org/gitlab/-/issues/407258#note_1385269274
+ usage_ping_tracking(definition.name, arg) unless definition.name == :summarize_diff
end
end
diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb
index 71314f85984..73d46a9ba70 100644
--- a/app/services/search/project_service.rb
+++ b/app/services/search/project_service.rb
@@ -4,7 +4,6 @@ module Search
class ProjectService
include Search::Filter
include Gitlab::Utils::StrongMemoize
- include ProjectsHelper
ALLOWED_SCOPES = %w(blobs issues merge_requests wiki_blobs commits notes milestones users).freeze
@@ -18,13 +17,13 @@ module Search
def execute
Gitlab::ProjectSearchResults.new(current_user,
- params[:search],
- project: project,
- repository_ref: params[:repository_ref],
- order_by: params[:order_by],
- sort: params[:sort],
- filters: filters
- )
+ params[:search],
+ project: project,
+ repository_ref: params[:repository_ref],
+ order_by: params[:order_by],
+ sort: params[:sort],
+ filters: filters
+ )
end
def allowed_scopes
@@ -33,10 +32,12 @@ module Search
def scope
strong_memoize(:scope) do
- next params[:scope] if allowed_scopes.include?(params[:scope]) && project_search_tabs?(params[:scope].to_sym)
+ search_navigation = Search::Navigation.new(user: current_user, project: project)
+ scope = params[:scope]
+ next scope if allowed_scopes.include?(scope) && search_navigation.tab_enabled_for_project?(scope.to_sym)
- allowed_scopes.find do |scope|
- project_search_tabs?(scope.to_sym)
+ allowed_scopes.find do |s|
+ search_navigation.tab_enabled_for_project?(s.to_sym)
end
end
end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 5705e4c7cef..433e9b0da6d 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -102,16 +102,6 @@ class SearchService
end
end
- def show_elasticsearch_tabs?
- # overridden in EE
- false
- end
-
- def show_epics?
- # overridden in EE
- false
- end
-
def global_search_enabled_for_scope?
return false if show_snippets? && Feature.disabled?(:global_search_snippet_titles_tab, current_user, type: :ops)
diff --git a/app/services/service_desk/custom_emails/base_service.rb b/app/services/service_desk/custom_emails/base_service.rb
new file mode 100644
index 00000000000..62152f31012
--- /dev/null
+++ b/app/services/service_desk/custom_emails/base_service.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module ServiceDesk
+ module CustomEmails
+ class BaseService < ::BaseProjectService
+ private
+
+ def legitimate_user?
+ can?(current_user, :admin_project, project)
+ end
+
+ def setting?
+ project.service_desk_setting.present?
+ end
+
+ def credential?
+ project.service_desk_custom_email_verification.present?
+ end
+
+ def verification?
+ project.service_desk_custom_email_credential.present?
+ end
+
+ def feature_flag_enabled?
+ Feature.enabled?(:service_desk_custom_email, project)
+ end
+
+ def error_user_not_authorized
+ error_response(s_('ServiceDesk|User cannot manage project.'))
+ end
+
+ def error_feature_flag_disabled
+ error_response('Feature flag service_desk_custom_email is not enabled')
+ end
+
+ def error_response(message)
+ ServiceResponse.error(message: message)
+ end
+ end
+ end
+end
diff --git a/app/services/service_desk/custom_emails/create_service.rb b/app/services/service_desk/custom_emails/create_service.rb
new file mode 100644
index 00000000000..c3ca98a0259
--- /dev/null
+++ b/app/services/service_desk/custom_emails/create_service.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module ServiceDesk
+ module CustomEmails
+ class CreateService < BaseService
+ def execute
+ return error_feature_flag_disabled unless feature_flag_enabled?
+ return error_user_not_authorized unless legitimate_user?
+ return error_params_missing unless has_required_params?
+ return error_custom_email_exists if credential? || verification?
+
+ return error_cannot_create_custom_email unless create_credential
+
+ if update_settings.error?
+ # We don't warp everything in a single transaction here and roll it back
+ # because ServiceDeskSettings::UpdateService uses safe_find_or_create_by!
+ rollback_credential
+ return error_cannot_create_custom_email
+ end
+
+ project.reset
+
+ # The create service may return an error response if the verification fails early.
+ # Here We want to indicate whether adding a custom email address was successful, so
+ # we don't use its response here.
+ create_verification
+
+ ServiceResponse.success
+ end
+
+ private
+
+ def update_settings
+ ServiceDeskSettings::UpdateService.new(project, current_user, create_setting_params).execute
+ end
+
+ def rollback_credential
+ ::ServiceDesk::CustomEmailCredential.find_by_project_id(project.id)&.destroy
+ end
+
+ def create_credential
+ credential = ::ServiceDesk::CustomEmailCredential.new(create_credential_params.merge(project: project))
+ credential.save
+ end
+
+ def create_verification
+ ::ServiceDesk::CustomEmailVerifications::CreateService.new(project: project, current_user: current_user).execute
+ end
+
+ def create_setting_params
+ ensure_params.permit(:custom_email)
+ end
+
+ def create_credential_params
+ ensure_params.permit(:smtp_address, :smtp_port, :smtp_username, :smtp_password)
+ end
+
+ def ensure_params
+ return params if params.is_a?(ActionController::Parameters)
+
+ ActionController::Parameters.new(params)
+ end
+
+ def has_required_params?
+ required_keys.all? { |key| params.key?(key) && params[key].present? }
+ end
+
+ def required_keys
+ %i[custom_email smtp_address smtp_port smtp_username smtp_password]
+ end
+
+ def error_custom_email_exists
+ error_response(s_('ServiceDesk|Custom email already exists'))
+ end
+
+ def error_params_missing
+ error_response(s_('ServiceDesk|Parameters missing'))
+ end
+
+ def error_cannot_create_custom_email
+ error_response(s_('ServiceDesk|Cannot create custom email'))
+ end
+ end
+ end
+end
diff --git a/app/services/service_desk/custom_emails/destroy_service.rb b/app/services/service_desk/custom_emails/destroy_service.rb
new file mode 100644
index 00000000000..1aa5994edd8
--- /dev/null
+++ b/app/services/service_desk/custom_emails/destroy_service.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module ServiceDesk
+ module CustomEmails
+ class DestroyService < BaseService
+ def execute
+ return error_feature_flag_disabled unless feature_flag_enabled?
+ return error_user_not_authorized unless legitimate_user?
+ return error_does_not_exist unless verification? || credential? || setting?
+
+ project.service_desk_custom_email_verification&.destroy
+ project.service_desk_custom_email_credential&.destroy
+ project.reset
+ project.service_desk_setting&.update!(custom_email: nil, custom_email_enabled: false)
+
+ ServiceResponse.success
+ end
+
+ private
+
+ def error_does_not_exist
+ error_response(s_('ServiceDesk|Custom email does not exist'))
+ end
+ end
+ end
+end
diff --git a/app/services/service_desk_settings/update_service.rb b/app/services/service_desk_settings/update_service.rb
index 5fe74f1f2ff..61cb6fce11f 100644
--- a/app/services/service_desk_settings/update_service.rb
+++ b/app/services/service_desk_settings/update_service.rb
@@ -8,9 +8,9 @@ module ServiceDeskSettings
params[:project_key] = nil if params[:project_key].blank?
if settings.update(params)
- success
+ ServiceResponse.success
else
- error(settings.errors.full_messages.to_sentence)
+ ServiceResponse.error(message: settings.errors.full_messages.to_sentence)
end
end
end
diff --git a/app/services/service_response.rb b/app/services/service_response.rb
index da4773ab9c7..86efc01bd30 100644
--- a/app/services/service_response.rb
+++ b/app/services/service_response.rb
@@ -56,6 +56,10 @@ class ServiceResponse
reason: reason)
end
+ def deconstruct_keys(keys)
+ to_h.slice(*keys)
+ end
+
def success?
status == :success
end
diff --git a/app/services/spam/spam_verdict_service.rb b/app/services/spam/spam_verdict_service.rb
index 2ecd431fd91..e0a6d58b904 100644
--- a/app/services/spam/spam_verdict_service.rb
+++ b/app/services/spam/spam_verdict_service.rb
@@ -85,7 +85,7 @@ module Spam
# than the override verdict's priority value), then we don't need to override it.
return false if SUPPORTED_VERDICTS[verdict][:priority] > SUPPORTED_VERDICTS[OVERRIDE_VIA_ALLOW_POSSIBLE_SPAM][:priority]
- target.allow_possible_spam?
+ target.allow_possible_spam?(user) || user.allow_possible_spam?
end
def spamcheck_client
diff --git a/app/services/system_notes/merge_requests_service.rb b/app/services/system_notes/merge_requests_service.rb
index 7758c1e8597..d71388a1552 100644
--- a/app/services/system_notes/merge_requests_service.rb
+++ b/app/services/system_notes/merge_requests_service.rb
@@ -181,3 +181,5 @@ module SystemNotes
end
end
end
+
+SystemNotes::MergeRequestsService.prepend_mod_with('SystemNotes::MergeRequestsService')
diff --git a/app/services/system_notes/time_tracking_service.rb b/app/services/system_notes/time_tracking_service.rb
index b7a2afbaf15..f9084ed67d3 100644
--- a/app/services/system_notes/time_tracking_service.rb
+++ b/app/services/system_notes/time_tracking_service.rb
@@ -147,9 +147,9 @@ module SystemNotes
readable_date = date_key.humanize.downcase
if changed_date.nil?
- "removed #{readable_date} #{changed_dates[date_key].first.to_s(:long)}"
+ "removed #{readable_date} #{changed_dates[date_key].first.to_fs(:long)}"
else
- "changed #{readable_date} to #{changed_date.to_s(:long)}"
+ "changed #{readable_date} to #{changed_date.to_fs(:long)}"
end
end
diff --git a/app/services/test_hooks/project_service.rb b/app/services/test_hooks/project_service.rb
index dcd92ac2b8c..42af65ebd57 100644
--- a/app/services/test_hooks/project_service.rb
+++ b/app/services/test_hooks/project_service.rb
@@ -32,6 +32,8 @@ module TestHooks
wiki_page_events_data
when 'releases_events'
releases_events_data
+ when 'emoji_events'
+ emoji_events_data
end
end
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index c55e1680bfe..1f6cf2c83c9 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -377,7 +377,7 @@ class TodoService
attributes = {
project_id: target&.project&.id,
target_id: target.id,
- target_type: target.class.name,
+ target_type: target.class.try(:polymorphic_name) || target.class.name,
commit_id: nil
}
diff --git a/app/services/users/allow_possible_spam_service.rb b/app/services/users/allow_possible_spam_service.rb
new file mode 100644
index 00000000000..d9273fe0fc1
--- /dev/null
+++ b/app/services/users/allow_possible_spam_service.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Users
+ class AllowPossibleSpamService < BaseService
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(user)
+ custom_attribute = {
+ user_id: user.id,
+ key: UserCustomAttribute::ALLOW_POSSIBLE_SPAM,
+ value: "#{current_user.username}/#{current_user.id}+#{Time.current}"
+ }
+ UserCustomAttribute.upsert_custom_attributes([custom_attribute])
+ end
+ end
+end
diff --git a/app/services/users/ban_service.rb b/app/services/users/ban_service.rb
index 5ed31cdb778..20c34b15f15 100644
--- a/app/services/users/ban_service.rb
+++ b/app/services/users/ban_service.rb
@@ -2,6 +2,8 @@
module Users
class BanService < BannedUserBaseService
+ extend ::Gitlab::Utils::Override
+
private
def update_user(user)
@@ -15,6 +17,11 @@ module Users
def action
:ban
end
+
+ override :track_event
+ def track_event(user)
+ experiment(:phone_verification_for_low_risk_users, user: user).track(:banned)
+ end
end
end
diff --git a/app/services/users/banned_user_base_service.rb b/app/services/users/banned_user_base_service.rb
index 74c10581a6e..cec351904a9 100644
--- a/app/services/users/banned_user_base_service.rb
+++ b/app/services/users/banned_user_base_service.rb
@@ -12,6 +12,7 @@ module Users
if update_user(user)
log_event(user)
+ track_event(user)
success
else
messages = user.errors.full_messages
@@ -23,6 +24,9 @@ module Users
attr_reader :current_user
+ # Overridden in Users::BanService
+ def track_event(_); end
+
def state_error(user)
error(_("You cannot %{action} %{state} users." % { action: action.to_s, state: user.state }), :forbidden)
end
diff --git a/app/services/users/disallow_possible_spam_service.rb b/app/services/users/disallow_possible_spam_service.rb
new file mode 100644
index 00000000000..e31ba7ddff0
--- /dev/null
+++ b/app/services/users/disallow_possible_spam_service.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Users
+ class DisallowPossibleSpamService < BaseService
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
+ def execute(user)
+ user.custom_attributes.by_key(UserCustomAttribute::ALLOW_POSSIBLE_SPAM).delete_all
+ end
+ end
+end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 9ab6fcc9832..6837bc47035 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -189,6 +189,7 @@ class WebHookService
'Content-Type' => 'application/json',
'User-Agent' => "GitLab/#{Gitlab::VERSION}",
Gitlab::WebHooks::GITLAB_EVENT_HEADER => self.class.hook_to_event(hook_name),
+ Gitlab::WebHooks::GITLAB_UUID_HEADER => SecureRandom.uuid,
Gitlab::WebHooks::GITLAB_INSTANCE_HEADER => Gitlab.config.gitlab.base_url
}
diff --git a/app/services/work_items/export_csv_service.rb b/app/services/work_items/export_csv_service.rb
index ee20a2832ce..74bc1f526bf 100644
--- a/app/services/work_items/export_csv_service.rb
+++ b/app/services/work_items/export_csv_service.rb
@@ -28,7 +28,7 @@ module WorkItems
'Type' => ->(work_item) { work_item.work_item_type.name },
'Author' => 'author_name',
'Author Username' => ->(work_item) { work_item.author.username },
- 'Created At (UTC)' => ->(work_item) { work_item.created_at.to_s(:csv) }
+ 'Created At (UTC)' => ->(work_item) { work_item.created_at.to_fs(:csv) }
}
end