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>2020-06-18 14:18:50 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-06-18 14:18:50 +0300
commit8c7f4e9d5f36cff46365a7f8c4b9c21578c1e781 (patch)
treea77e7fe7a93de11213032ed4ab1f33a3db51b738 /app/services
parent00b35af3db1abfe813a778f643dad221aad51fca (diff)
Add latest changes from gitlab-org/gitlab@13-1-stable-ee
Diffstat (limited to 'app/services')
-rw-r--r--app/services/admin/propagate_integration_service.rb142
-rw-r--r--app/services/alert_management/alerts/update_service.rb98
-rw-r--r--app/services/alert_management/create_alert_issue_service.rb3
-rw-r--r--app/services/alert_management/process_prometheus_alert_service.rb6
-rw-r--r--app/services/auto_merge/base_service.rb54
-rw-r--r--app/services/award_emojis/destroy_service.rb2
-rw-r--r--app/services/ci/authorize_job_artifact_service.rb53
-rw-r--r--app/services/ci/build_report_result_service.rb36
-rw-r--r--app/services/ci/create_cross_project_pipeline_service.rb1
-rw-r--r--app/services/ci/create_web_ide_terminal_service.rb123
-rw-r--r--app/services/ci/extract_sections_from_build_trace_service.rb2
-rw-r--r--app/services/ci/process_pipeline_service.rb2
-rw-r--r--app/services/ci/update_ci_ref_status_service.rb1
-rw-r--r--app/services/ci/web_ide_config_service.rb59
-rw-r--r--app/services/clusters/applications/prometheus_config_service.rb12
-rw-r--r--app/services/clusters/parse_cluster_applications_artifact_service.rb27
-rw-r--r--app/services/commits/create_service.rb2
-rw-r--r--app/services/concerns/exclusive_lease_guard.rb2
-rw-r--r--app/services/concerns/integrations/project_test_data.rb62
-rw-r--r--app/services/concerns/measurable.rb2
-rw-r--r--app/services/concerns/spam_check_methods.rb11
-rw-r--r--app/services/container_expiration_policies/update_service.rb38
-rw-r--r--app/services/container_expiration_policy_service.rb7
-rw-r--r--app/services/design_management/delete_designs_service.rb5
-rw-r--r--app/services/design_management/save_designs_service.rb17
-rw-r--r--app/services/discussions/resolve_service.rb55
-rw-r--r--app/services/draft_notes/base_service.rb21
-rw-r--r--app/services/draft_notes/create_service.rb56
-rw-r--r--app/services/draft_notes/destroy_service.rb23
-rw-r--r--app/services/draft_notes/publish_service.rb67
-rw-r--r--app/services/event_create_service.rb132
-rw-r--r--app/services/git/wiki_push_service/change.rb6
-rw-r--r--app/services/groups/destroy_service.rb2
-rw-r--r--app/services/groups/group_links/create_service.rb2
-rw-r--r--app/services/groups/group_links/destroy_service.rb6
-rw-r--r--app/services/groups/import_export/export_service.rb29
-rw-r--r--app/services/groups/import_export/import_service.rb23
-rw-r--r--app/services/groups/transfer_service.rb10
-rw-r--r--app/services/import/github_service.rb29
-rw-r--r--app/services/integrations/test/base_service.rb36
-rw-r--r--app/services/integrations/test/project_service.rb47
-rw-r--r--app/services/issuable/bulk_update_service.rb13
-rw-r--r--app/services/issuable/clone/attributes_rewriter.rb2
-rw-r--r--app/services/issuable_base_service.rb12
-rw-r--r--app/services/issues/create_service.rb7
-rw-r--r--app/services/issues/import_csv_service.rb2
-rw-r--r--app/services/issues/update_service.rb6
-rw-r--r--app/services/jira/requests/base.rb52
-rw-r--r--app/services/jira/requests/projects.rb32
-rw-r--r--app/services/jira_import/start_import_service.rb4
-rw-r--r--app/services/jira_import/users_importer.rb43
-rw-r--r--app/services/jira_import/users_mapper.rb31
-rw-r--r--app/services/keys/create_service.rb8
-rw-r--r--app/services/labels/available_labels_service.rb6
-rw-r--r--app/services/labels/create_service.rb2
-rw-r--r--app/services/labels/promote_service.rb2
-rw-r--r--app/services/merge_requests/merge_service.rb4
-rw-r--r--app/services/merge_requests/update_service.rb4
-rw-r--r--app/services/metrics/dashboard/base_service.rb3
-rw-r--r--app/services/metrics/dashboard/self_monitoring_dashboard_service.rb4
-rw-r--r--app/services/metrics/dashboard/system_dashboard_service.rb4
-rw-r--r--app/services/milestones/promote_service.rb2
-rw-r--r--app/services/namespaces/check_storage_size_service.rb5
-rw-r--r--app/services/notes/create_service.rb8
-rw-r--r--app/services/notes/post_process_service.rb2
-rw-r--r--app/services/notification_recipients/build_service.rb6
-rw-r--r--app/services/notification_recipients/builder/new_review.rb43
-rw-r--r--app/services/notification_service.rb29
-rw-r--r--app/services/pages/delete_service.rb2
-rw-r--r--app/services/projects/after_import_service.rb8
-rw-r--r--app/services/projects/alerting/notify_service.rb33
-rw-r--r--app/services/projects/container_repository/cleanup_tags_service.rb12
-rw-r--r--app/services/projects/create_service.rb24
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/detect_repository_languages_service.rb2
-rw-r--r--app/services/projects/group_links/create_service.rb1
-rw-r--r--app/services/projects/group_links/destroy_service.rb4
-rw-r--r--app/services/projects/group_links/update_service.rb29
-rw-r--r--app/services/projects/hashed_storage/base_attachment_service.rb2
-rw-r--r--app/services/projects/import_export/export_service.rb14
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_link_service.rb2
-rw-r--r--app/services/projects/lsif_data_service.rb101
-rw-r--r--app/services/projects/move_deploy_keys_projects_service.rb2
-rw-r--r--app/services/projects/move_lfs_objects_projects_service.rb2
-rw-r--r--app/services/projects/move_notification_settings_service.rb2
-rw-r--r--app/services/projects/move_project_group_links_service.rb2
-rw-r--r--app/services/projects/move_project_members_service.rb2
-rw-r--r--app/services/projects/operations/update_service.rb4
-rw-r--r--app/services/projects/prometheus/alerts/create_events_service.rb8
-rw-r--r--app/services/projects/prometheus/alerts/notify_service.rb35
-rw-r--r--app/services/projects/propagate_service_template.rb48
-rw-r--r--app/services/projects/update_remote_mirror_service.rb6
-rw-r--r--app/services/projects/update_repository_storage_service.rb7
-rw-r--r--app/services/projects/update_service.rb13
-rw-r--r--app/services/projects/update_statistics_service.rb2
-rw-r--r--app/services/prometheus/create_default_alerts_service.rb11
-rw-r--r--app/services/prometheus/proxy_service.rb4
-rw-r--r--app/services/prometheus/proxy_variable_substitution_service.rb24
-rw-r--r--app/services/protected_branches/legacy_api_update_service.rb4
-rw-r--r--app/services/releases/create_evidence_service.rb25
-rw-r--r--app/services/releases/create_service.rb33
-rw-r--r--app/services/resource_events/change_labels_service.rb2
-rw-r--r--app/services/resource_events/change_state_service.rb36
-rw-r--r--app/services/resource_events/merge_into_notes_service.rb5
-rw-r--r--app/services/resource_events/synthetic_state_notes_builder_service.rb20
-rw-r--r--app/services/search_service.rb22
-rw-r--r--app/services/service_response.rb6
-rw-r--r--app/services/snippets/base_service.rb33
-rw-r--r--app/services/snippets/bulk_destroy_service.rb6
-rw-r--r--app/services/snippets/create_service.rb31
-rw-r--r--app/services/snippets/update_service.rb41
-rw-r--r--app/services/spam/akismet_service.rb4
-rw-r--r--app/services/spam/spam_action_service.rb21
-rw-r--r--app/services/spam/spam_constants.rb22
-rw-r--r--app/services/spam/spam_verdict_service.rb76
-rw-r--r--app/services/submit_usage_ping_service.rb2
-rw-r--r--app/services/suggestions/apply_service.rb110
-rw-r--r--app/services/suggestions/create_service.rb2
-rw-r--r--app/services/system_notes/issuables_service.rb14
-rw-r--r--app/services/test_hooks/base_service.rb28
-rw-r--r--app/services/test_hooks/project_service.rb72
-rw-r--r--app/services/test_hooks/system_service.rb25
-rw-r--r--app/services/todo_service.rb98
-rw-r--r--app/services/user_project_access_changed_service.rb3
-rw-r--r--app/services/users/build_service.rb3
-rw-r--r--app/services/users/destroy_service.rb2
-rw-r--r--app/services/users/migrate_to_ghost_user_service.rb5
-rw-r--r--app/services/web_hook_service.rb2
-rw-r--r--app/services/wiki_pages/create_service.rb2
-rw-r--r--app/services/wiki_pages/destroy_service.rb2
-rw-r--r--app/services/wiki_pages/update_service.rb2
132 files changed, 2168 insertions, 648 deletions
diff --git a/app/services/admin/propagate_integration_service.rb b/app/services/admin/propagate_integration_service.rb
new file mode 100644
index 00000000000..084b103ee3b
--- /dev/null
+++ b/app/services/admin/propagate_integration_service.rb
@@ -0,0 +1,142 @@
+# frozen_string_literal: true
+
+module Admin
+ class PropagateIntegrationService
+ BATCH_SIZE = 100
+
+ delegate :data_fields_present?, to: :integration
+
+ def self.propagate(integration:, overwrite:)
+ new(integration, overwrite).propagate
+ end
+
+ def initialize(integration, overwrite)
+ @integration = integration
+ @overwrite = overwrite
+ end
+
+ def propagate
+ if overwrite
+ update_integration_for_all_projects
+ else
+ update_integration_for_inherited_projects
+ end
+
+ create_integration_for_projects_without_integration
+ end
+
+ private
+
+ attr_reader :integration, :overwrite
+
+ # rubocop: disable Cop/InBatches
+ # rubocop: disable CodeReuse/ActiveRecord
+ def update_integration_for_inherited_projects
+ Service.where(type: integration.type, inherit_from_id: integration.id).in_batches(of: BATCH_SIZE) do |batch|
+ bulk_update_from_integration(batch)
+ end
+ end
+
+ def update_integration_for_all_projects
+ Service.where(type: integration.type).in_batches(of: BATCH_SIZE) do |batch|
+ bulk_update_from_integration(batch)
+ end
+ end
+ # rubocop: enable Cop/InBatches
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def bulk_update_from_integration(batch)
+ # Retrieving the IDs instantiates the ActiveRecord relation (batch)
+ # into concrete models, otherwise update_all will clear the relation.
+ # https://stackoverflow.com/q/34811646/462015
+ batch_ids = batch.pluck(:id)
+
+ Service.transaction do
+ batch.update_all(service_hash)
+
+ if data_fields_present?
+ integration.data_fields.class.where(service_id: batch_ids).update_all(data_fields_hash)
+ end
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def create_integration_for_projects_without_integration
+ loop do
+ batch = Project.uncached { project_ids_without_integration }
+
+ bulk_create_from_integration(batch) unless batch.empty?
+
+ break if batch.size < BATCH_SIZE
+ end
+ end
+
+ def bulk_create_from_integration(batch)
+ service_list = ServiceList.new(batch, service_hash, { 'inherit_from_id' => integration.id }).to_array
+
+ Project.transaction do
+ results = bulk_insert(*service_list)
+
+ if data_fields_present?
+ data_list = DataList.new(results, data_fields_hash, integration.data_fields.class).to_array
+
+ bulk_insert(*data_list)
+ end
+
+ run_callbacks(batch)
+ end
+ end
+
+ def bulk_insert(klass, columns, values_array)
+ items_to_insert = values_array.map { |array| Hash[columns.zip(array)] }
+
+ klass.insert_all(items_to_insert, returning: [:id])
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def run_callbacks(batch)
+ if active_external_issue_tracker?
+ Project.where(id: batch).update_all(has_external_issue_tracker: true)
+ end
+
+ if active_external_wiki?
+ Project.where(id: batch).update_all(has_external_wiki: true)
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def active_external_issue_tracker?
+ integration.issue_tracker? && !integration.default
+ end
+
+ def active_external_wiki?
+ integration.type == 'ExternalWikiService'
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def project_ids_without_integration
+ services = Service
+ .select('1')
+ .where('services.project_id = projects.id')
+ .where(type: integration.type)
+
+ Project
+ .where('NOT EXISTS (?)', services)
+ .where(pending_delete: false)
+ .where(archived: false)
+ .limit(BATCH_SIZE)
+ .pluck(:id)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def service_hash
+ @service_hash ||= integration.to_service_hash
+ .tap { |json| json['inherit_from_id'] = integration.id }
+ end
+
+ def data_fields_hash
+ @data_fields_hash ||= integration.to_data_fields_hash
+ end
+ end
+end
diff --git a/app/services/alert_management/alerts/update_service.rb b/app/services/alert_management/alerts/update_service.rb
new file mode 100644
index 00000000000..ffabbb37289
--- /dev/null
+++ b/app/services/alert_management/alerts/update_service.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module AlertManagement
+ module Alerts
+ class UpdateService
+ include Gitlab::Utils::StrongMemoize
+
+ # @param alert [AlertManagement::Alert]
+ # @param current_user [User]
+ # @param params [Hash] Attributes of the alert
+ def initialize(alert, current_user, params)
+ @alert = alert
+ @current_user = current_user
+ @params = params
+ end
+
+ def execute
+ return error_no_permissions unless allowed?
+ return error_no_updates if params.empty?
+
+ filter_assignees
+ old_assignees = alert.assignees.to_a
+
+ if alert.update(params)
+ process_assignement(old_assignees)
+
+ success
+ else
+ error(alert.errors.full_messages.to_sentence)
+ end
+ end
+
+ private
+
+ attr_reader :alert, :current_user, :params
+
+ def allowed?
+ current_user&.can?(:update_alert_management_alert, alert)
+ end
+
+ def assignee_todo_allowed?
+ assignee&.can?(:read_alert_management_alert, alert)
+ end
+
+ def todo_service
+ strong_memoize(:todo_service) do
+ TodoService.new
+ end
+ end
+
+ def success
+ ServiceResponse.success(payload: { alert: alert })
+ end
+
+ def error(message)
+ ServiceResponse.error(payload: { alert: alert }, message: message)
+ end
+
+ def error_no_permissions
+ error(_('You have no permissions'))
+ end
+
+ def error_no_updates
+ error(_('Please provide attributes to update'))
+ end
+
+ # ----- Assignee-related behavior ------
+ def filter_assignees
+ return if params[:assignees].nil?
+
+ params[:assignees] = Array(assignee)
+ end
+
+ def assignee
+ strong_memoize(:assignee) do
+ # Take first assignee while multiple are not currently supported
+ params[:assignees]&.first
+ end
+ end
+
+ def process_assignement(old_assignees)
+ assign_todo
+ add_assignee_system_note(old_assignees)
+ end
+
+ def assign_todo
+ # Remove check in follow-up issue https://gitlab.com/gitlab-org/gitlab/-/issues/222672
+ return unless assignee_todo_allowed?
+
+ todo_service.assign_alert(alert, current_user)
+ end
+
+ def add_assignee_system_note(old_assignees)
+ SystemNoteService.change_issuable_assignees(alert, alert.project, current_user, old_assignees)
+ end
+ end
+ end
+end
diff --git a/app/services/alert_management/create_alert_issue_service.rb b/app/services/alert_management/create_alert_issue_service.rb
index 0197f29145d..beacd240b08 100644
--- a/app/services/alert_management/create_alert_issue_service.rb
+++ b/app/services/alert_management/create_alert_issue_service.rb
@@ -29,8 +29,7 @@ module AlertManagement
delegate :project, to: :alert
def allowed?
- Feature.enabled?(:alert_management_create_alert_issue, project) &&
- user.can?(:create_issue, project)
+ user.can?(:create_issue, project)
end
def create_issue(alert, user, alert_payload)
diff --git a/app/services/alert_management/process_prometheus_alert_service.rb b/app/services/alert_management/process_prometheus_alert_service.rb
index af28f1354b3..90fcbd95e4b 100644
--- a/app/services/alert_management/process_prometheus_alert_service.rb
+++ b/app/services/alert_management/process_prometheus_alert_service.rb
@@ -29,6 +29,7 @@ module AlertManagement
def process_firing_alert_management_alert
if am_alert.present?
+ am_alert.register_new_event!
reset_alert_management_alert_status
else
create_alert_management_alert
@@ -47,7 +48,10 @@ module AlertManagement
def create_alert_management_alert
am_alert = AlertManagement::Alert.new(am_alert_params.merge(ended_at: nil))
- return if am_alert.save
+ if am_alert.save
+ am_alert.execute_services
+ return
+ end
logger.warn(
message: 'Unable to create AlertManagement::Alert',
diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb
index 1de2f31f87c..c4109765a1c 100644
--- a/app/services/auto_merge/base_service.rb
+++ b/app/services/auto_merge/base_service.rb
@@ -6,19 +6,18 @@ module AutoMerge
include MergeRequests::AssignsMergeParams
def execute(merge_request)
- assign_allowed_merge_params(merge_request, params.merge(auto_merge_strategy: strategy))
-
- merge_request.auto_merge_enabled = true
- merge_request.merge_user = current_user
-
- return :failed unless merge_request.save
-
- yield if block_given?
+ ActiveRecord::Base.transaction do
+ register_auto_merge_parameters!(merge_request)
+ yield if block_given?
+ end
# Notify the event that auto merge is enabled or merge param is updated
AutoMergeProcessWorker.perform_async(merge_request.id)
strategy.to_sym
+ rescue => e
+ track_exception(e, merge_request)
+ :failed
end
def update(merge_request)
@@ -30,23 +29,27 @@ module AutoMerge
end
def cancel(merge_request)
- if clear_auto_merge_parameters(merge_request)
+ ActiveRecord::Base.transaction do
+ clear_auto_merge_parameters!(merge_request)
yield if block_given?
-
- success
- else
- error("Can't cancel the automatic merge", 406)
end
+
+ success
+ rescue => e
+ track_exception(e, merge_request)
+ error("Can't cancel the automatic merge", 406)
end
def abort(merge_request, reason)
- if clear_auto_merge_parameters(merge_request)
+ ActiveRecord::Base.transaction do
+ clear_auto_merge_parameters!(merge_request)
yield if block_given?
-
- success
- else
- error("Can't abort the automatic merge", 406)
end
+
+ success
+ rescue => e
+ track_exception(e, merge_request)
+ error("Can't abort the automatic merge", 406)
end
def available_for?(merge_request)
@@ -65,7 +68,14 @@ module AutoMerge
end
end
- def clear_auto_merge_parameters(merge_request)
+ def register_auto_merge_parameters!(merge_request)
+ assign_allowed_merge_params(merge_request, params.merge(auto_merge_strategy: strategy))
+ merge_request.auto_merge_enabled = true
+ merge_request.merge_user = current_user
+ merge_request.save!
+ end
+
+ def clear_auto_merge_parameters!(merge_request)
merge_request.auto_merge_enabled = false
merge_request.merge_user = nil
@@ -76,7 +86,11 @@ module AutoMerge
'auto_merge_strategy'
)
- merge_request.save
+ merge_request.save!
+ end
+
+ def track_exception(error, merge_request)
+ Gitlab::ErrorTracking.track_exception(error, merge_request_id: merge_request&.id)
end
end
end
diff --git a/app/services/award_emojis/destroy_service.rb b/app/services/award_emojis/destroy_service.rb
index a61a7911a9d..cfd194262f9 100644
--- a/app/services/award_emojis/destroy_service.rb
+++ b/app/services/award_emojis/destroy_service.rb
@@ -13,7 +13,7 @@ module AwardEmojis
return error("User has not awarded emoji of type #{name} on the awardable", status: :forbidden)
end
- award = awards.destroy_all.first # rubocop: disable DestroyAll
+ award = awards.destroy_all.first # rubocop: disable Cop/DestroyAll
after_destroy(award)
success(award: award)
diff --git a/app/services/ci/authorize_job_artifact_service.rb b/app/services/ci/authorize_job_artifact_service.rb
new file mode 100644
index 00000000000..893e92d427c
--- /dev/null
+++ b/app/services/ci/authorize_job_artifact_service.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Ci
+ class AuthorizeJobArtifactService
+ include Gitlab::Utils::StrongMemoize
+
+ # Max size of the zipped LSIF artifact
+ LSIF_ARTIFACT_MAX_SIZE = 20.megabytes
+ LSIF_ARTIFACT_TYPE = 'lsif'
+
+ def initialize(job, params, max_size:)
+ @job = job
+ @max_size = max_size
+ @size = params[:filesize]
+ @type = params[:artifact_type].to_s
+ end
+
+ def forbidden?
+ lsif? && !code_navigation_enabled?
+ end
+
+ def too_large?
+ size && max_size <= size.to_i
+ end
+
+ def headers
+ default_headers = JobArtifactUploader.workhorse_authorize(has_length: false, maximum_size: max_size)
+ default_headers.tap do |h|
+ h[:ProcessLsif] = true if lsif? && code_navigation_enabled?
+ end
+ end
+
+ private
+
+ attr_reader :job, :size, :type
+
+ def code_navigation_enabled?
+ strong_memoize(:code_navigation_enabled) do
+ Feature.enabled?(:code_navigation, job.project, default_enabled: true)
+ end
+ end
+
+ def lsif?
+ strong_memoize(:lsif) do
+ type == LSIF_ARTIFACT_TYPE
+ end
+ end
+
+ def max_size
+ lsif? ? LSIF_ARTIFACT_MAX_SIZE : @max_size.to_i
+ end
+ end
+end
diff --git a/app/services/ci/build_report_result_service.rb b/app/services/ci/build_report_result_service.rb
new file mode 100644
index 00000000000..758ba1c73bf
--- /dev/null
+++ b/app/services/ci/build_report_result_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Ci
+ class BuildReportResultService
+ def execute(build)
+ return unless Feature.enabled?(:build_report_summary, build.project)
+ return unless build.has_test_reports?
+
+ build.report_results.create!(
+ project_id: build.project_id,
+ data: tests_params(build)
+ )
+ end
+
+ private
+
+ def generate_test_suite_report(build)
+ build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
+ end
+
+ def tests_params(build)
+ test_suite = generate_test_suite_report(build)
+
+ {
+ tests: {
+ name: test_suite.name,
+ duration: test_suite.total_time,
+ failed: test_suite.failed_count,
+ errored: test_suite.error_count,
+ skipped: test_suite.skipped_count,
+ success: test_suite.success_count
+ }
+ }
+ end
+ end
+end
diff --git a/app/services/ci/create_cross_project_pipeline_service.rb b/app/services/ci/create_cross_project_pipeline_service.rb
index a73a2e2b471..1700312b941 100644
--- a/app/services/ci/create_cross_project_pipeline_service.rb
+++ b/app/services/ci/create_cross_project_pipeline_service.rb
@@ -47,6 +47,7 @@ module Ci
# and update the status when the downstream pipeline completes.
subject.success! unless subject.dependent?
else
+ subject.options[:downstream_errors] = pipeline.errors.full_messages
subject.drop!(:downstream_pipeline_creation_failed)
end
end
diff --git a/app/services/ci/create_web_ide_terminal_service.rb b/app/services/ci/create_web_ide_terminal_service.rb
new file mode 100644
index 00000000000..29d40756ab4
--- /dev/null
+++ b/app/services/ci/create_web_ide_terminal_service.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+module Ci
+ class CreateWebIdeTerminalService < ::BaseService
+ include ::Gitlab::Utils::StrongMemoize
+
+ TerminalCreationError = Class.new(StandardError)
+
+ TERMINAL_NAME = 'terminal'.freeze
+
+ attr_reader :terminal
+
+ def execute
+ check_access!
+ validate_params!
+ load_terminal_config!
+
+ pipeline = create_pipeline!
+ success(pipeline: pipeline)
+ rescue TerminalCreationError => e
+ error(e.message)
+ rescue ActiveRecord::RecordInvalid => e
+ error("Failed to persist the pipeline: #{e.message}")
+ end
+
+ private
+
+ def create_pipeline!
+ build_pipeline.tap do |pipeline|
+ pipeline.stages << terminal_stage_seed(pipeline).to_resource
+ pipeline.save!
+
+ Ci::ProcessPipelineService
+ .new(pipeline)
+ .execute(nil, initial_process: true)
+
+ pipeline_created_counter.increment(source: :webide)
+ end
+ end
+
+ def build_pipeline
+ Ci::Pipeline.new(
+ project: project,
+ user: current_user,
+ source: :webide,
+ config_source: :webide_source,
+ ref: ref,
+ sha: sha,
+ tag: false,
+ before_sha: Gitlab::Git::BLANK_SHA
+ )
+ end
+
+ def terminal_stage_seed(pipeline)
+ attributes = {
+ name: TERMINAL_NAME,
+ index: 0,
+ builds: [terminal_build_seed]
+ }
+
+ Gitlab::Ci::Pipeline::Seed::Stage.new(pipeline, attributes, [])
+ end
+
+ def terminal_build_seed
+ terminal.merge(
+ name: TERMINAL_NAME,
+ stage: TERMINAL_NAME,
+ user: current_user,
+ scheduling_type: :stage)
+ end
+
+ def load_terminal_config!
+ result = ::Ci::WebIdeConfigService.new(project, current_user, sha: sha).execute
+ raise TerminalCreationError, result[:message] if result[:status] != :success
+
+ @terminal = result[:terminal]
+ raise TerminalCreationError, 'Terminal is not configured' unless terminal
+ end
+
+ def validate_params!
+ unless sha
+ raise TerminalCreationError, 'Ref does not exist'
+ end
+
+ unless branch_exists?
+ raise TerminalCreationError, 'Ref needs to be a branch'
+ end
+ end
+
+ def check_access!
+ unless can?(current_user, :create_web_ide_terminal, project)
+ raise TerminalCreationError, 'Insufficient permissions to create a terminal'
+ end
+
+ if terminal_active?
+ raise TerminalCreationError, 'There is already a terminal running'
+ end
+ end
+
+ def pipeline_created_counter
+ @pipeline_created_counter ||= Gitlab::Metrics
+ .counter(:pipelines_created_total, "Counter of pipelines created")
+ end
+
+ def terminal_active?
+ project.active_webide_pipelines(user: current_user).exists?
+ end
+
+ def ref
+ strong_memoize(:ref) do
+ Gitlab::Git.ref_name(params[:ref])
+ end
+ end
+
+ def branch_exists?
+ project.repository.branch_exists?(ref)
+ end
+
+ def sha
+ project.commit(params[:ref]).try(:id)
+ end
+ end
+end
diff --git a/app/services/ci/extract_sections_from_build_trace_service.rb b/app/services/ci/extract_sections_from_build_trace_service.rb
index 97f9918fdb7..c756e376901 100644
--- a/app/services/ci/extract_sections_from_build_trace_service.rb
+++ b/app/services/ci/extract_sections_from_build_trace_service.rb
@@ -5,7 +5,7 @@ module Ci
def execute(build)
return false unless build.trace_sections.empty?
- Gitlab::Database.bulk_insert(BuildTraceSection.table_name, extract_sections(build))
+ Gitlab::Database.bulk_insert(BuildTraceSection.table_name, extract_sections(build)) # rubocop:disable Gitlab/BulkInsert
true
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 3f23e81dcdd..80ebe5f5eb6 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -11,7 +11,7 @@ module Ci
def execute(trigger_build_ids = nil, initial_process: false)
update_retried
- if Feature.enabled?(:ci_atomic_processing, pipeline.project)
+ if ::Gitlab::Ci::Features.atomic_processing?(pipeline.project)
Ci::PipelineProcessing::AtomicProcessingService
.new(pipeline)
.execute
diff --git a/app/services/ci/update_ci_ref_status_service.rb b/app/services/ci/update_ci_ref_status_service.rb
index 4f7ac4d11b0..22cc43232cc 100644
--- a/app/services/ci/update_ci_ref_status_service.rb
+++ b/app/services/ci/update_ci_ref_status_service.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+# NOTE: This class is unused and to be removed in 13.1~
module Ci
class UpdateCiRefStatusService
include Gitlab::OptimisticLocking
diff --git a/app/services/ci/web_ide_config_service.rb b/app/services/ci/web_ide_config_service.rb
new file mode 100644
index 00000000000..ade9132f419
--- /dev/null
+++ b/app/services/ci/web_ide_config_service.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Ci
+ class WebIdeConfigService < ::BaseService
+ include ::Gitlab::Utils::StrongMemoize
+
+ ValidationError = Class.new(StandardError)
+
+ WEBIDE_CONFIG_FILE = '.gitlab/.gitlab-webide.yml'.freeze
+
+ attr_reader :config, :config_content
+
+ def execute
+ check_access!
+ load_config_content!
+ load_config!
+
+ success(terminal: config.terminal_value)
+ rescue ValidationError => e
+ error(e.message)
+ end
+
+ private
+
+ def check_access!
+ unless can?(current_user, :download_code, project)
+ raise ValidationError, 'Insufficient permissions to read configuration'
+ end
+ end
+
+ def load_config_content!
+ @config_content = webide_yaml_from_repo
+
+ unless config_content
+ raise ValidationError, "Failed to load Web IDE config file '#{WEBIDE_CONFIG_FILE}' for #{params[:sha]}"
+ end
+ end
+
+ def load_config!
+ @config = Gitlab::WebIde::Config.new(config_content)
+
+ unless @config.valid?
+ raise ValidationError, @config.errors.first
+ end
+ rescue Gitlab::WebIde::Config::ConfigError => e
+ raise ValidationError, e.message
+ end
+
+ def webide_yaml_from_repo
+ gitlab_webide_yml_for(params[:sha])
+ rescue GRPC::NotFound, GRPC::Internal
+ nil
+ end
+
+ def gitlab_webide_yml_for(sha)
+ project.repository.blob_data_at(sha, WEBIDE_CONFIG_FILE)
+ end
+ end
+end
diff --git a/app/services/clusters/applications/prometheus_config_service.rb b/app/services/clusters/applications/prometheus_config_service.rb
index 34d44ab881e..50c4e26b0d0 100644
--- a/app/services/clusters/applications/prometheus_config_service.rb
+++ b/app/services/clusters/applications/prometheus_config_service.rb
@@ -132,19 +132,21 @@ module Clusters
end
def alerts(environment)
- variables = Gitlab::Prometheus::QueryVariables.call(environment)
alerts = Projects::Prometheus::AlertsFinder
.new(environment: environment)
.execute
alerts.map do |alert|
- substitute_query_variables(alert.to_param, variables)
+ hash = alert.to_param
+ hash['expr'] = substitute_query_variables(hash['expr'], environment)
+ hash
end
end
- def substitute_query_variables(hash, variables)
- hash['expr'] %= variables
- hash
+ def substitute_query_variables(query, environment)
+ result = ::Prometheus::ProxyVariableSubstitutionService.new(environment, query: query).execute
+
+ result[:params][:query]
end
def environments
diff --git a/app/services/clusters/parse_cluster_applications_artifact_service.rb b/app/services/clusters/parse_cluster_applications_artifact_service.rb
index b8e1c80cfe7..35fba5f47c7 100644
--- a/app/services/clusters/parse_cluster_applications_artifact_service.rb
+++ b/app/services/clusters/parse_cluster_applications_artifact_service.rb
@@ -18,13 +18,9 @@ module Clusters
raise ArgumentError, 'Artifact is not cluster_applications file type' unless artifact&.cluster_applications?
- unless artifact.file.size < MAX_ACCEPTABLE_ARTIFACT_SIZE
- return error(too_big_error_message, :bad_request)
- end
-
- unless cluster
- return error(s_('ClusterIntegration|No deployment cluster found for this job'))
- end
+ return error(too_big_error_message, :bad_request) unless artifact.file.size < MAX_ACCEPTABLE_ARTIFACT_SIZE
+ return error(no_deployment_message, :bad_request) unless job.deployment
+ return error(no_deployment_cluster_message, :bad_request) unless cluster
parse!(artifact)
@@ -61,7 +57,8 @@ module Clusters
Clusters::Cluster.transaction do
RELEASE_NAMES.each do |release_name|
- application = find_or_build_application(release_name)
+ application_class = Clusters::Cluster::APPLICATIONS[release_name]
+ application = cluster.find_or_build_application(application_class)
release = release_by_name[release_name]
@@ -80,16 +77,18 @@ module Clusters
end
end
- def find_or_build_application(application_name)
- application_class = Clusters::Cluster::APPLICATIONS[application_name]
-
- cluster.find_or_build_application(application_class)
- end
-
def too_big_error_message
human_size = ActiveSupport::NumberHelper.number_to_human_size(MAX_ACCEPTABLE_ARTIFACT_SIZE)
s_('ClusterIntegration|Cluster_applications artifact too big. Maximum allowable size: %{human_size}') % { human_size: human_size }
end
+
+ def no_deployment_message
+ s_('ClusterIntegration|No deployment found for this job')
+ end
+
+ def no_deployment_cluster_message
+ s_('ClusterIntegration|No deployment cluster found for this job')
+ end
end
end
diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb
index bd238605ac1..d80d9bebe9c 100644
--- a/app/services/commits/create_service.rb
+++ b/app/services/commits/create_service.rb
@@ -30,12 +30,14 @@ module Commits
success(result: new_commit)
rescue ChangeError => ex
+ Gitlab::ErrorTracking.log_exception(ex)
error(ex.message, pass_back: { error_code: ex.error_code })
rescue ValidationError,
Gitlab::Git::Index::IndexError,
Gitlab::Git::CommitError,
Gitlab::Git::PreReceiveError,
Gitlab::Git::CommandError => ex
+ Gitlab::ErrorTracking.log_exception(ex)
error(ex.message)
end
diff --git a/app/services/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb
index 0c5ecca3a50..4678d051d29 100644
--- a/app/services/concerns/exclusive_lease_guard.rb
+++ b/app/services/concerns/exclusive_lease_guard.rb
@@ -58,6 +58,6 @@ module ExclusiveLeaseGuard
end
def log_error(message, extra_args = {})
- Rails.logger.error(message) # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.error(message)
end
end
diff --git a/app/services/concerns/integrations/project_test_data.rb b/app/services/concerns/integrations/project_test_data.rb
new file mode 100644
index 00000000000..4d551430315
--- /dev/null
+++ b/app/services/concerns/integrations/project_test_data.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module Integrations
+ module ProjectTestData
+ private
+
+ def push_events_data
+ Gitlab::DataBuilder::Push.build_sample(project, current_user)
+ end
+
+ def note_events_data
+ note = project.notes.first
+ return { error: s_('TestHooks|Ensure the project has notes.') } unless note.present?
+
+ Gitlab::DataBuilder::Note.build(note, current_user)
+ end
+
+ def issues_events_data
+ issue = project.issues.first
+ return { error: s_('TestHooks|Ensure the project has issues.') } unless issue.present?
+
+ issue.to_hook_data(current_user)
+ end
+
+ def merge_requests_events_data
+ merge_request = project.merge_requests.first
+ return { error: s_('TestHooks|Ensure the project has merge requests.') } unless merge_request.present?
+
+ merge_request.to_hook_data(current_user)
+ end
+
+ def job_events_data
+ build = project.builds.first
+ return { error: s_('TestHooks|Ensure the project has CI jobs.') } unless build.present?
+
+ Gitlab::DataBuilder::Build.build(build)
+ end
+
+ def pipeline_events_data
+ pipeline = project.ci_pipelines.newest_first.first
+ return { error: s_('TestHooks|Ensure the project has CI pipelines.') } unless pipeline.present?
+
+ Gitlab::DataBuilder::Pipeline.build(pipeline)
+ end
+
+ def wiki_page_events_data
+ page = project.wiki.list_pages(limit: 1).first
+ if !project.wiki_enabled? || page.blank?
+ return { error: s_('TestHooks|Ensure the wiki is enabled and has pages.') }
+ end
+
+ Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create')
+ end
+
+ def deployment_events_data
+ deployment = project.deployments.first
+ return { error: s_('TestHooks|Ensure the project has deployments.') } unless deployment.present?
+
+ Gitlab::DataBuilder::Deployment.build(deployment)
+ end
+ end
+end
diff --git a/app/services/concerns/measurable.rb b/app/services/concerns/measurable.rb
index 5a74f15506e..b099a58a9ae 100644
--- a/app/services/concerns/measurable.rb
+++ b/app/services/concerns/measurable.rb
@@ -4,8 +4,6 @@
# Example:
# ```
# class DummyService
-# prepend Measurable
-#
# def execute
# # ...
# end
diff --git a/app/services/concerns/spam_check_methods.rb b/app/services/concerns/spam_check_methods.rb
index 53e9e001463..939f8f183ab 100644
--- a/app/services/concerns/spam_check_methods.rb
+++ b/app/services/concerns/spam_check_methods.rb
@@ -22,15 +22,18 @@ module SpamCheckMethods
# a dirty instance, which means it should be already assigned with the new
# attribute values.
# rubocop:disable Gitlab/ModuleWithInstanceVariables
- def spam_check(spammable, user)
+ def spam_check(spammable, user, action:)
+ raise ArgumentError.new('Please provide an action, such as :create') unless action
+
Spam::SpamActionService.new(
spammable: spammable,
- request: @request
+ request: @request,
+ user: user,
+ context: { action: action }
).execute(
api: @api,
recaptcha_verified: @recaptcha_verified,
- spam_log_id: @spam_log_id,
- user: user)
+ spam_log_id: @spam_log_id)
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/services/container_expiration_policies/update_service.rb b/app/services/container_expiration_policies/update_service.rb
new file mode 100644
index 00000000000..2f34941d692
--- /dev/null
+++ b/app/services/container_expiration_policies/update_service.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module ContainerExpirationPolicies
+ class UpdateService < BaseContainerService
+ include Gitlab::Utils::StrongMemoize
+
+ ALLOWED_ATTRIBUTES = %i[enabled cadence older_than keep_n name_regex name_regex_keep].freeze
+
+ def execute
+ return ServiceResponse.error(message: 'Access Denied', http_status: 403) unless allowed?
+
+ if container_expiration_policy.update(container_expiration_policy_params)
+ ServiceResponse.success(payload: { container_expiration_policy: container_expiration_policy })
+ else
+ ServiceResponse.error(
+ message: container_expiration_policy.errors.full_messages.to_sentence || 'Bad request',
+ http_status: 400
+ )
+ end
+ end
+
+ private
+
+ def container_expiration_policy
+ strong_memoize(:container_expiration_policy) do
+ @container.container_expiration_policy || @container.build_container_expiration_policy
+ end
+ end
+
+ def allowed?
+ Ability.allowed?(current_user, :destroy_container_image, @container)
+ end
+
+ def container_expiration_policy_params
+ @params.slice(*ALLOWED_ATTRIBUTES)
+ end
+ end
+end
diff --git a/app/services/container_expiration_policy_service.rb b/app/services/container_expiration_policy_service.rb
index 82274fd8668..80f32298323 100644
--- a/app/services/container_expiration_policy_service.rb
+++ b/app/services/container_expiration_policy_service.rb
@@ -1,7 +1,14 @@
# frozen_string_literal: true
class ContainerExpirationPolicyService < BaseService
+ InvalidPolicyError = Class.new(StandardError)
+
def execute(container_expiration_policy)
+ unless container_expiration_policy.valid?
+ container_expiration_policy.disable!
+ raise InvalidPolicyError
+ end
+
container_expiration_policy.schedule_next_run!
container_expiration_policy.container_repositories.find_each do |container_repository|
diff --git a/app/services/design_management/delete_designs_service.rb b/app/services/design_management/delete_designs_service.rb
index e69f07db5bf..5d875c630a0 100644
--- a/app/services/design_management/delete_designs_service.rb
+++ b/app/services/design_management/delete_designs_service.rb
@@ -15,6 +15,7 @@ module DesignManagement
return error('Forbidden!') unless can_delete_designs?
version = delete_designs!
+ EventCreateService.new.destroy_designs(designs, current_user)
success(version: version)
end
@@ -48,7 +49,9 @@ module DesignManagement
end
def design_action(design)
- on_success { counter.count(:delete) }
+ on_success do
+ counter.count(:delete)
+ end
DesignManagement::DesignAction.new(design, :delete)
end
diff --git a/app/services/design_management/save_designs_service.rb b/app/services/design_management/save_designs_service.rb
index a09c19bc885..0446d2f1ee8 100644
--- a/app/services/design_management/save_designs_service.rb
+++ b/app/services/design_management/save_designs_service.rb
@@ -20,6 +20,7 @@ module DesignManagement
uploaded_designs, version = upload_designs!
skipped_designs = designs - uploaded_designs
+ create_events
success({ designs: uploaded_designs, version: version, skipped_designs: skipped_designs })
rescue ::ActiveRecord::RecordInvalid => e
error(e.message)
@@ -47,7 +48,7 @@ module DesignManagement
end
def build_actions
- files.zip(designs).flat_map do |(file, design)|
+ @actions ||= files.zip(designs).flat_map do |(file, design)|
Array.wrap(build_design_action(file, design))
end
end
@@ -57,7 +58,9 @@ module DesignManagement
return if design_unchanged?(design, content)
action = new_file?(design) ? :create : :update
- on_success { ::Gitlab::UsageDataCounters::DesignsCounter.count(action) }
+ on_success do
+ ::Gitlab::UsageDataCounters::DesignsCounter.count(action)
+ end
DesignManagement::DesignAction.new(design, action, content)
end
@@ -67,6 +70,16 @@ module DesignManagement
content == existing_blobs[design]&.data
end
+ def create_events
+ by_action = @actions.group_by(&:action).transform_values { |grp| grp.map(&:design) }
+
+ event_create_service.save_designs(current_user, **by_action)
+ end
+
+ def event_create_service
+ @event_create_service ||= EventCreateService.new
+ end
+
def commit_message
<<~MSG
Updated #{files.size} #{'designs'.pluralize(files.size)}
diff --git a/app/services/discussions/resolve_service.rb b/app/services/discussions/resolve_service.rb
index 816cd45b07a..946fb5f1372 100644
--- a/app/services/discussions/resolve_service.rb
+++ b/app/services/discussions/resolve_service.rb
@@ -2,25 +2,68 @@
module Discussions
class ResolveService < Discussions::BaseService
- def execute(one_or_more_discussions)
- Array(one_or_more_discussions).each { |discussion| resolve_discussion(discussion) }
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(project, user = nil, params = {})
+ @discussions = Array.wrap(params.fetch(:one_or_more_discussions))
+ @follow_up_issue = params[:follow_up_issue]
+ @resolved_count = 0
+
+ raise ArgumentError, 'Discussions must be all for the same noteable' \
+ unless noteable_is_same?
+
+ super
+ end
+
+ def execute
+ discussions.each(&method(:resolve_discussion))
+ process_auto_merge
+ end
+
+ private
+
+ attr_accessor :discussions, :follow_up_issue
+
+ def noteable_is_same?
+ return true unless discussions.size > 1
+
+ # Perform this check without fetching extra records
+ discussions.all? do |discussion|
+ discussion.noteable_type == first_discussion.noteable_type &&
+ discussion.noteable_id == first_discussion.noteable_id
+ end
end
def resolve_discussion(discussion)
return unless discussion.can_resolve?(current_user)
discussion.resolve!(current_user)
+ @resolved_count += 1
- MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(merge_request)
+ MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(merge_request) if merge_request
SystemNoteService.discussion_continued_in_issue(discussion, project, current_user, follow_up_issue) if follow_up_issue
end
+ def first_discussion
+ @first_discussion ||= discussions.first
+ end
+
def merge_request
- params[:merge_request]
+ strong_memoize(:merge_request) do
+ first_discussion.noteable if first_discussion.for_merge_request?
+ end
+ end
+
+ def process_auto_merge
+ return unless merge_request
+ return unless @resolved_count.positive?
+ return unless discussions_ready_to_merge?
+
+ AutoMergeProcessWorker.perform_async(merge_request.id)
end
- def follow_up_issue
- params[:follow_up_issue]
+ def discussions_ready_to_merge?
+ merge_request.auto_merge_enabled? && merge_request.mergeable_discussions_state?
end
end
end
diff --git a/app/services/draft_notes/base_service.rb b/app/services/draft_notes/base_service.rb
new file mode 100644
index 00000000000..89daae0e8f4
--- /dev/null
+++ b/app/services/draft_notes/base_service.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module DraftNotes
+ class BaseService < ::BaseService
+ attr_accessor :merge_request, :current_user, :params
+
+ def initialize(merge_request, current_user, params = nil)
+ @merge_request, @current_user, @params = merge_request, current_user, params.dup
+ end
+
+ private
+
+ def draft_notes
+ @draft_notes ||= merge_request.draft_notes.order_id_asc.authored_by(current_user)
+ end
+
+ def project
+ merge_request.target_project
+ end
+ end
+end
diff --git a/app/services/draft_notes/create_service.rb b/app/services/draft_notes/create_service.rb
new file mode 100644
index 00000000000..501778b7d5f
--- /dev/null
+++ b/app/services/draft_notes/create_service.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+module DraftNotes
+ class CreateService < DraftNotes::BaseService
+ attr_accessor :in_draft_mode, :in_reply_to_discussion_id
+
+ def initialize(merge_request, current_user, params = nil)
+ @in_reply_to_discussion_id = params.delete(:in_reply_to_discussion_id)
+ super
+ end
+
+ def execute
+ if in_reply_to_discussion_id.present?
+ unless discussion
+ return base_error(_('Thread to reply to cannot be found'))
+ end
+
+ params[:discussion_id] = discussion.reply_id
+ end
+
+ if params[:resolve_discussion] && !can_resolve_discussion?
+ return base_error(_('User is not allowed to resolve thread'))
+ end
+
+ draft_note = DraftNote.new(params)
+ draft_note.merge_request = merge_request
+ draft_note.author = current_user
+ draft_note.save
+
+ if in_reply_to_discussion_id.blank? && draft_note.diff_file&.unfolded?
+ merge_request.diffs.clear_cache
+ end
+
+ draft_note
+ end
+
+ private
+
+ def base_error(text)
+ DraftNote.new.tap do |draft|
+ draft.errors.add(:base, text)
+ end
+ end
+
+ def discussion
+ @discussion ||= merge_request.notes.find_discussion(in_reply_to_discussion_id)
+ end
+
+ def can_resolve_discussion?
+ note = discussion&.notes&.first
+ return false unless note
+
+ current_user && Ability.allowed?(current_user, :resolve_note, note)
+ end
+ end
+end
diff --git a/app/services/draft_notes/destroy_service.rb b/app/services/draft_notes/destroy_service.rb
new file mode 100644
index 00000000000..ddca0debb03
--- /dev/null
+++ b/app/services/draft_notes/destroy_service.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module DraftNotes
+ class DestroyService < DraftNotes::BaseService
+ # If no `draft` is given it fallsback to all
+ # draft notes of the given merge request and user.
+ def execute(draft = nil)
+ drafts = draft || draft_notes
+
+ clear_highlight_diffs_cache(Array.wrap(drafts))
+
+ drafts.is_a?(DraftNote) ? drafts.destroy! : drafts.delete_all
+ end
+
+ private
+
+ def clear_highlight_diffs_cache(drafts)
+ if drafts.any? { |draft| draft.diff_file&.unfolded? }
+ merge_request.diffs.clear_cache
+ end
+ end
+ end
+end
diff --git a/app/services/draft_notes/publish_service.rb b/app/services/draft_notes/publish_service.rb
new file mode 100644
index 00000000000..a9a7304e5ed
--- /dev/null
+++ b/app/services/draft_notes/publish_service.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module DraftNotes
+ class PublishService < DraftNotes::BaseService
+ def execute(draft = nil)
+ return error('Not allowed to create notes') unless can?(current_user, :create_note, merge_request)
+
+ if draft
+ publish_draft_note(draft)
+ else
+ publish_draft_notes
+ end
+
+ success
+ rescue ActiveRecord::RecordInvalid => e
+ message = "Unable to save #{e.record.class.name}: #{e.record.errors.full_messages.join(", ")} "
+ error(message)
+ end
+
+ private
+
+ def publish_draft_note(draft)
+ create_note_from_draft(draft)
+ draft.delete
+
+ MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(merge_request)
+ end
+
+ def publish_draft_notes
+ return if draft_notes.empty?
+
+ review = Review.create!(author: current_user, merge_request: merge_request, project: project)
+
+ draft_notes.map do |draft_note|
+ draft_note.review = review
+ create_note_from_draft(draft_note)
+ end
+ draft_notes.delete_all
+
+ notification_service.async.new_review(review)
+ MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(merge_request)
+ end
+
+ def create_note_from_draft(draft)
+ # Make sure the diff file is unfolded in order to find the correct line
+ # codes.
+ draft.diff_file&.unfold_diff_lines(draft.original_position)
+
+ note = Notes::CreateService.new(draft.project, draft.author, draft.publish_params).execute
+ set_discussion_resolve_status(note, draft)
+
+ note
+ end
+
+ def set_discussion_resolve_status(note, draft_note)
+ return unless draft_note.discussion_id.present?
+
+ discussion = note.discussion
+
+ if draft_note.resolve_discussion && discussion.can_resolve?(current_user)
+ discussion.resolve!(current_user)
+ else
+ discussion.unresolve!
+ end
+ end
+ end
+end
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index 522f36cda46..89c3225dbcd 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -11,67 +11,81 @@ class EventCreateService
IllegalActionError = Class.new(StandardError)
def open_issue(issue, current_user)
- create_record_event(issue, current_user, Event::CREATED)
+ create_resource_event(issue, current_user, :opened)
+
+ create_record_event(issue, current_user, :created)
end
def close_issue(issue, current_user)
- create_record_event(issue, current_user, Event::CLOSED)
+ create_resource_event(issue, current_user, :closed)
+
+ create_record_event(issue, current_user, :closed)
end
def reopen_issue(issue, current_user)
- create_record_event(issue, current_user, Event::REOPENED)
+ create_resource_event(issue, current_user, :reopened)
+
+ create_record_event(issue, current_user, :reopened)
end
def open_mr(merge_request, current_user)
- create_record_event(merge_request, current_user, Event::CREATED)
+ create_resource_event(merge_request, current_user, :opened)
+
+ create_record_event(merge_request, current_user, :created)
end
def close_mr(merge_request, current_user)
- create_record_event(merge_request, current_user, Event::CLOSED)
+ create_resource_event(merge_request, current_user, :closed)
+
+ create_record_event(merge_request, current_user, :closed)
end
def reopen_mr(merge_request, current_user)
- create_record_event(merge_request, current_user, Event::REOPENED)
+ create_resource_event(merge_request, current_user, :reopened)
+
+ create_record_event(merge_request, current_user, :reopened)
end
def merge_mr(merge_request, current_user)
- create_record_event(merge_request, current_user, Event::MERGED)
+ create_resource_event(merge_request, current_user, :merged)
+
+ create_record_event(merge_request, current_user, :merged)
end
def open_milestone(milestone, current_user)
- create_record_event(milestone, current_user, Event::CREATED)
+ create_record_event(milestone, current_user, :created)
end
def close_milestone(milestone, current_user)
- create_record_event(milestone, current_user, Event::CLOSED)
+ create_record_event(milestone, current_user, :closed)
end
def reopen_milestone(milestone, current_user)
- create_record_event(milestone, current_user, Event::REOPENED)
+ create_record_event(milestone, current_user, :reopened)
end
def destroy_milestone(milestone, current_user)
- create_record_event(milestone, current_user, Event::DESTROYED)
+ create_record_event(milestone, current_user, :destroyed)
end
def leave_note(note, current_user)
- create_record_event(note, current_user, Event::COMMENTED)
+ create_record_event(note, current_user, :commented)
end
def join_project(project, current_user)
- create_event(project, current_user, Event::JOINED)
+ create_event(project, current_user, :joined)
end
def leave_project(project, current_user)
- create_event(project, current_user, Event::LEFT)
+ create_event(project, current_user, :left)
end
def expired_leave_project(project, current_user)
- create_event(project, current_user, Event::EXPIRED)
+ create_event(project, current_user, :expired)
end
def create_project(project, current_user)
- create_event(project, current_user, Event::CREATED)
+ create_event(project, current_user, :created)
end
def push(project, current_user, push_data)
@@ -82,11 +96,34 @@ class EventCreateService
create_push_event(BulkPushEventPayloadService, project, current_user, push_data)
end
+ def save_designs(current_user, create: [], update: [])
+ created = create.group_by(&:project).flat_map do |project, designs|
+ Feature.enabled?(:design_activity_events, project) ? designs : []
+ end.to_set
+ updated = update.group_by(&:project).flat_map do |project, designs|
+ Feature.enabled?(:design_activity_events, project) ? designs : []
+ end.to_set
+ return [] if created.empty? && updated.empty?
+
+ records = created.zip([:created].cycle) + updated.zip([:updated].cycle)
+
+ create_record_events(records, current_user)
+ end
+
+ def destroy_designs(designs, current_user)
+ designs = designs.select do |design|
+ Feature.enabled?(:design_activity_events, design.project)
+ end
+ return [] unless designs.present?
+
+ create_record_events(designs.zip([:destroyed].cycle), current_user)
+ end
+
# Create a new wiki page event
#
# @param [WikiPage::Meta] wiki_page_meta The event target
# @param [User] author The event author
- # @param [Integer] action One of the Event::WIKI_ACTIONS
+ # @param [Symbol] action One of the Event::WIKI_ACTIONS
#
# @return a tuple of event and either :found or :created
def wiki_event(wiki_page_meta, author, action)
@@ -100,7 +137,7 @@ class EventCreateService
event = create_record_event(wiki_page_meta, author, action)
# Ensure that the event is linked in time to the metadata, for non-deletes
- unless action == Event::DESTROYED
+ unless event.destroyed_action?
time_stamp = wiki_page_meta.updated_at
event.update_columns(updated_at: time_stamp, created_at: time_stamp)
end
@@ -111,16 +148,41 @@ class EventCreateService
private
def existing_wiki_event(wiki_page_meta, action)
- if action == Event::DESTROYED
+ if Event.actions.fetch(action) == Event.actions[:destroyed]
most_recent = Event.for_wiki_meta(wiki_page_meta).recent.first
- return most_recent if most_recent.present? && most_recent.action == action
+ return most_recent if most_recent.present? && Event.actions[most_recent.action] == Event.actions[action]
else
Event.for_wiki_meta(wiki_page_meta).created_at(wiki_page_meta.updated_at).first
end
end
def create_record_event(record, current_user, status)
- create_event(record.resource_parent, current_user, status, target_id: record.id, target_type: record.class.name)
+ create_event(record.resource_parent, current_user, status,
+ target_id: record.id, target_type: record.class.name)
+ end
+
+ # If creating several events, this method will insert them all in a single
+ # statement
+ #
+ # @param [[Eventable, Symbol]] a list of pairs of records and a valid status
+ # @param [User] the author of the event
+ def create_record_events(pairs, current_user)
+ base_attrs = {
+ created_at: Time.now.utc,
+ updated_at: Time.now.utc,
+ author_id: current_user.id
+ }
+
+ attribute_sets = pairs.map do |record, status|
+ action = Event.actions[status]
+ raise IllegalActionError, "#{status} is not a valid status" if action.nil?
+
+ parent_attrs(record.resource_parent)
+ .merge(base_attrs)
+ .merge(action: action, target_id: record.id, target_type: record.class.name)
+ end
+
+ Event.insert_all(attribute_sets, returning: %w[id])
end
def create_push_event(service_class, project, current_user, push_data)
@@ -128,7 +190,7 @@ class EventCreateService
# when creating push payload data will result in the event creation being
# rolled back as well.
event = Event.transaction do
- new_event = create_event(project, current_user, Event::PUSHED)
+ new_event = create_event(project, current_user, :pushed)
service_class.new(new_event, push_data).execute
@@ -146,16 +208,34 @@ class EventCreateService
action: status,
author_id: current_user.id
)
+ attributes.merge!(parent_attrs(resource_parent))
+
+ Event.create!(attributes)
+ end
+ def parent_attrs(resource_parent)
resource_parent_attr = case resource_parent
when Project
- :project
+ :project_id
when Group
- :group
+ :group_id
end
- attributes[resource_parent_attr] = resource_parent if resource_parent_attr
- Event.create!(attributes)
+ return {} unless resource_parent_attr
+
+ { resource_parent_attr => resource_parent.id }
+ end
+
+ def create_resource_event(issuable, current_user, status)
+ return unless state_change_tracking_enabled?(issuable)
+
+ ResourceEvents::ChangeStateService.new(resource: issuable, user: current_user)
+ .execute(status)
+ end
+
+ def state_change_tracking_enabled?(issuable)
+ issuable&.respond_to?(:resource_state_events) &&
+ ::Feature.enabled?(:track_resource_state_change_events, issuable&.project)
end
end
diff --git a/app/services/git/wiki_push_service/change.rb b/app/services/git/wiki_push_service/change.rb
index 8685850165a..14e622dd147 100644
--- a/app/services/git/wiki_push_service/change.rb
+++ b/app/services/git/wiki_push_service/change.rb
@@ -21,11 +21,11 @@ module Git
def event_action
case raw_change.operation
when :added
- Event::CREATED
+ :created
when :deleted
- Event::DESTROYED
+ :destroyed
else
- Event::UPDATED
+ :updated
end
end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index 9437eb9eede..1bff70e6c2e 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -6,7 +6,7 @@ module Groups
def async_execute
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
- Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/groups/group_links/create_service.rb b/app/services/groups/group_links/create_service.rb
index 2ce53fcfe4a..589ac7ccde7 100644
--- a/app/services/groups/group_links/create_service.rb
+++ b/app/services/groups/group_links/create_service.rb
@@ -5,7 +5,7 @@ module Groups
class CreateService < BaseService
def execute(shared_group)
unless group && shared_group &&
- can?(current_user, :admin_group, shared_group) &&
+ can?(current_user, :admin_group_member, shared_group) &&
can?(current_user, :read_group, group)
return error('Not Found', 404)
end
diff --git a/app/services/groups/group_links/destroy_service.rb b/app/services/groups/group_links/destroy_service.rb
index 6835b6c4637..b0d496ae78c 100644
--- a/app/services/groups/group_links/destroy_service.rb
+++ b/app/services/groups/group_links/destroy_service.rb
@@ -3,7 +3,11 @@
module Groups
module GroupLinks
class DestroyService < BaseService
- def execute(one_or_more_links)
+ def execute(one_or_more_links, skip_authorization: false)
+ unless skip_authorization || group && can?(current_user, :admin_group_member, group)
+ return error('Not Found', 404)
+ end
+
links = Array(one_or_more_links)
if GroupGroupLink.delete(links)
diff --git a/app/services/groups/import_export/export_service.rb b/app/services/groups/import_export/export_service.rb
index 0f2e3bb65f9..abac0ffc5d9 100644
--- a/app/services/groups/import_export/export_service.rb
+++ b/app/services/groups/import_export/export_service.rb
@@ -4,10 +4,11 @@ module Groups
module ImportExport
class ExportService
def initialize(group:, user:, params: {})
- @group = group
+ @group = group
@current_user = user
- @params = params
- @shared = @params[:shared] || Gitlab::ImportExport::Shared.new(@group)
+ @params = params
+ @shared = @params[:shared] || Gitlab::ImportExport::Shared.new(@group)
+ @logger = Gitlab::Export::Logger.build
end
def async_execute
@@ -21,7 +22,7 @@ module Groups
save!
ensure
- cleanup
+ remove_base_tmp_dir
end
private
@@ -80,8 +81,8 @@ module Groups
Gitlab::ImportExport::Saver.new(exportable: @group, shared: @shared)
end
- def cleanup
- FileUtils.rm_rf(shared.archive_path) if shared&.archive_path
+ def remove_base_tmp_dir
+ FileUtils.rm_rf(shared.base_path) if shared&.base_path
end
def notify_error!
@@ -91,21 +92,21 @@ module Groups
end
def notify_success
- @shared.logger.info(
- group_id: @group.id,
- group_name: @group.name,
- message: 'Group Import/Export: Export succeeded'
+ @logger.info(
+ message: 'Group Export succeeded',
+ group_id: @group.id,
+ group_name: @group.name
)
notification_service.group_was_exported(@group, @current_user)
end
def notify_error
- @shared.logger.error(
- group_id: @group.id,
+ @logger.error(
+ message: 'Group Export failed',
+ group_id: @group.id,
group_name: @group.name,
- error: @shared.errors.join(', '),
- message: 'Group Import/Export: Export failed'
+ errors: @shared.errors.join(', ')
)
notification_service.group_was_not_exported(@group, @current_user, @shared.errors)
diff --git a/app/services/groups/import_export/import_service.rb b/app/services/groups/import_export/import_service.rb
index 6f692c98c38..a5c776f8fc2 100644
--- a/app/services/groups/import_export/import_service.rb
+++ b/app/services/groups/import_export/import_service.rb
@@ -9,6 +9,20 @@ module Groups
@group = group
@current_user = user
@shared = Gitlab::ImportExport::Shared.new(@group)
+ @logger = Gitlab::Import::Logger.build
+ end
+
+ def async_execute
+ group_import_state = GroupImportState.safe_find_or_create_by!(group: group)
+ jid = GroupImportWorker.perform_async(current_user.id, group.id)
+
+ if jid.present?
+ group_import_state.update!(jid: jid)
+ else
+ group_import_state.fail_op('Failed to schedule import job')
+
+ false
+ end
end
def execute
@@ -21,6 +35,7 @@ module Groups
end
ensure
+ remove_base_tmp_dir
remove_import_file
end
@@ -77,7 +92,7 @@ module Groups
end
def notify_success
- @shared.logger.info(
+ @logger.info(
group_id: @group.id,
group_name: @group.name,
message: 'Group Import/Export: Import succeeded'
@@ -85,7 +100,7 @@ module Groups
end
def notify_error
- @shared.logger.error(
+ @logger.error(
group_id: @group.id,
group_name: @group.name,
message: "Group Import/Export: Errors occurred, see '#{Gitlab::ErrorTracking::Logger.file_name}' for details"
@@ -97,6 +112,10 @@ module Groups
raise Gitlab::ImportExport::Error.new(@shared.errors.to_sentence)
end
+
+ def remove_base_tmp_dir
+ FileUtils.rm_rf(@shared.base_path)
+ end
end
end
end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index fe3ab884302..fbbf4ce8baf 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -45,6 +45,7 @@ module Groups
raise_transfer_error(:invalid_policies) unless valid_policies?
raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
raise_transfer_error(:group_contains_images) if group_projects_contain_registry_images?
+ raise_transfer_error(:cannot_transfer_to_subgroup) if transfer_to_subgroup?
end
def group_is_already_root?
@@ -55,6 +56,11 @@ module Groups
@new_parent_group && @new_parent_group.id == @group.parent_id
end
+ def transfer_to_subgroup?
+ @new_parent_group && \
+ @group.self_and_descendants.pluck_primary_key.include?(@new_parent_group.id)
+ end
+
def valid_policies?
return false unless can?(current_user, :admin_group, @group)
@@ -82,6 +88,7 @@ module Groups
end
@group.parent = @new_parent_group
+ @group.clear_memoization(:self_and_ancestors_ids)
@group.save!
end
@@ -125,7 +132,8 @@ module Groups
group_is_already_root: s_('TransferGroup|Group is already a root group.'),
same_parent_as_current: s_('TransferGroup|Group is already associated to the parent group.'),
invalid_policies: s_("TransferGroup|You don't have enough permissions."),
- group_contains_images: s_('TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again.')
+ group_contains_images: s_('TransferGroup|Cannot update the path because there are projects under this group that contain Docker images in their Container Registry. Please remove the images from your projects first and try again.'),
+ cannot_transfer_to_subgroup: s_('TransferGroup|Cannot transfer group to one of its subgroup.')
}.freeze
end
end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
index 3c57fada677..0cf17568c78 100644
--- a/app/services/import/github_service.rb
+++ b/app/services/import/github_service.rb
@@ -10,15 +10,26 @@ module Import
return error(_('This namespace has already been taken! Please choose another one.'), :unprocessable_entity)
end
- project = Gitlab::LegacyGithubImport::ProjectCreator
- .new(repo, project_name, target_namespace, current_user, access_params, type: provider)
- .execute(extra_project_attrs)
+ project = create_project(access_params, provider)
if project.persisted?
success(project)
else
error(project_save_error(project), :unprocessable_entity)
end
+ rescue Octokit::Error => e
+ log_error(e)
+ end
+
+ def create_project(access_params, provider)
+ Gitlab::LegacyGithubImport::ProjectCreator.new(
+ repo,
+ project_name,
+ target_namespace,
+ current_user,
+ access_params,
+ type: provider
+ ).execute(extra_project_attrs)
end
def repo
@@ -44,6 +55,18 @@ module Import
def authorized?
can?(current_user, :create_projects, target_namespace)
end
+
+ private
+
+ def log_error(exception)
+ Gitlab::Import::Logger.error(
+ message: 'Import failed due to a GitHub error',
+ status: exception.response_status,
+ error: exception.response_body
+ )
+
+ error(_('Import failed due to a GitHub error: %{original}') % { original: exception.response_body }, :unprocessable_entity)
+ end
end
end
diff --git a/app/services/integrations/test/base_service.rb b/app/services/integrations/test/base_service.rb
new file mode 100644
index 00000000000..a8a027092d5
--- /dev/null
+++ b/app/services/integrations/test/base_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Integrations
+ module Test
+ class BaseService
+ include BaseServiceUtility
+
+ attr_accessor :integration, :current_user, :event
+
+ # @param integration [Service] The external service that will be called
+ # @param current_user [User] The user calling the service
+ # @param event [String/nil] The event that triggered this
+ def initialize(integration, current_user, event = nil)
+ @integration = integration
+ @current_user = current_user
+ @event = event
+ end
+
+ def execute
+ if event && (integration.supported_events.exclude?(event) || data.blank?)
+ return error('Testing not available for this event')
+ end
+
+ return error(data[:error]) if data[:error].present?
+
+ integration.test(data)
+ end
+
+ private
+
+ def data
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/app/services/integrations/test/project_service.rb b/app/services/integrations/test/project_service.rb
new file mode 100644
index 00000000000..941d70c2cc4
--- /dev/null
+++ b/app/services/integrations/test/project_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Integrations
+ module Test
+ class ProjectService < Integrations::Test::BaseService
+ include Integrations::ProjectTestData
+ include Gitlab::Utils::StrongMemoize
+
+ def project
+ strong_memoize(:project) do
+ integration.project
+ end
+ end
+
+ private
+
+ def data
+ strong_memoize(:data) do
+ next pipeline_events_data if integration.is_a?(::PipelinesEmailService)
+
+ case event
+ when 'push', 'tag_push'
+ push_events_data
+ when 'note', 'confidential_note'
+ note_events_data
+ when 'issue', 'confidential_issue'
+ issues_events_data
+ when 'merge_request'
+ merge_requests_events_data
+ when 'job'
+ job_events_data
+ when 'pipeline'
+ pipeline_events_data
+ when 'wiki_page'
+ wiki_page_events_data
+ when 'deployment'
+ deployment_events_data
+ else
+ push_events_data
+ end
+ end
+ end
+ end
+ end
+end
+
+Integrations::Test::ProjectService.prepend_if_ee('::EE::Integrations::Test::ProjectService')
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
index 2cd0e1e992d..2902385da4a 100644
--- a/app/services/issuable/bulk_update_service.rb
+++ b/app/services/issuable/bulk_update_service.rb
@@ -17,9 +17,8 @@ module Issuable
ids = params.delete(:issuable_ids).split(",")
items = find_issuables(parent, model_class, ids)
- permitted_attrs(type).each do |key|
- params.delete(key) unless params[key].present?
- end
+ params.slice!(*permitted_attrs(type))
+ params.delete_if { |k, v| v.blank? }
if params[:assignee_ids] == [IssuableFinder::Params::NONE.to_s]
params[:assignee_ids] = []
@@ -40,9 +39,13 @@ module Issuable
private
def permitted_attrs(type)
- attrs = %i(state_event milestone_id assignee_id assignee_ids add_label_ids remove_label_ids subscription_event)
+ attrs = %i(state_event milestone_id add_label_ids remove_label_ids subscription_event)
+
+ issuable_specific_attrs(type, attrs)
+ end
- if type == 'issue'
+ def issuable_specific_attrs(type, attrs)
+ if type == 'issue' || type == 'merge_request'
attrs.push(:assignee_ids)
else
attrs.push(:assignee_id)
diff --git a/app/services/issuable/clone/attributes_rewriter.rb b/app/services/issuable/clone/attributes_rewriter.rb
index a78e191c85f..b185ab592ff 100644
--- a/app/services/issuable/clone/attributes_rewriter.rb
+++ b/app/services/issuable/clone/attributes_rewriter.rb
@@ -105,7 +105,7 @@ module Issuable
yield(event)
end.compact
- Gitlab::Database.bulk_insert(table_name, events)
+ Gitlab::Database.bulk_insert(table_name, events) # rubocop:disable Gitlab/BulkInsert
end
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 18062bd60da..38b10996f44 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -129,15 +129,11 @@ class IssuableBaseService < BaseService
add_label_ids = attributes.delete(:add_label_ids)
remove_label_ids = attributes.delete(:remove_label_ids)
- new_label_ids = existing_label_ids || label_ids || []
+ new_label_ids = label_ids || existing_label_ids || []
new_label_ids |= extra_label_ids
- if add_label_ids.blank? && remove_label_ids.blank?
- new_label_ids = label_ids if label_ids
- else
- new_label_ids |= add_label_ids if add_label_ids
- new_label_ids -= remove_label_ids if remove_label_ids
- end
+ new_label_ids |= add_label_ids if add_label_ids
+ new_label_ids -= remove_label_ids if remove_label_ids
new_label_ids.uniq
end
@@ -350,7 +346,7 @@ class IssuableBaseService < BaseService
todo_service.mark_todo(issuable, current_user)
when 'done'
todo = TodosFinder.new(current_user).find_by(target: issuable)
- todo_service.mark_todos_as_done_by_ids(todo, current_user) if todo
+ todo_service.resolve_todo(todo, current_user) if todo
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 7869509aa9c..c0194f5b847 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -15,7 +15,7 @@ module Issues
end
def before_create(issue)
- spam_check(issue, current_user)
+ spam_check(issue, current_user, action: :create)
issue.move_to_end
# current_user (defined in BaseService) is not available within run_after_commit block
@@ -38,9 +38,8 @@ module Issues
return if discussions_to_resolve.empty?
Discussions::ResolveService.new(project, current_user,
- merge_request: merge_request_to_resolve_discussions_of,
- follow_up_issue: issue)
- .execute(discussions_to_resolve)
+ one_or_more_discussions: discussions_to_resolve,
+ follow_up_issue: issue).execute
end
private
diff --git a/app/services/issues/import_csv_service.rb b/app/services/issues/import_csv_service.rb
index c01db5fcfe6..60790ba3547 100644
--- a/app/services/issues/import_csv_service.rb
+++ b/app/services/issues/import_csv_service.rb
@@ -46,7 +46,7 @@ module Issues
end
def email_results_to_user
- Notify.import_issues_csv_email(@user.id, @project.id, @results).deliver_now
+ Notify.import_issues_csv_email(@user.id, @project.id, @results).deliver_later
end
def detect_col_sep(header)
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index ee1a22634af..8d22f0edcdd 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -18,7 +18,7 @@ module Issues
end
def before_update(issue, skip_spam_check: false)
- spam_check(issue, current_user) unless skip_spam_check
+ spam_check(issue, current_user, action: :update) unless skip_spam_check
end
def after_update(issue)
@@ -32,7 +32,7 @@ module Issues
old_assignees = old_associations.fetch(:assignees, [])
if has_changes?(issue, old_labels: old_labels, old_assignees: old_assignees)
- todo_service.mark_pending_todos_as_done(issue, current_user)
+ todo_service.resolve_todos_for_target(issue, current_user)
end
if issue.previous_changes.include?('title') ||
@@ -68,7 +68,7 @@ module Issues
end
def handle_task_changes(issuable)
- todo_service.mark_pending_todos_as_done(issuable, current_user)
+ todo_service.resolve_todos_for_target(issuable, current_user)
todo_service.update_issue(issuable, current_user)
end
diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb
new file mode 100644
index 00000000000..7521c7610cb
--- /dev/null
+++ b/app/services/jira/requests/base.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Jira
+ module Requests
+ class Base
+ include ProjectServicesLoggable
+
+ PER_PAGE = 50
+
+ attr_reader :jira_service, :project, :limit, :start_at, :query
+
+ def initialize(jira_service, limit: PER_PAGE, start_at: 0, query: nil)
+ @project = jira_service&.project
+ @jira_service = jira_service
+
+ @limit = limit
+ @start_at = start_at
+ @query = query
+ end
+
+ def execute
+ return ServiceResponse.error(message: _('Jira service not configured.')) unless jira_service&.active?
+ return ServiceResponse.success(payload: empty_payload) if limit.to_i <= 0
+
+ request
+ end
+
+ private
+
+ def client
+ @client ||= jira_service.client
+ end
+
+ def request
+ response = client.get(url)
+ build_service_response(response)
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => error
+ error_message = "Jira request error: #{error.message}"
+ log_error("Error sending message", client_url: client.options[:site], error: error_message)
+ ServiceResponse.error(message: error_message)
+ end
+
+ def url
+ raise NotImplementedError
+ end
+
+ def build_service_response(response)
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/app/services/jira/requests/projects.rb b/app/services/jira/requests/projects.rb
new file mode 100644
index 00000000000..da464503211
--- /dev/null
+++ b/app/services/jira/requests/projects.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Jira
+ module Requests
+ class Projects < Base
+ extend ::Gitlab::Utils::Override
+
+ private
+
+ override :url
+ def url
+ '/rest/api/2/project/search?query=%{query}&maxResults=%{limit}&startAt=%{start_at}' %
+ { query: CGI.escape(query.to_s), limit: limit.to_i, start_at: start_at.to_i }
+ end
+
+ override :build_service_response
+ def build_service_response(response)
+ return ServiceResponse.success(payload: empty_payload) unless response['values'].present?
+
+ ServiceResponse.success(payload: { projects: map_projects(response), is_last: response['isLast'] })
+ end
+
+ def map_projects(response)
+ response['values'].map { |v| JIRA::Resource::Project.build(client, v) }
+ end
+
+ def empty_payload
+ { projects: [], is_last: true }
+ end
+ end
+ end
+end
diff --git a/app/services/jira_import/start_import_service.rb b/app/services/jira_import/start_import_service.rb
index 59fd463022f..a06cc6df719 100644
--- a/app/services/jira_import/start_import_service.rb
+++ b/app/services/jira_import/start_import_service.rb
@@ -28,8 +28,8 @@ module JiraImport
rescue => ex
# in case project.save! raises an erorr
Gitlab::ErrorTracking.track_exception(ex, project_id: project.id)
+ jira_import&.do_fail!(error_message: ex.message)
build_error_response(ex.message)
- jira_import.do_fail!
end
def build_jira_import
@@ -62,7 +62,7 @@ module JiraImport
end
def validate
- project.validate_jira_import_settings!(user: user)
+ Gitlab::JiraImport.validate_project_settings!(project, user: user)
return build_error_response(_('Unable to find Jira project to import data from.')) if jira_project_key.blank?
return build_error_response(_('Jira import is already running.')) if import_in_progress?
diff --git a/app/services/jira_import/users_importer.rb b/app/services/jira_import/users_importer.rb
new file mode 100644
index 00000000000..579d3675073
--- /dev/null
+++ b/app/services/jira_import/users_importer.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module JiraImport
+ class UsersImporter
+ attr_reader :user, :project, :start_at, :result
+
+ MAX_USERS = 50
+
+ def initialize(user, project, start_at)
+ @project = project
+ @start_at = start_at
+ @user = user
+ end
+
+ def execute
+ Gitlab::JiraImport.validate_project_settings!(project, user: user)
+
+ return ServiceResponse.success(payload: nil) if users.blank?
+
+ result = UsersMapper.new(project, users).execute
+ ServiceResponse.success(payload: result)
+ rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => error
+ Gitlab::ErrorTracking.track_exception(error, project_id: project.id, request: url)
+ ServiceResponse.error(message: "There was an error when communicating to Jira: #{error.message}")
+ rescue Projects::ImportService::Error => error
+ ServiceResponse.error(message: error.message)
+ end
+
+ private
+
+ def users
+ @users ||= client.get(url)
+ end
+
+ def url
+ "/rest/api/2/users?maxResults=#{MAX_USERS}&startAt=#{start_at.to_i}"
+ end
+
+ def client
+ @client ||= project.jira_service.client
+ end
+ end
+end
diff --git a/app/services/jira_import/users_mapper.rb b/app/services/jira_import/users_mapper.rb
new file mode 100644
index 00000000000..31a3f721556
--- /dev/null
+++ b/app/services/jira_import/users_mapper.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module JiraImport
+ class UsersMapper
+ attr_reader :project, :jira_users
+
+ def initialize(project, jira_users)
+ @project = project
+ @jira_users = jira_users
+ end
+
+ def execute
+ jira_users.to_a.map do |jira_user|
+ {
+ jira_account_id: jira_user['accountId'],
+ jira_display_name: jira_user['displayName'],
+ jira_email: jira_user['emailAddress'],
+ gitlab_id: match_user(jira_user)
+ }
+ end
+ end
+
+ private
+
+ # TODO: Matching user by email and displayName will be done as the part
+ # of follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/219023
+ def match_user(jira_user)
+ nil
+ end
+ end
+end
diff --git a/app/services/keys/create_service.rb b/app/services/keys/create_service.rb
index 32c4ab645df..c256de7b35d 100644
--- a/app/services/keys/create_service.rb
+++ b/app/services/keys/create_service.rb
@@ -2,6 +2,14 @@
module Keys
class CreateService < ::Keys::BaseService
+ attr_accessor :current_user
+
+ def initialize(current_user, params = {})
+ @current_user, @params = current_user, params
+ @ip_address = @params.delete(:ip_address)
+ @user = params.delete(:user) || current_user
+ end
+
def execute
key = user.keys.create(params)
notification_service.new_key(key) if key.persisted?
diff --git a/app/services/labels/available_labels_service.rb b/app/services/labels/available_labels_service.rb
index 8886e58d6ef..979964e09fd 100644
--- a/app/services/labels/available_labels_service.rb
+++ b/app/services/labels/available_labels_service.rb
@@ -30,11 +30,13 @@ module Labels
end
def filter_labels_ids_in_param(key)
- return [] if params[key].to_a.empty?
+ ids = params[key].to_a
+ return [] if ids.empty?
# rubocop:disable CodeReuse/ActiveRecord
- available_labels.by_ids(params[key]).pluck(:id)
+ existing_ids = available_labels.by_ids(ids).pluck(:id)
# rubocop:enable CodeReuse/ActiveRecord
+ ids.map(&:to_i) & existing_ids
end
private
diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb
index c032985be42..a5b30e29e55 100644
--- a/app/services/labels/create_service.rb
+++ b/app/services/labels/create_service.rb
@@ -20,7 +20,7 @@ module Labels
label.save
label
else
- Rails.logger.warn("target_params should contain :project or :group or :template, actual value: #{target_params}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.warn("target_params should contain :project or :group or :template, actual value: #{target_params}")
end
end
end
diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb
index cc91fd4b4d2..9ed10f6a11b 100644
--- a/app/services/labels/promote_service.rb
+++ b/app/services/labels/promote_service.rb
@@ -90,7 +90,7 @@ module Labels
# rubocop: disable CodeReuse/ActiveRecord
def destroy_project_labels(label_ids)
- Label.where(id: label_ids).destroy_all # rubocop: disable DestroyAll
+ Label.where(id: label_ids).destroy_all # rubocop: disable Cop/DestroyAll
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 31097b9151a..8d57a76f7d0 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -121,12 +121,12 @@ module MergeRequests
end
def handle_merge_error(log_message:, save_message_on_model: false)
- Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.error("MergeService ERROR: #{merge_request_info} - #{log_message}")
@merge_request.update(merge_error: log_message) if save_message_on_model
end
def log_info(message)
- @logger ||= Rails.logger # rubocop:disable Gitlab/RailsLogger
+ @logger ||= Gitlab::AppLogger
@logger.info("#{merge_request_info} - #{message}")
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 2d33e87bf4b..561695baeab 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -27,7 +27,7 @@ module MergeRequests
old_assignees = old_associations.fetch(:assignees, [])
if has_changes?(merge_request, old_labels: old_labels, old_assignees: old_assignees)
- todo_service.mark_pending_todos_as_done(merge_request, current_user)
+ todo_service.resolve_todos_for_target(merge_request, current_user)
end
if merge_request.previous_changes.include?('title') ||
@@ -73,7 +73,7 @@ module MergeRequests
end
def handle_task_changes(merge_request)
- todo_service.mark_pending_todos_as_done(merge_request, current_user)
+ todo_service.resolve_todos_for_target(merge_request, current_user)
todo_service.update_merge_request(merge_request, current_user)
end
diff --git a/app/services/metrics/dashboard/base_service.rb b/app/services/metrics/dashboard/base_service.rb
index 514793694ba..c2a0f22e73e 100644
--- a/app/services/metrics/dashboard/base_service.rb
+++ b/app/services/metrics/dashboard/base_service.rb
@@ -13,7 +13,8 @@ module Metrics
STAGES::EndpointInserter,
STAGES::PanelIdsInserter,
STAGES::Sorter,
- STAGES::AlertsInserter
+ STAGES::AlertsInserter,
+ STAGES::UrlValidator
].freeze
def get_dashboard
diff --git a/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb b/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb
index d97668d1c7c..8599c23c206 100644
--- a/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb
+++ b/app/services/metrics/dashboard/self_monitoring_dashboard_service.rb
@@ -6,7 +6,7 @@ module Metrics
module Dashboard
class SelfMonitoringDashboardService < ::Metrics::Dashboard::PredefinedDashboardService
DASHBOARD_PATH = 'config/prometheus/self_monitoring_default.yml'
- DASHBOARD_NAME = 'Default'
+ DASHBOARD_NAME = N_('Default dashboard')
SEQUENCE = [
STAGES::CustomMetricsInserter,
@@ -23,7 +23,7 @@ module Metrics
def all_dashboard_paths(_project)
[{
path: DASHBOARD_PATH,
- display_name: DASHBOARD_NAME,
+ display_name: _(DASHBOARD_NAME),
default: true,
system_dashboard: false
}]
diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb
index ed4b78ba159..db5599b4def 100644
--- a/app/services/metrics/dashboard/system_dashboard_service.rb
+++ b/app/services/metrics/dashboard/system_dashboard_service.rb
@@ -6,7 +6,7 @@ module Metrics
module Dashboard
class SystemDashboardService < ::Metrics::Dashboard::PredefinedDashboardService
DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
- DASHBOARD_NAME = 'Default'
+ DASHBOARD_NAME = N_('Default dashboard')
SEQUENCE = [
STAGES::CommonMetricsInserter,
@@ -22,7 +22,7 @@ module Metrics
def all_dashboard_paths(_project)
[{
path: DASHBOARD_PATH,
- display_name: DASHBOARD_NAME,
+ display_name: _(DASHBOARD_NAME),
default: true,
system_dashboard: true
}]
diff --git a/app/services/milestones/promote_service.rb b/app/services/milestones/promote_service.rb
index 80e6456f729..2431318cbb2 100644
--- a/app/services/milestones/promote_service.rb
+++ b/app/services/milestones/promote_service.rb
@@ -76,7 +76,7 @@ module Milestones
# rubocop: disable CodeReuse/ActiveRecord
def destroy_old_milestones(milestone)
- Milestone.where(id: milestone_ids_for_merge(milestone)).destroy_all # rubocop: disable DestroyAll
+ Milestone.where(id: milestone_ids_for_merge(milestone)).destroy_all # rubocop: disable Cop/DestroyAll
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/namespaces/check_storage_size_service.rb b/app/services/namespaces/check_storage_size_service.rb
index b3cf17681ee..57d2645a0c8 100644
--- a/app/services/namespaces/check_storage_size_service.rb
+++ b/app/services/namespaces/check_storage_size_service.rb
@@ -41,7 +41,8 @@ module Namespaces
{
explanation_message: explanation_message,
usage_message: usage_message,
- alert_level: alert_level
+ alert_level: alert_level,
+ root_namespace: root_namespace
}
end
@@ -50,7 +51,7 @@ module Namespaces
end
def usage_message
- s_("You reached %{usage_in_percent} of %{namespace_name}'s capacity (%{used_storage} of %{storage_limit})" % current_usage_params)
+ s_("You reached %{usage_in_percent} of %{namespace_name}'s storage capacity (%{used_storage} of %{storage_limit})" % current_usage_params)
end
def alert_level
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 6c1f52ec866..935dbfb72dd 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -88,9 +88,11 @@ module Notes
end
end
- # EE::Notes::CreateService would override this method
def quick_action_options
- { merge_request_diff_head_sha: params[:merge_request_diff_head_sha] }
+ {
+ merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
+ review_id: params[:review_id]
+ }
end
def tracking_data_for(note)
@@ -103,5 +105,3 @@ module Notes
end
end
end
-
-Notes::CreateService.prepend_if_ee('EE::Notes::CreateService')
diff --git a/app/services/notes/post_process_service.rb b/app/services/notes/post_process_service.rb
index bc86118a150..0e455c641ce 100644
--- a/app/services/notes/post_process_service.rb
+++ b/app/services/notes/post_process_service.rb
@@ -36,7 +36,7 @@ module Notes
return unless @note.project
note_data = hook_data
- hooks_scope = @note.confidential? ? :confidential_note_hooks : :note_hooks
+ hooks_scope = @note.confidential?(include_noteable: true) ? :confidential_note_hooks : :note_hooks
@note.project.execute_hooks(note_data, hooks_scope)
@note.project.execute_services(note_data, hooks_scope)
diff --git a/app/services/notification_recipients/build_service.rb b/app/services/notification_recipients/build_service.rb
index df807f11e1b..0fe0d26d7b2 100644
--- a/app/services/notification_recipients/build_service.rb
+++ b/app/services/notification_recipients/build_service.rb
@@ -32,7 +32,9 @@ module NotificationRecipients
def self.build_new_release_recipients(*args)
::NotificationRecipients::Builder::NewRelease.new(*args).notification_recipients
end
+
+ def self.build_new_review_recipients(*args)
+ ::NotificationRecipients::Builder::NewReview.new(*args).notification_recipients
+ end
end
end
-
-NotificationRecipients::BuildService.prepend_if_ee('EE::NotificationRecipients::BuildService')
diff --git a/app/services/notification_recipients/builder/new_review.rb b/app/services/notification_recipients/builder/new_review.rb
new file mode 100644
index 00000000000..3b1296f6967
--- /dev/null
+++ b/app/services/notification_recipients/builder/new_review.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module NotificationRecipients
+ module Builder
+ class NewReview < Base
+ attr_reader :review
+ def initialize(review)
+ @review = review
+ end
+
+ def target
+ review.merge_request
+ end
+
+ def project
+ review.project
+ end
+
+ def group
+ project.group
+ end
+
+ def build!
+ add_participants(review.author)
+ add_mentions(review.author, target: review)
+ add_project_watchers
+ add_custom_notifications
+ add_subscribed_users
+ end
+
+ # A new review is a batch of new notes
+ # therefore new_note subscribers should also
+ # receive incoming new reviews
+ def custom_action
+ :new_note
+ end
+
+ def acting_user
+ review.author
+ end
+ end
+ end
+end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 4c1db03fab8..73e60ac8420 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -68,10 +68,10 @@ class NotificationService
# Notify a user when a previously unknown IP or device is used to
# sign in to their account
- def unknown_sign_in(user, ip)
+ def unknown_sign_in(user, ip, time)
return unless user.can?(:receive_notifications)
- mailer.unknown_sign_in_email(user, ip).deliver_later
+ mailer.unknown_sign_in_email(user, ip, time).deliver_later
end
# When create an issue we should send an email to:
@@ -447,14 +447,14 @@ class NotificationService
# from the PipelinesEmailService integration.
return if pipeline.project.emails_disabled?
- ref_status ||= pipeline.status
- email_template = "pipeline_#{ref_status}_email"
+ status = pipeline_notification_status(ref_status, pipeline)
+ email_template = "pipeline_#{status}_email"
return unless mailer.respond_to?(email_template)
recipients ||= notifiable_users(
[pipeline.user], :watch,
- custom_action: :"#{ref_status}_pipeline",
+ custom_action: :"#{status}_pipeline",
target: pipeline
).map do |user|
user.notification_email_for(pipeline.project.group)
@@ -557,6 +557,15 @@ class NotificationService
mailer.group_was_not_exported_email(current_user, group, errors).deliver_later
end
+ # Notify users on new review in system
+ def new_review(review)
+ recipients = NotificationRecipients::BuildService.build_new_review_recipients(review)
+
+ recipients.each do |recipient|
+ mailer.new_review_email(recipient.user.id, review.id).deliver_later
+ end
+ end
+
protected
def new_resource_email(target, method)
@@ -652,6 +661,16 @@ class NotificationService
private
+ def pipeline_notification_status(ref_status, pipeline)
+ if Ci::Ref.failing_state?(ref_status)
+ 'failed'
+ elsif ref_status
+ ref_status
+ else
+ pipeline.status
+ end
+ end
+
def owners_and_maintainers_without_invites(project)
recipients = project.members.active_without_invites_and_requests.owners_and_maintainers
diff --git a/app/services/pages/delete_service.rb b/app/services/pages/delete_service.rb
index d4de6bb750d..7408943e78c 100644
--- a/app/services/pages/delete_service.rb
+++ b/app/services/pages/delete_service.rb
@@ -4,7 +4,7 @@ module Pages
class DeleteService < BaseService
def execute
project.remove_pages
- project.pages_domains.destroy_all # rubocop: disable DestroyAll
+ project.pages_domains.destroy_all # rubocop: disable Cop/DestroyAll
end
end
end
diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb
index ee2dde8aa7f..fad2290a47b 100644
--- a/app/services/projects/after_import_service.rb
+++ b/app/services/projects/after_import_service.rb
@@ -22,8 +22,12 @@ module Projects
# causing GC to run every time.
service.increment!
rescue Projects::HousekeepingService::LeaseTaken => e
- Rails.logger.info( # rubocop:disable Gitlab/RailsLogger
- "Could not perform housekeeping for project #{@project.full_path} (#{@project.id}): #{e}")
+ Gitlab::Import::Logger.info(
+ message: 'Project housekeeping failed',
+ project_full_path: @project.full_path,
+ project_id: @project.id,
+ error: e.message
+ )
end
private
diff --git a/app/services/projects/alerting/notify_service.rb b/app/services/projects/alerting/notify_service.rb
index 76c89e85f17..86c408aeec8 100644
--- a/app/services/projects/alerting/notify_service.rb
+++ b/app/services/projects/alerting/notify_service.rb
@@ -10,7 +10,7 @@ module Projects
return forbidden unless alerts_service_activated?
return unauthorized unless valid_token?(token)
- alert = create_alert
+ alert = process_alert
return bad_request unless alert.persisted?
process_incident_issues(alert) if process_issues?
@@ -26,11 +26,36 @@ module Projects
delegate :alerts_service, :alerts_service_activated?, to: :project
def am_alert_params
- Gitlab::AlertManagement::AlertParams.from_generic_alert(project: project, payload: params.to_h)
+ strong_memoize(:am_alert_params) do
+ Gitlab::AlertManagement::AlertParams.from_generic_alert(project: project, payload: params.to_h)
+ end
+ end
+
+ def process_alert
+ existing_alert = find_alert_by_fingerprint(am_alert_params[:fingerprint])
+
+ if existing_alert
+ process_existing_alert(existing_alert)
+ else
+ create_alert
+ end
+ end
+
+ def process_existing_alert(alert)
+ alert.register_new_event!
end
def create_alert
- AlertManagement::Alert.create(am_alert_params)
+ alert = AlertManagement::Alert.create(am_alert_params)
+ alert.execute_services if alert.persisted?
+
+ alert
+ end
+
+ def find_alert_by_fingerprint(fingerprint)
+ return unless fingerprint
+
+ AlertManagement::Alert.for_fingerprint(project, fingerprint).first
end
def send_email?
@@ -38,6 +63,8 @@ module Projects
end
def process_incident_issues(alert)
+ return if alert.issue
+
IncidentManagement::ProcessAlertWorker
.perform_async(project.id, parsed_payload, alert.id)
end
diff --git a/app/services/projects/container_repository/cleanup_tags_service.rb b/app/services/projects/container_repository/cleanup_tags_service.rb
index b53a9c1561e..c5809c11ea9 100644
--- a/app/services/projects/container_repository/cleanup_tags_service.rb
+++ b/app/services/projects/container_repository/cleanup_tags_service.rb
@@ -6,6 +6,7 @@ module Projects
def execute(container_repository)
return error('feature disabled') unless can_use?
return error('access denied') unless can_destroy?
+ return error('invalid regex') unless valid_regex?
tags = container_repository.tags
tags = without_latest(tags)
@@ -76,6 +77,17 @@ module Projects
def can_use?
Feature.enabled?(:container_registry_cleanup, project, default_enabled: true)
end
+
+ def valid_regex?
+ %w(name_regex_delete name_regex name_regex_keep).each do |param_name|
+ regex = params[param_name]
+ Gitlab::UntrustedRegexp.new(regex) unless regex.blank?
+ end
+ true
+ rescue RegexpError => e
+ Gitlab::ErrorTracking.log_exception(e, project_id: project.id)
+ false
+ end
end
end
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 3233d1799b8..bffd443c49f 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -150,7 +150,7 @@ module Projects
if @project.save
unless @project.gitlab_project_import?
- create_services_from_active_templates(@project)
+ create_services_from_active_instances_or_templates(@project)
@project.create_labels
end
@@ -166,7 +166,7 @@ module Projects
log_message = message.dup
log_message << " Project ID: #{@project.id}" if @project&.id
- Rails.logger.error(log_message) # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.error(log_message)
if @project && @project.persisted? && @project.import_state
@project.import_state.mark_as_failed(message)
@@ -175,15 +175,6 @@ module Projects
@project
end
- # rubocop: disable CodeReuse/ActiveRecord
- def create_services_from_active_templates(project)
- Service.where(template: true, active: true).each do |template|
- service = Service.build_from_template(project.id, template)
- service.save!
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
def create_prometheus_service
service = @project.find_or_initialize_service(::PrometheusService.to_param)
@@ -225,6 +216,15 @@ module Projects
private
+ # rubocop: disable CodeReuse/ActiveRecord
+ def create_services_from_active_instances_or_templates(project)
+ Service.active.where(instance: true).or(Service.active.where(template: true)).group_by(&:type).each do |type, records|
+ service = records.find(&:instance?) || records.find(&:template?)
+ Service.build_from_integration(project.id, service).save!
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
def project_namespace
@project_namespace ||= Namespace.find_by_id(@params[:namespace_id]) || current_user.namespace
end
@@ -249,9 +249,7 @@ module Projects
end
end
-# rubocop: disable Cop/InjectEnterpriseEditionModule
Projects::CreateService.prepend_if_ee('EE::Projects::CreateService')
-# rubocop: enable Cop/InjectEnterpriseEditionModule
# Measurable should be at the bottom of the ancestor chain, so it will measure execution of EE::Projects::CreateService as well
Projects::CreateService.prepend(Measurable)
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index fd1366d2c4a..2e949f2fc55 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -64,7 +64,7 @@ module Projects
end
def remove_snippets
- response = Snippets::BulkDestroyService.new(current_user, project.snippets).execute
+ response = ::Snippets::BulkDestroyService.new(current_user, project.snippets).execute
response.success?
end
diff --git a/app/services/projects/detect_repository_languages_service.rb b/app/services/projects/detect_repository_languages_service.rb
index 942cd8162e4..c57773c3302 100644
--- a/app/services/projects/detect_repository_languages_service.rb
+++ b/app/services/projects/detect_repository_languages_service.rb
@@ -21,7 +21,7 @@ module Projects
.update_all(share: update[:share])
end
- Gitlab::Database.bulk_insert(
+ Gitlab::Database.bulk_insert( # rubocop:disable Gitlab/BulkInsert
RepositoryLanguage.table_name,
detection.insertions(matching_programming_languages)
)
diff --git a/app/services/projects/group_links/create_service.rb b/app/services/projects/group_links/create_service.rb
index 241948b335b..2ba3cd6694f 100644
--- a/app/services/projects/group_links/create_service.rb
+++ b/app/services/projects/group_links/create_service.rb
@@ -13,6 +13,7 @@ module Projects
)
if link.save
+ group.refresh_members_authorized_projects
success(link: link)
else
error(link.errors.full_messages.to_sentence, 409)
diff --git a/app/services/projects/group_links/destroy_service.rb b/app/services/projects/group_links/destroy_service.rb
index ea7d05551fd..229191e41f6 100644
--- a/app/services/projects/group_links/destroy_service.rb
+++ b/app/services/projects/group_links/destroy_service.rb
@@ -12,7 +12,9 @@ module Projects
TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, nil, project.id)
end
- group_link.destroy
+ group_link.destroy.tap do |link|
+ link.group.refresh_members_authorized_projects
+ end
end
end
end
diff --git a/app/services/projects/group_links/update_service.rb b/app/services/projects/group_links/update_service.rb
new file mode 100644
index 00000000000..7de4b7a211d
--- /dev/null
+++ b/app/services/projects/group_links/update_service.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Projects
+ module GroupLinks
+ class UpdateService < BaseService
+ def initialize(group_link, user = nil)
+ super(group_link.project, user)
+
+ @group_link = group_link
+ end
+
+ def execute(group_link_params)
+ group_link.update!(group_link_params)
+
+ if requires_authorization_refresh?(group_link_params)
+ group_link.group.refresh_members_authorized_projects
+ end
+ end
+
+ private
+
+ attr_reader :group_link
+
+ def requires_authorization_refresh?(params)
+ params.include?(:group_access)
+ end
+ end
+ end
+end
diff --git a/app/services/projects/hashed_storage/base_attachment_service.rb b/app/services/projects/hashed_storage/base_attachment_service.rb
index a2a7895ba17..d61a2af6c1c 100644
--- a/app/services/projects/hashed_storage/base_attachment_service.rb
+++ b/app/services/projects/hashed_storage/base_attachment_service.rb
@@ -19,7 +19,7 @@ module Projects
def initialize(project:, old_disk_path:, logger: nil)
@project = project
@old_disk_path = old_disk_path
- @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
+ @logger = logger || Gitlab::AppLogger
end
# Return whether this operation was skipped or not
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index 86cb4f35206..031b99753c3 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -9,6 +9,7 @@ module Projects
super
@shared = project.import_export_shared
+ @logger = Gitlab::Export::Logger.build
end
def execute(after_export_strategy = nil)
@@ -115,11 +116,20 @@ module Projects
end
def notify_success
- Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported") # rubocop:disable Gitlab/RailsLogger
+ @logger.info(
+ message: 'Project successfully exported',
+ project_name: project.name,
+ project_id: project.id
+ )
end
def notify_error
- Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{shared.errors.join(', ')}") # rubocop:disable Gitlab/RailsLogger
+ @logger.error(
+ message: 'Project export error',
+ export_errors: shared.errors.join(', '),
+ project_name: project.name,
+ project_id: project.id
+ )
notification_service.project_not_exported(project, current_user, shared.errors)
end
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 449c4c3de6b..b4abb5b6df7 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -149,9 +149,7 @@ module Projects
end
end
-# rubocop: disable Cop/InjectEnterpriseEditionModule
Projects::ImportService.prepend_if_ee('EE::Projects::ImportService')
-# rubocop: enable Cop/InjectEnterpriseEditionModule
# Measurable should be at the bottom of the ancestor chain, so it will measure execution of EE::Projects::ImportService as well
Projects::ImportService.prepend(Measurable)
diff --git a/app/services/projects/lfs_pointers/lfs_link_service.rb b/app/services/projects/lfs_pointers/lfs_link_service.rb
index 39cd553261f..e86106f0a09 100644
--- a/app/services/projects/lfs_pointers/lfs_link_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_link_service.rb
@@ -38,7 +38,7 @@ module Projects
rows = existent_lfs_objects
.not_linked_to_project(project)
.map { |existing_lfs_object| { project_id: project.id, lfs_object_id: existing_lfs_object.id } }
- Gitlab::Database.bulk_insert(:lfs_objects_projects, rows)
+ Gitlab::Database.bulk_insert(:lfs_objects_projects, rows) # rubocop:disable Gitlab/BulkInsert
iterations += 1
linked_existing_objects += existent_lfs_objects.map(&:oid)
diff --git a/app/services/projects/lsif_data_service.rb b/app/services/projects/lsif_data_service.rb
deleted file mode 100644
index 5e7055b3309..00000000000
--- a/app/services/projects/lsif_data_service.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- class LsifDataService
- attr_reader :file, :project, :commit_id, :docs,
- :doc_ranges, :ranges, :def_refs, :hover_refs
-
- CACHE_EXPIRE_IN = 1.hour
-
- def initialize(file, project, commit_id)
- @file = file
- @project = project
- @commit_id = commit_id
-
- fetch_data!
- end
-
- def execute(path)
- doc_id = find_doc_id(docs, path)
- dir_absolute_path = docs[doc_id]&.delete_suffix(path)
-
- doc_ranges[doc_id]&.map do |range_id|
- location, ref_id = ranges[range_id].values_at('loc', 'ref_id')
- line_data, column_data = location
-
- {
- start_line: line_data.first,
- end_line: line_data.last,
- start_char: column_data.first,
- end_char: column_data.last,
- definition_url: definition_url_for(def_refs[ref_id], dir_absolute_path),
- hover: highlighted_hover(hover_refs[ref_id])
- }
- end
- end
-
- private
-
- def fetch_data
- Rails.cache.fetch("project:#{project.id}:lsif:#{commit_id}", expires_in: CACHE_EXPIRE_IN) do
- data = nil
-
- file.open do |stream|
- Zlib::GzipReader.wrap(stream) do |gz_stream|
- data = Gitlab::Json.parse(gz_stream.read)
- end
- end
-
- data
- end
- end
-
- def fetch_data!
- data = fetch_data
-
- @docs = data['docs']
- @doc_ranges = data['doc_ranges']
- @ranges = data['ranges']
- @def_refs = data['def_refs']
- @hover_refs = data['hover_refs']
- end
-
- def find_doc_id(docs, path)
- docs.reduce(nil) do |doc_id, (id, doc_path)|
- next doc_id unless doc_path =~ /#{path}$/
-
- if doc_id.nil? || docs[doc_id].size > doc_path.size
- doc_id = id
- end
-
- doc_id
- end
- end
-
- def definition_url_for(ref_id, dir_absolute_path)
- return unless range = ranges[ref_id]
-
- def_doc_id, location = range.values_at('doc_id', 'loc')
- localized_doc_url = docs[def_doc_id].delete_prefix(dir_absolute_path)
-
- # location is stored as [[start_line, end_line], [start_char, end_char]]
- start_line = location.first.first
-
- line_anchor = "L#{start_line + 1}"
- definition_ref_path = [commit_id, localized_doc_url].join('/')
-
- Gitlab::Routing.url_helpers.project_blob_path(project, definition_ref_path, anchor: line_anchor)
- end
-
- def highlighted_hover(hovers)
- hovers&.map do |hover|
- # Documentation for a method which is added as comments on top of the method
- # is stored as a raw string value in LSIF file
- next { value: hover } unless hover.is_a?(Hash)
-
- value = Gitlab::Highlight.highlight(nil, hover['value'], language: hover['language'])
- { language: hover['language'], value: value }
- end
- end
- end
-end
diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb
index 01419563538..51d84af249e 100644
--- a/app/services/projects/move_deploy_keys_projects_service.rb
+++ b/app/services/projects/move_deploy_keys_projects_service.rb
@@ -28,7 +28,7 @@ module Projects
# rubocop: enable CodeReuse/ActiveRecord
def remove_remaining_deploy_keys_projects
- source_project.deploy_keys_projects.destroy_all # rubocop: disable DestroyAll
+ source_project.deploy_keys_projects.destroy_all # rubocop: disable Cop/DestroyAll
end
end
end
diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb
index 8cc420d7ba7..57a8d3d69c6 100644
--- a/app/services/projects/move_lfs_objects_projects_service.rb
+++ b/app/services/projects/move_lfs_objects_projects_service.rb
@@ -20,7 +20,7 @@ module Projects
end
def remove_remaining_lfs_objects_project
- source_project.lfs_objects_projects.destroy_all # rubocop: disable DestroyAll
+ source_project.lfs_objects_projects.destroy_all # rubocop: disable Cop/DestroyAll
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb
index 65a888fe26b..efe06f158cc 100644
--- a/app/services/projects/move_notification_settings_service.rb
+++ b/app/services/projects/move_notification_settings_service.rb
@@ -21,7 +21,7 @@ module Projects
# Remove remaining notification settings from source_project
def remove_remaining_notification_settings
- source_project.notification_settings.destroy_all # rubocop: disable DestroyAll
+ source_project.notification_settings.destroy_all # rubocop: disable Cop/DestroyAll
end
# Get users of current notification_settings
diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb
index d1aa9af2bcb..349953ff973 100644
--- a/app/services/projects/move_project_group_links_service.rb
+++ b/app/services/projects/move_project_group_links_service.rb
@@ -25,7 +25,7 @@ module Projects
# Remove remaining project group links from source_project
def remove_remaining_project_group_links
- source_project.reset.project_group_links.destroy_all # rubocop: disable DestroyAll
+ source_project.reset.project_group_links.destroy_all # rubocop: disable Cop/DestroyAll
end
def group_links_in_target_project
diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb
index de4e7e5a1e3..9a1b7c6d1b6 100644
--- a/app/services/projects/move_project_members_service.rb
+++ b/app/services/projects/move_project_members_service.rb
@@ -25,7 +25,7 @@ module Projects
def remove_remaining_members
# Remove remaining members and authorizations from source_project
- source_project.project_members.destroy_all # rubocop: disable DestroyAll
+ source_project.project_members.destroy_all # rubocop: disable Cop/DestroyAll
end
def project_members_in_target_project
diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb
index c06f572b52f..7aa7ea73639 100644
--- a/app/services/projects/operations/update_service.rb
+++ b/app/services/projects/operations/update_service.rb
@@ -41,9 +41,9 @@ module Projects
attribs = params[:metrics_setting_attributes]
return {} unless attribs
- destroy = attribs[:external_dashboard_url].blank?
+ attribs[:external_dashboard_url] = attribs[:external_dashboard_url].presence
- { metrics_setting_attributes: attribs.merge(_destroy: destroy) }
+ { metrics_setting_attributes: attribs }
end
def error_tracking_params
diff --git a/app/services/projects/prometheus/alerts/create_events_service.rb b/app/services/projects/prometheus/alerts/create_events_service.rb
index a29240947ff..4fcf841314b 100644
--- a/app/services/projects/prometheus/alerts/create_events_service.rb
+++ b/app/services/projects/prometheus/alerts/create_events_service.rb
@@ -40,17 +40,13 @@ module Projects
def create_managed_prometheus_alert_event(parsed_alert)
alert = find_alert(parsed_alert.metric_id)
- payload_key = PrometheusAlertEvent.payload_key_for(parsed_alert.metric_id, parsed_alert.starts_at_raw)
-
- event = PrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, alert, payload_key)
+ event = PrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, alert, parsed_alert.gitlab_fingerprint)
set_status(parsed_alert, event)
end
def create_self_managed_prometheus_alert_event(parsed_alert)
- payload_key = SelfManagedPrometheusAlertEvent.payload_key_for(parsed_alert.starts_at_raw, parsed_alert.title, parsed_alert.full_query)
-
- event = SelfManagedPrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, payload_key) do |event|
+ event = SelfManagedPrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, parsed_alert.gitlab_fingerprint) do |event|
event.environment = parsed_alert.environment
event.title = parsed_alert.title
event.query_expression = parsed_alert.full_query
diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb
index 2583a6cae9f..877a4f99a94 100644
--- a/app/services/projects/prometheus/alerts/notify_service.rb
+++ b/app/services/projects/prometheus/alerts/notify_service.rb
@@ -7,9 +7,19 @@ module Projects
include Gitlab::Utils::StrongMemoize
include IncidentManagement::Settings
+ # This set of keys identifies a payload as a valid Prometheus
+ # payload and thus processable by this service. See also
+ # https://prometheus.io/docs/alerting/configuration/#webhook_config
+ REQUIRED_PAYLOAD_KEYS = %w[
+ version groupKey status receiver groupLabels commonLabels
+ commonAnnotations externalURL alerts
+ ].to_set.freeze
+
+ SUPPORTED_VERSION = '4'
+
def execute(token)
return bad_request unless valid_payload_size?
- return unprocessable_entity unless valid_version?
+ return unprocessable_entity unless self.class.processable?(params)
return unauthorized unless valid_alert_manager_token?(token)
process_prometheus_alerts
@@ -20,6 +30,14 @@ module Projects
ServiceResponse.success
end
+ def self.processable?(params)
+ # Workaround for https://gitlab.com/gitlab-org/gitlab/-/issues/220496
+ return false unless params
+
+ REQUIRED_PAYLOAD_KEYS.subset?(params.keys.to_set) &&
+ params['version'] == SUPPORTED_VERSION
+ end
+
private
def valid_payload_size?
@@ -42,12 +60,10 @@ module Projects
params['alerts']
end
- def valid_version?
- params['version'] == '4'
- end
-
def valid_alert_manager_token?(token)
- valid_for_manual?(token) || valid_for_managed?(token)
+ valid_for_manual?(token) ||
+ valid_for_alerts_endpoint?(token) ||
+ valid_for_managed?(token)
end
def valid_for_manual?(token)
@@ -61,6 +77,13 @@ module Projects
end
end
+ def valid_for_alerts_endpoint?(token)
+ return false unless project.alerts_service_activated?
+
+ # Here we are enforcing the existence of the token
+ compare_token(token, project.alerts_service.token)
+ end
+
def valid_for_managed?(token)
prometheus_application = available_prometheus_application(project)
return false unless prometheus_application
diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb
index 0483c951f1e..4adcda042d1 100644
--- a/app/services/projects/propagate_service_template.rb
+++ b/app/services/projects/propagate_service_template.rb
@@ -26,7 +26,7 @@ module Projects
def propagate_projects_with_template
loop do
- batch = Project.uncached { project_ids_batch }
+ batch = Project.uncached { project_ids_without_integration }
bulk_create_from_template(batch) unless batch.empty?
@@ -35,40 +35,36 @@ module Projects
end
def bulk_create_from_template(batch)
- service_list = batch.map do |project_id|
- service_hash.values << project_id
- end
+ service_list = ServiceList.new(batch, service_hash).to_array
Project.transaction do
- results = bulk_insert(Service, service_hash.keys << 'project_id', service_list)
+ results = bulk_insert(*service_list)
if data_fields_present?
- data_list = results.map { |row| data_hash.values << row['id'] }
+ data_list = DataList.new(results, data_fields_hash, template.data_fields.class).to_array
- bulk_insert(template.data_fields.class, data_hash.keys << 'service_id', data_list)
+ bulk_insert(*data_list)
end
run_callbacks(batch)
end
end
- def project_ids_batch
- Project.connection.select_values(
- <<-SQL
- SELECT id
- FROM projects
- WHERE NOT EXISTS (
- SELECT true
- FROM services
- WHERE services.project_id = projects.id
- AND services.type = #{ActiveRecord::Base.connection.quote(template.type)}
- )
- AND projects.pending_delete = false
- AND projects.archived = false
- LIMIT #{BATCH_SIZE}
- SQL
- )
+ # rubocop: disable CodeReuse/ActiveRecord
+ def project_ids_without_integration
+ services = Service
+ .select('1')
+ .where('services.project_id = projects.id')
+ .where(type: template.type)
+
+ Project
+ .where('NOT EXISTS (?)', services)
+ .where(pending_delete: false)
+ .where(archived: false)
+ .limit(BATCH_SIZE)
+ .pluck(:id)
end
+ # rubocop: enable CodeReuse/ActiveRecord
def bulk_insert(klass, columns, values_array)
items_to_insert = values_array.map { |array| Hash[columns.zip(array)] }
@@ -77,11 +73,11 @@ module Projects
end
def service_hash
- @service_hash ||= template.as_json(methods: :type, except: %w[id template project_id])
+ @service_hash ||= template.to_service_hash
end
- def data_hash
- @data_hash ||= template.data_fields.as_json(only: template.data_fields.class.column_names).except('id', 'service_id')
+ def data_fields_hash
+ @data_fields_hash ||= template.to_data_fields_hash
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb
index e554bed6819..5f8ef75a8d7 100644
--- a/app/services/projects/update_remote_mirror_service.rb
+++ b/app/services/projects/update_remote_mirror_service.rb
@@ -27,7 +27,11 @@ module Projects
remote_mirror.update_start!
remote_mirror.ensure_remote!
- repository.fetch_remote(remote_mirror.remote_name, ssh_auth: remote_mirror, no_tags: true)
+
+ # https://gitlab.com/gitlab-org/gitaly/-/issues/2670
+ if Feature.disabled?(:gitaly_ruby_remote_branches_ls_remote)
+ repository.fetch_remote(remote_mirror.remote_name, ssh_auth: remote_mirror, no_tags: true)
+ end
response = remote_mirror.update_repository
diff --git a/app/services/projects/update_repository_storage_service.rb b/app/services/projects/update_repository_storage_service.rb
index 0632df6f6d7..fa8d4c5aa5f 100644
--- a/app/services/projects/update_repository_storage_service.rb
+++ b/app/services/projects/update_repository_storage_service.rb
@@ -24,7 +24,7 @@ module Projects
mark_old_paths_for_archive
repository_storage_move.finish!
- project.update!(repository_storage: destination_storage_name, repository_read_only: false)
+
project.leave_pool_repository
project.track_project_repository
end
@@ -34,10 +34,7 @@ module Projects
ServiceResponse.success
rescue StandardError => e
- project.transaction do
- repository_storage_move.do_fail!
- project.update!(repository_read_only: false)
- end
+ repository_storage_move.do_fail!
Gitlab::ErrorTracking.track_exception(e, project_path: project.full_path)
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index e10dede632a..58c9bce963b 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -13,8 +13,12 @@ module Projects
ensure_wiki_exists if enabling_wiki?
- if changing_storage_size?
- project.change_repository_storage(params.delete(:repository_storage))
+ if changing_repository_storage?
+ storage_move = project.repository_storage_moves.build(
+ source_storage_name: project.repository_storage,
+ destination_storage_name: params.delete(:repository_storage)
+ )
+ storage_move.schedule
end
yield if block_given?
@@ -132,7 +136,7 @@ module Projects
def ensure_wiki_exists
ProjectWiki.new(project, project.owner).wiki
- rescue ProjectWiki::CouldNotCreateWikiError
+ rescue Wiki::CouldNotCreateWikiError
log_error("Could not create wiki for #{project.full_name}")
Gitlab::Metrics.counter(:wiki_can_not_be_created_total, 'Counts the times we failed to create a wiki').increment
end
@@ -145,10 +149,11 @@ module Projects
project.previous_changes.include?(:pages_https_only)
end
- def changing_storage_size?
+ def changing_repository_storage?
new_repository_storage = params[:repository_storage]
new_repository_storage && project.repository.exists? &&
+ project.repository_storage != new_repository_storage &&
can?(current_user, :change_repository_storage, project)
end
end
diff --git a/app/services/projects/update_statistics_service.rb b/app/services/projects/update_statistics_service.rb
index cc6ffa9eafc..a0793cff2df 100644
--- a/app/services/projects/update_statistics_service.rb
+++ b/app/services/projects/update_statistics_service.rb
@@ -5,7 +5,7 @@ module Projects
def execute
return unless project
- Rails.logger.info("Updating statistics for project #{project.id}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.info("Updating statistics for project #{project.id}")
project.statistics.refresh!(only: statistics.map(&:to_sym))
end
diff --git a/app/services/prometheus/create_default_alerts_service.rb b/app/services/prometheus/create_default_alerts_service.rb
index c87cbbbe3cf..53baf6a650e 100644
--- a/app/services/prometheus/create_default_alerts_service.rb
+++ b/app/services/prometheus/create_default_alerts_service.rb
@@ -33,6 +33,7 @@ module Prometheus
return ServiceResponse.error(message: 'Invalid environment') unless environment
create_alerts
+ schedule_prometheus_update
ServiceResponse.success
end
@@ -51,6 +52,16 @@ module Prometheus
end
end
+ def schedule_prometheus_update
+ return unless prometheus_application
+
+ ::Clusters::Applications::ScheduleUpdateService.new(prometheus_application, project).execute
+ end
+
+ def prometheus_application
+ environment.cluster_prometheus_adapter
+ end
+
def metrics_by_identifier
strong_memoize(:metrics_by_identifier) do
metric_identifiers = DEFAULT_ALERTS.map { |alert| alert[:identifier] }
diff --git a/app/services/prometheus/proxy_service.rb b/app/services/prometheus/proxy_service.rb
index 085cfc76196..e0bc5518d30 100644
--- a/app/services/prometheus/proxy_service.rb
+++ b/app/services/prometheus/proxy_service.rb
@@ -30,6 +30,10 @@ module Prometheus
'query_range' => {
method: ['GET'],
params: %w(query start end step timeout)
+ },
+ 'series' => {
+ method: %w(GET),
+ params: %w(match start end)
}
}.freeze
diff --git a/app/services/prometheus/proxy_variable_substitution_service.rb b/app/services/prometheus/proxy_variable_substitution_service.rb
index 7b98cfc592a..10fb3a8c1b5 100644
--- a/app/services/prometheus/proxy_variable_substitution_service.rb
+++ b/app/services/prometheus/proxy_variable_substitution_service.rb
@@ -58,7 +58,7 @@ module Prometheus
def substitute_variables(result)
return success(result) unless query(result)
- result[:params][:query] = gsub(query(result), full_context)
+ result[:params][:query] = gsub(query(result), full_context(result))
success(result)
end
@@ -75,12 +75,16 @@ module Prometheus
end
end
- def predefined_context
- Gitlab::Prometheus::QueryVariables.call(@environment).stringify_keys
+ def predefined_context(result)
+ Gitlab::Prometheus::QueryVariables.call(
+ @environment,
+ start_time: start_timestamp(result),
+ end_time: end_timestamp(result)
+ ).stringify_keys
end
- def full_context
- @full_context ||= predefined_context.reverse_merge(variables_hash)
+ def full_context(result)
+ @full_context ||= predefined_context(result).reverse_merge(variables_hash)
end
def variables
@@ -91,6 +95,16 @@ module Prometheus
variables.to_h
end
+ def start_timestamp(result)
+ Time.rfc3339(result[:params][:start])
+ rescue ArgumentError
+ end
+
+ def end_timestamp(result)
+ Time.rfc3339(result[:params][:end])
+ rescue ArgumentError
+ end
+
def query(result)
result[:params][:query]
end
diff --git a/app/services/protected_branches/legacy_api_update_service.rb b/app/services/protected_branches/legacy_api_update_service.rb
index 65dc3297ae8..0cad23f20f7 100644
--- a/app/services/protected_branches/legacy_api_update_service.rb
+++ b/app/services/protected_branches/legacy_api_update_service.rb
@@ -39,11 +39,11 @@ module ProtectedBranches
def delete_redundant_access_levels
unless developers_can_merge.nil?
- protected_branch.merge_access_levels.destroy_all # rubocop: disable DestroyAll
+ protected_branch.merge_access_levels.destroy_all # rubocop: disable Cop/DestroyAll
end
unless developers_can_push.nil?
- protected_branch.push_access_levels.destroy_all # rubocop: disable DestroyAll
+ protected_branch.push_access_levels.destroy_all # rubocop: disable Cop/DestroyAll
end
end
end
diff --git a/app/services/releases/create_evidence_service.rb b/app/services/releases/create_evidence_service.rb
new file mode 100644
index 00000000000..ac13dce1729
--- /dev/null
+++ b/app/services/releases/create_evidence_service.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Releases
+ class CreateEvidenceService
+ def initialize(release, pipeline: nil)
+ @release = release
+ @pipeline = pipeline
+ end
+
+ def execute
+ evidence = release.evidences.build
+
+ summary = Evidences::EvidenceSerializer.new.represent(evidence) # rubocop: disable CodeReuse/Serializer
+ evidence.summary = summary
+ # TODO: fix the sha generating https://gitlab.com/gitlab-org/gitlab/-/issues/209000
+ evidence.summary_sha = Gitlab::CryptoHelper.sha256(summary)
+
+ evidence.save!
+ end
+
+ private
+
+ attr_reader :release
+ end
+end
diff --git a/app/services/releases/create_service.rb b/app/services/releases/create_service.rb
index 81ca9d6d123..92a0b875dd4 100644
--- a/app/services/releases/create_service.rb
+++ b/app/services/releases/create_service.rb
@@ -9,11 +9,16 @@ module Releases
return error('Release already exists', 409) if release
return error("Milestone(s) not found: #{inexistent_milestones.join(', ')}", 400) if inexistent_milestones.any?
+ # should be found before the creation of new tag
+ # because tag creation can spawn new pipeline
+ # which won't have any data for evidence yet
+ evidence_pipeline = find_evidence_pipeline
+
tag = ensure_tag
return tag unless tag.is_a?(Gitlab::Git::Tag)
- create_release(tag)
+ create_release(tag, evidence_pipeline)
end
def find_or_build_release
@@ -42,13 +47,15 @@ module Releases
Ability.allowed?(current_user, :create_release, project)
end
- def create_release(tag)
+ def create_release(tag, evidence_pipeline)
release = build_release(tag)
release.save!
notify_create_release(release)
+ create_evidence!(release, evidence_pipeline)
+
success(tag: tag, release: release)
rescue => e
error(e.message, 400)
@@ -70,5 +77,27 @@ module Releases
milestones: milestones
)
end
+
+ def find_evidence_pipeline
+ # TODO: remove this with the release creation moved to it's own form https://gitlab.com/gitlab-org/gitlab/-/issues/214245
+ return params[:evidence_pipeline] if params[:evidence_pipeline]
+
+ sha = existing_tag&.dereferenced_target&.sha
+ sha ||= repository.commit(ref)&.sha
+
+ return unless sha
+
+ project.ci_pipelines.for_sha(sha).last
+ end
+
+ def create_evidence!(release, pipeline)
+ return if release.historical_release?
+
+ if release.upcoming_release?
+ CreateEvidenceWorker.perform_at(release.released_at, release.id, pipeline&.id)
+ else
+ CreateEvidenceWorker.perform_async(release.id, pipeline&.id)
+ end
+ end
end
end
diff --git a/app/services/resource_events/change_labels_service.rb b/app/services/resource_events/change_labels_service.rb
index e0d019f54be..dc23f727079 100644
--- a/app/services/resource_events/change_labels_service.rb
+++ b/app/services/resource_events/change_labels_service.rb
@@ -22,7 +22,7 @@ module ResourceEvents
label_hash.merge(label_id: label.id, action: ResourceLabelEvent.actions['remove'])
end
- Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, labels)
+ Gitlab::Database.bulk_insert(ResourceLabelEvent.table_name, labels) # rubocop:disable Gitlab/BulkInsert
resource.expire_note_etag_cache
end
diff --git a/app/services/resource_events/change_state_service.rb b/app/services/resource_events/change_state_service.rb
new file mode 100644
index 00000000000..8beb76d8aee
--- /dev/null
+++ b/app/services/resource_events/change_state_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module ResourceEvents
+ class ChangeStateService
+ attr_reader :resource, :user
+
+ def initialize(user:, resource:)
+ @user, @resource = user, resource
+ end
+
+ def execute(state)
+ ResourceStateEvent.create(
+ user: user,
+ issue: issue,
+ merge_request: merge_request,
+ state: ResourceStateEvent.states[state],
+ created_at: Time.zone.now)
+
+ resource.expire_note_etag_cache
+ end
+
+ private
+
+ def issue
+ return unless resource.is_a?(Issue)
+
+ resource
+ end
+
+ def merge_request
+ return unless resource.is_a?(MergeRequest)
+
+ resource
+ end
+ end
+end
diff --git a/app/services/resource_events/merge_into_notes_service.rb b/app/services/resource_events/merge_into_notes_service.rb
index 4aa9bb80229..122bcb8550f 100644
--- a/app/services/resource_events/merge_into_notes_service.rb
+++ b/app/services/resource_events/merge_into_notes_service.rb
@@ -11,7 +11,8 @@ module ResourceEvents
SYNTHETIC_NOTE_BUILDER_SERVICES = [
SyntheticLabelNotesBuilderService,
- SyntheticMilestoneNotesBuilderService
+ SyntheticMilestoneNotesBuilderService,
+ SyntheticStateNotesBuilderService
].freeze
attr_reader :resource, :current_user, :params
@@ -23,7 +24,7 @@ module ResourceEvents
end
def execute(notes = [])
- (notes + synthetic_notes).sort_by { |n| n.created_at }
+ (notes + synthetic_notes).sort_by(&:created_at)
end
private
diff --git a/app/services/resource_events/synthetic_state_notes_builder_service.rb b/app/services/resource_events/synthetic_state_notes_builder_service.rb
new file mode 100644
index 00000000000..763134d98d8
--- /dev/null
+++ b/app/services/resource_events/synthetic_state_notes_builder_service.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module ResourceEvents
+ class SyntheticStateNotesBuilderService < BaseSyntheticNotesBuilderService
+ private
+
+ def synthetic_notes
+ state_change_events.map do |event|
+ StateNote.from_event(event, resource: resource, resource_parent: resource_parent)
+ end
+ end
+
+ def state_change_events
+ return [] unless resource.respond_to?(:resource_state_events)
+
+ events = resource.resource_state_events.includes(user: :status) # rubocop: disable CodeReuse/ActiveRecord
+ since_fetch_at(events)
+ end
+ end
+end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index bf21eba28f7..650dc197f8c 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -5,7 +5,6 @@ class SearchService
SEARCH_TERM_LIMIT = 64
SEARCH_CHAR_LIMIT = 4096
-
DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE
MAX_PER_PAGE = 200
@@ -62,8 +61,8 @@ class SearchService
@search_results ||= search_service.execute
end
- def search_objects
- @search_objects ||= redact_unauthorized_results(search_results.objects(scope, page: params[:page], per_page: per_page))
+ def search_objects(preload_method = nil)
+ @search_objects ||= redact_unauthorized_results(search_results.objects(scope, page: params[:page], per_page: per_page, preload_method: preload_method))
end
private
@@ -83,16 +82,21 @@ class SearchService
end
def redact_unauthorized_results(results_collection)
- results = results_collection.to_a
- permitted_results = results.select { |object| visible_result?(object) }
+ redacted_results = results_collection.reject { |object| visible_result?(object) }
+
+ if redacted_results.any?
+ redacted_log = redacted_results.each_with_object({}) do |object, memo|
+ memo[object.id] = { ability: :"read_#{object.to_ability_name}", id: object.id, class_name: object.class.name }
+ end
+
+ log_redacted_search_results(redacted_log.values)
- redacted_results = (results - permitted_results).each_with_object({}) do |object, memo|
- memo[object.id] = { ability: :"read_#{object.to_ability_name}", id: object.id, class_name: object.class.name }
+ return results_collection.id_not_in(redacted_log.keys) if results_collection.is_a?(ActiveRecord::Relation)
end
- log_redacted_search_results(redacted_results.values) if redacted_results.any?
+ return results_collection if results_collection.is_a?(ActiveRecord::Relation)
- return results_collection.id_not_in(redacted_results.keys) if results_collection.is_a?(ActiveRecord::Relation)
+ permitted_results = results_collection - redacted_results
Kaminari.paginate_array(
permitted_results,
diff --git a/app/services/service_response.rb b/app/services/service_response.rb
index 08b7e9d0831..74c0be22d46 100644
--- a/app/services/service_response.rb
+++ b/app/services/service_response.rb
@@ -26,6 +26,12 @@ class ServiceResponse
status == :error
end
+ def errors
+ return [] unless error?
+
+ Array.wrap(message)
+ end
+
private
attr_writer :status, :message, :http_status, :payload
diff --git a/app/services/snippets/base_service.rb b/app/services/snippets/base_service.rb
index 81d12997335..5d1fe815d83 100644
--- a/app/services/snippets/base_service.rb
+++ b/app/services/snippets/base_service.rb
@@ -6,12 +6,13 @@ module Snippets
CreateRepositoryError = Class.new(StandardError)
- attr_reader :uploaded_files
+ attr_reader :uploaded_assets, :snippet_files
def initialize(project, user = nil, params = {})
super
- @uploaded_files = Array(@params.delete(:files).presence)
+ @uploaded_assets = Array(@params.delete(:files).presence)
+ @snippet_files = SnippetInputActionCollection.new(Array(@params.delete(:snippet_files).presence))
filter_spam_check_params
end
@@ -22,12 +23,30 @@ module Snippets
Gitlab::VisibilityLevel.allowed_for?(current_user, visibility_level)
end
- def error_forbidden_visibility(snippet)
+ def forbidden_visibility_error(snippet)
deny_visibility_level(snippet)
snippet_error_response(snippet, 403)
end
+ def valid_params?
+ return true if snippet_files.empty?
+
+ (params.keys & [:content, :file_name]).none? && snippet_files.valid?
+ end
+
+ def invalid_params_error(snippet)
+ if snippet_files.valid?
+ [:content, :file_name].each do |key|
+ snippet.errors.add(key, 'and snippet files cannot be used together') if params.key?(key)
+ end
+ else
+ snippet.errors.add(:snippet_files, 'have invalid data')
+ end
+
+ snippet_error_response(snippet, 403)
+ end
+
def snippet_error_response(snippet, http_status)
ServiceResponse.error(
message: snippet.errors.full_messages.to_sentence,
@@ -52,5 +71,13 @@ module Snippets
message
end
+
+ def files_to_commit(snippet)
+ snippet_files.to_commit_actions.presence || build_actions_from_params(snippet)
+ end
+
+ def build_actions_from_params(snippet)
+ raise NotImplementedError
+ end
end
end
diff --git a/app/services/snippets/bulk_destroy_service.rb b/app/services/snippets/bulk_destroy_service.rb
index d9cc383a5a6..a612d8f8dfc 100644
--- a/app/services/snippets/bulk_destroy_service.rb
+++ b/app/services/snippets/bulk_destroy_service.rb
@@ -14,12 +14,12 @@ module Snippets
@snippets = snippets
end
- def execute
+ def execute(options = {})
return ServiceResponse.success(message: 'No snippets found.') if snippets.empty?
- user_can_delete_snippets!
+ user_can_delete_snippets! unless options[:hard_delete]
attempt_delete_repositories!
- snippets.destroy_all # rubocop: disable DestroyAll
+ snippets.destroy_all # rubocop: disable Cop/DestroyAll
ServiceResponse.success(message: 'Snippets were deleted.')
rescue SnippetAccessError
diff --git a/app/services/snippets/create_service.rb b/app/services/snippets/create_service.rb
index ed6da3a0ad0..7b477621da3 100644
--- a/app/services/snippets/create_service.rb
+++ b/app/services/snippets/create_service.rb
@@ -5,13 +5,15 @@ module Snippets
def execute
@snippet = build_from_params
+ return invalid_params_error(@snippet) unless valid_params?
+
unless visibility_allowed?(@snippet, @snippet.visibility_level)
- return error_forbidden_visibility(@snippet)
+ return forbidden_visibility_error(@snippet)
end
@snippet.author = current_user
- spam_check(@snippet, current_user)
+ spam_check(@snippet, current_user, action: :create)
if save_and_commit
UserAgentDetailService.new(@snippet, @request).create
@@ -29,12 +31,21 @@ module Snippets
def build_from_params
if project
- project.snippets.build(params)
+ project.snippets.build(create_params)
else
- PersonalSnippet.new(params)
+ PersonalSnippet.new(create_params)
end
end
+ # If the snippet_files param is present
+ # we need to fill content and file_name from
+ # the model
+ def create_params
+ return params if snippet_files.empty?
+
+ params.merge(content: snippet_files[0].content, file_name: snippet_files[0].file_path)
+ end
+
def save_and_commit
snippet_saved = @snippet.save
@@ -75,19 +86,19 @@ module Snippets
message: 'Initial commit'
}
- @snippet.snippet_repository.multi_files_action(current_user, snippet_files, commit_attrs)
- end
-
- def snippet_files
- [{ file_path: params[:file_name], content: params[:content] }]
+ @snippet.snippet_repository.multi_files_action(current_user, files_to_commit(@snippet), commit_attrs)
end
def move_temporary_files
return unless @snippet.is_a?(PersonalSnippet)
- uploaded_files.each do |file|
+ uploaded_assets.each do |file|
FileMover.new(file, from_model: current_user, to_model: @snippet).execute
end
end
+
+ def build_actions_from_params(_snippet)
+ [{ file_path: params[:file_name], content: params[:content] }]
+ end
end
end
diff --git a/app/services/snippets/update_service.rb b/app/services/snippets/update_service.rb
index 250120c1c19..6cdc2c374da 100644
--- a/app/services/snippets/update_service.rb
+++ b/app/services/snippets/update_service.rb
@@ -7,12 +7,14 @@ module Snippets
UpdateError = Class.new(StandardError)
def execute(snippet)
+ return invalid_params_error(snippet) unless valid_params?
+
if visibility_changed?(snippet) && !visibility_allowed?(snippet, visibility_level)
- return error_forbidden_visibility(snippet)
+ return forbidden_visibility_error(snippet)
end
- snippet.assign_attributes(params)
- spam_check(snippet, current_user)
+ update_snippet_attributes(snippet)
+ spam_check(snippet, current_user, action: :update)
if save_and_commit(snippet)
Gitlab::UsageDataCounters::SnippetCounter.count(:update)
@@ -29,6 +31,19 @@ module Snippets
visibility_level && visibility_level.to_i != snippet.visibility_level
end
+ def update_snippet_attributes(snippet)
+ # We can remove the following condition once
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/217801
+ # is implemented.
+ # Once we can perform different operations through this service
+ # we won't need to keep track of the `content` and `file_name` fields
+ if snippet_files.any?
+ params.merge!(content: snippet_files[0].content, file_name: snippet_files[0].file_path)
+ end
+
+ snippet.assign_attributes(params)
+ end
+
def save_and_commit(snippet)
return false unless snippet.save
@@ -81,15 +96,7 @@ module Snippets
message: 'Update snippet'
}
- snippet.snippet_repository.multi_files_action(current_user, snippet_files(snippet), commit_attrs)
- end
-
- def snippet_files(snippet)
- file_name_on_repo = snippet.file_name_on_repo
-
- [{ previous_path: file_name_on_repo,
- file_path: params[:file_name] || file_name_on_repo,
- content: params[:content] }]
+ snippet.snippet_repository.multi_files_action(current_user, files_to_commit(snippet), commit_attrs)
end
# Because we are removing repositories we don't want to remove
@@ -101,7 +108,15 @@ module Snippets
end
def committable_attributes?
- (params.stringify_keys.keys & COMMITTABLE_ATTRIBUTES).present?
+ (params.stringify_keys.keys & COMMITTABLE_ATTRIBUTES).present? || snippet_files.any?
+ end
+
+ def build_actions_from_params(snippet)
+ file_name_on_repo = snippet.file_name_on_repo
+
+ [{ previous_path: file_name_on_repo,
+ file_path: params[:file_name] || file_name_on_repo,
+ content: params[:content] }]
end
end
end
diff --git a/app/services/spam/akismet_service.rb b/app/services/spam/akismet_service.rb
index ab35fb8700f..e11a1dbdd96 100644
--- a/app/services/spam/akismet_service.rb
+++ b/app/services/spam/akismet_service.rb
@@ -27,7 +27,7 @@ module Spam
is_spam, is_blatant = akismet_client.check(options[:ip_address], options[:user_agent], params)
is_spam || is_blatant
rescue => e
- Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.error("Unable to connect to Akismet: #{e}, skipping check")
false
end
end
@@ -67,7 +67,7 @@ module Spam
akismet_client.public_send(type, options[:ip_address], options[:user_agent], params) # rubocop:disable GitlabSecurity/PublicSend
true
rescue => e
- Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.error("Unable to connect to Akismet: #{e}, skipping!")
false
end
end
diff --git a/app/services/spam/spam_action_service.rb b/app/services/spam/spam_action_service.rb
index f0a4aff4443..b745b67f566 100644
--- a/app/services/spam/spam_action_service.rb
+++ b/app/services/spam/spam_action_service.rb
@@ -7,9 +7,11 @@ module Spam
attr_accessor :target, :request, :options
attr_reader :spam_log
- def initialize(spammable:, request:)
+ def initialize(spammable:, request:, user:, context: {})
@target = spammable
@request = request
+ @user = user
+ @context = context
@options = {}
if @request
@@ -22,7 +24,7 @@ module Spam
end
end
- def execute(api: false, recaptcha_verified:, spam_log_id:, user:)
+ def execute(api: false, recaptcha_verified:, spam_log_id:)
if recaptcha_verified
# If it's a request which is already verified through reCAPTCHA,
# update the spam log accordingly.
@@ -40,6 +42,8 @@ module Spam
private
+ attr_reader :user, :context
+
def allowlisted?(user)
user.respond_to?(:gitlab_employee) && user.gitlab_employee?
end
@@ -49,7 +53,8 @@ module Spam
# ask the SpamVerdictService what to do with the target.
spam_verdict_service.execute.tap do |result|
case result
- when REQUIRE_RECAPTCHA
+ when CONDITIONAL_ALLOW
+ # at the moment, this means "ask for reCAPTCHA"
create_spam_log(api)
break if target.allow_possible_spam?
@@ -74,7 +79,7 @@ module Spam
description: target.spam_description,
source_ip: options[:ip_address],
user_agent: options[:user_agent],
- noteable_type: target.class.to_s,
+ noteable_type: notable_type,
via_api: api
}
)
@@ -84,8 +89,14 @@ module Spam
def spam_verdict_service
SpamVerdictService.new(target: target,
+ user: user,
request: @request,
- options: options)
+ options: options,
+ context: context.merge(target_type: notable_type))
+ end
+
+ def notable_type
+ @notable_type ||= target.class.to_s
end
end
end
diff --git a/app/services/spam/spam_constants.rb b/app/services/spam/spam_constants.rb
index 085bac684c4..2a16cfae78b 100644
--- a/app/services/spam/spam_constants.rb
+++ b/app/services/spam/spam_constants.rb
@@ -2,8 +2,24 @@
module Spam
module SpamConstants
- REQUIRE_RECAPTCHA = :recaptcha
- DISALLOW = :disallow
- ALLOW = :allow
+ CONDITIONAL_ALLOW = "conditional_allow"
+ DISALLOW = "disallow"
+ ALLOW = "allow"
+ BLOCK_USER = "block"
+
+ SUPPORTED_VERDICTS = {
+ BLOCK_USER => {
+ priority: 1
+ },
+ DISALLOW => {
+ priority: 2
+ },
+ CONDITIONAL_ALLOW => {
+ priority: 3
+ },
+ ALLOW => {
+ priority: 4
+ }
+ }.freeze
end
end
diff --git a/app/services/spam/spam_verdict_service.rb b/app/services/spam/spam_verdict_service.rb
index 2b4d5f4a984..68f1135ae28 100644
--- a/app/services/spam/spam_verdict_service.rb
+++ b/app/services/spam/spam_verdict_service.rb
@@ -5,22 +5,90 @@ module Spam
include AkismetMethods
include SpamConstants
- def initialize(target:, request:, options:)
+ def initialize(user:, target:, request:, options:, context: {})
@target = target
@request = request
+ @user = user
@options = options
+ @verdict_params = assemble_verdict_params(context)
end
def execute
+ external_spam_check_result = spam_verdict
+ akismet_result = akismet_verdict
+
+ # filter out anything we don't recognise, including nils.
+ valid_results = [external_spam_check_result, akismet_result].compact.select { |r| SUPPORTED_VERDICTS.key?(r) }
+ # Treat nils - such as service unavailable - as ALLOW
+ return ALLOW unless valid_results.any?
+
+ # Favour the most restrictive result.
+ valid_results.min_by { |v| SUPPORTED_VERDICTS[v][:priority] }
+ end
+
+ private
+
+ attr_reader :user, :target, :request, :options, :verdict_params
+
+ def akismet_verdict
if akismet.spam?
- Gitlab::Recaptcha.enabled? ? REQUIRE_RECAPTCHA : DISALLOW
+ Gitlab::Recaptcha.enabled? ? CONDITIONAL_ALLOW : DISALLOW
else
ALLOW
end
end
- private
+ def spam_verdict
+ return unless Gitlab::CurrentSettings.spam_check_endpoint_enabled
+ return if endpoint_url.blank?
+
+ begin
+ result = Gitlab::HTTP.post(endpoint_url, body: verdict_params.to_json, headers: { 'Content-Type' => 'application/json' })
+ return unless result
+
+ json_result = Gitlab::Json.parse(result).with_indifferent_access
+ # @TODO metrics/logging
+ # Expecting:
+ # error: (string or nil)
+ # result: (string or nil)
+ verdict = json_result[:verdict]
+ return unless SUPPORTED_VERDICTS.include?(verdict)
- attr_reader :target, :request, :options
+ # @TODO log if json_result[:error]
+
+ json_result[:verdict]
+ rescue *Gitlab::HTTP::HTTP_ERRORS => e
+ # @TODO: log error via try_post https://gitlab.com/gitlab-org/gitlab/-/issues/219223
+ Gitlab::ErrorTracking.log_exception(e)
+ return
+ rescue
+ # @TODO log
+ ALLOW
+ end
+ end
+
+ def assemble_verdict_params(context)
+ return {} unless endpoint_url.present?
+
+ project = target.try(:project)
+
+ context.merge({
+ target: {
+ title: target.spam_title,
+ description: target.spam_description,
+ type: target.class.to_s
+ },
+ user: {
+ created_at: user.created_at,
+ email: user.email,
+ username: user.username
+ },
+ user_in_project: user.authorized_project?(project)
+ })
+ end
+
+ def endpoint_url
+ @endpoint_url ||= Gitlab::CurrentSettings.current_application_settings.spam_check_endpoint_url
+ end
end
end
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 3265eb106eb..4bbde3a9648 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -28,7 +28,7 @@ class SubmitUsagePingService
true
rescue Gitlab::HTTP::Error => e
- Rails.logger.info "Unable to contact GitLab, Inc.: #{e}" # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.info("Unable to contact GitLab, Inc.: #{e}")
false
end
diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb
index 479eed3ce57..ab80b23a37b 100644
--- a/app/services/suggestions/apply_service.rb
+++ b/app/services/suggestions/apply_service.rb
@@ -2,109 +2,49 @@
module Suggestions
class ApplyService < ::BaseService
- DEFAULT_SUGGESTION_COMMIT_MESSAGE = 'Apply suggestion to %{file_path}'
-
- PLACEHOLDERS = {
- 'project_path' => ->(suggestion, user) { suggestion.project.path },
- 'project_name' => ->(suggestion, user) { suggestion.project.name },
- 'file_path' => ->(suggestion, user) { suggestion.file_path },
- 'branch_name' => ->(suggestion, user) { suggestion.branch },
- 'username' => ->(suggestion, user) { user.username },
- 'user_full_name' => ->(suggestion, user) { user.name }
- }.freeze
-
- # This regex is built dynamically using the keys from the PLACEHOLDER struct.
- # So, we can easily add new placeholder just by modifying the PLACEHOLDER hash.
- # This regex will build the new PLACEHOLDER_REGEX with the new information
- PLACEHOLDERS_REGEX = Regexp.union(PLACEHOLDERS.keys.map { |key| Regexp.new(Regexp.escape(key)) }).freeze
-
- attr_reader :current_user
-
- def initialize(current_user)
+ def initialize(current_user, *suggestions)
@current_user = current_user
+ @suggestion_set = Gitlab::Suggestions::SuggestionSet.new(suggestions)
end
- def execute(suggestion)
- unless suggestion.appliable?(cached: false)
- return error('Suggestion is not appliable')
- end
-
- unless latest_source_head?(suggestion)
- return error('The file has been changed')
+ def execute
+ if suggestion_set.valid?
+ result
+ else
+ error(suggestion_set.error_message)
end
+ end
- diff_file = suggestion.diff_file
-
- unless diff_file
- return error('The file was not found')
- end
+ private
- params = file_update_params(suggestion, diff_file)
- result = ::Files::UpdateService.new(suggestion.project, current_user, params).execute
+ attr_reader :current_user, :suggestion_set
- if result[:status] == :success
- suggestion.update(commit_id: result[:result], applied: true)
+ def result
+ multi_service.execute.tap do |result|
+ update_suggestions(result)
end
-
- result
- rescue Files::UpdateService::FileChangedError
- error('The file has been changed')
end
- private
+ def update_suggestions(result)
+ return unless result[:status] == :success
- # Checks whether the latest source branch HEAD matches with
- # the position HEAD we're using to update the file content. Since
- # the persisted HEAD is updated async (for MergeRequest),
- # it's more consistent to fetch this data directly from the
- # repository.
- def latest_source_head?(suggestion)
- suggestion.position.head_sha == suggestion.noteable.source_branch_sha
+ Suggestion.id_in(suggestion_set.suggestions)
+ .update_all(commit_id: result[:result], applied: true)
end
- def file_update_params(suggestion, diff_file)
- blob = diff_file.new_blob
- project = suggestion.project
- file_path = suggestion.file_path
- branch_name = suggestion.branch
- file_content = new_file_content(suggestion, blob)
- commit_message = processed_suggestion_commit_message(suggestion)
-
- file_last_commit =
- Gitlab::Git::Commit.last_for_path(project.repository,
- blob.commit_id,
- blob.path)
-
- {
- file_path: file_path,
- branch_name: branch_name,
- start_branch: branch_name,
+ def multi_service
+ params = {
commit_message: commit_message,
- file_content: file_content,
- last_commit_sha: file_last_commit&.id
+ branch_name: suggestion_set.branch,
+ start_branch: suggestion_set.branch,
+ actions: suggestion_set.actions
}
- end
-
- def new_file_content(suggestion, blob)
- range = suggestion.from_line_index..suggestion.to_line_index
- blob.load_all_data!
- content = blob.data.lines
- content[range] = suggestion.to_content
-
- content.join
- end
-
- def suggestion_commit_message(project)
- project.suggestion_commit_message.presence || DEFAULT_SUGGESTION_COMMIT_MESSAGE
+ ::Files::MultiService.new(suggestion_set.project, current_user, params)
end
- def processed_suggestion_commit_message(suggestion)
- message = suggestion_commit_message(suggestion.project)
-
- Gitlab::StringPlaceholderReplacer.replace_string_placeholders(message, PLACEHOLDERS_REGEX) do |key|
- PLACEHOLDERS[key].call(suggestion, current_user)
- end
+ def commit_message
+ Gitlab::Suggestions::CommitMessage.new(current_user, suggestion_set).message
end
end
end
diff --git a/app/services/suggestions/create_service.rb b/app/services/suggestions/create_service.rb
index 1d3338c1b45..93d2bd11426 100644
--- a/app/services/suggestions/create_service.rb
+++ b/app/services/suggestions/create_service.rb
@@ -25,7 +25,7 @@ module Suggestions
end
rows.in_groups_of(100, false) do |rows|
- Gitlab::Database.bulk_insert('suggestions', rows)
+ Gitlab::Database.bulk_insert('suggestions', rows) # rubocop:disable Gitlab/BulkInsert
end
end
end
diff --git a/app/services/system_notes/issuables_service.rb b/app/services/system_notes/issuables_service.rb
index 275c64bea89..7d7ee8d829e 100644
--- a/app/services/system_notes/issuables_service.rb
+++ b/app/services/system_notes/issuables_service.rb
@@ -128,7 +128,7 @@ module SystemNotes
body = cross_reference_note_content(gfm_reference)
if noteable.is_a?(ExternalIssue)
- noteable.project.issues_tracker.create_cross_reference_note(noteable, mentioner, author)
+ noteable.project.external_issue_tracker.create_cross_reference_note(noteable, mentioner, author)
else
create_note(NoteSummary.new(noteable, noteable.project, author, body, action: 'cross_reference'))
end
@@ -225,7 +225,12 @@ module SystemNotes
action = status == 'reopened' ? 'opened' : status
- create_note(NoteSummary.new(noteable, project, author, body, action: action))
+ # A state event which results in a synthetic note will be
+ # created by EventCreateService if change event tracking
+ # is enabled.
+ unless state_change_tracking_enabled?
+ create_note(NoteSummary.new(noteable, project, author, body, action: action))
+ end
end
# Check if a cross reference to a noteable from a mentioner already exists
@@ -318,6 +323,11 @@ module SystemNotes
def self.cross_reference?(note_text)
note_text =~ /\A#{cross_reference_note_prefix}/i
end
+
+ def state_change_tracking_enabled?
+ noteable.respond_to?(:resource_state_events) &&
+ ::Feature.enabled?(:track_resource_state_change_events, noteable.project)
+ end
end
end
diff --git a/app/services/test_hooks/base_service.rb b/app/services/test_hooks/base_service.rb
index ebebf29c28b..0fda6fb1ed0 100644
--- a/app/services/test_hooks/base_service.rb
+++ b/app/services/test_hooks/base_service.rb
@@ -2,6 +2,8 @@
module TestHooks
class BaseService
+ include BaseServiceUtility
+
attr_accessor :hook, :current_user, :trigger
def initialize(hook, current_user, trigger)
@@ -12,31 +14,11 @@ module TestHooks
def execute
trigger_key = hook.class.triggers.key(trigger.to_sym)
- trigger_data_method = "#{trigger}_data"
-
- if trigger_key.nil? || !self.respond_to?(trigger_data_method, true)
- return error('Testing not available for this hook')
- end
-
- error_message = catch(:validation_error) do # rubocop:disable Cop/BanCatchThrow
- sample_data = self.__send__(trigger_data_method) # rubocop:disable GitlabSecurity/PublicSend
-
- return hook.execute(sample_data, trigger_key) # rubocop:disable Cop/AvoidReturnFromBlocks
- end
-
- error(error_message)
- end
-
- private
- def error(message, http_status = nil)
- result = {
- message: message,
- status: :error
- }
+ return error('Testing not available for this hook') if trigger_key.nil? || data.blank?
+ return error(data[:error]) if data[:error].present?
- result[:http_status] = http_status if http_status
- result
+ hook.execute(data, trigger_key)
end
end
end
diff --git a/app/services/test_hooks/project_service.rb b/app/services/test_hooks/project_service.rb
index aa80cc928b9..4e554dce357 100644
--- a/app/services/test_hooks/project_service.rb
+++ b/app/services/test_hooks/project_service.rb
@@ -2,6 +2,9 @@
module TestHooks
class ProjectService < TestHooks::BaseService
+ include Integrations::ProjectTestData
+ include Gitlab::Utils::StrongMemoize
+
attr_writer :project
def project
@@ -10,58 +13,25 @@ module TestHooks
private
- def push_events_data
- throw(:validation_error, s_('TestHooks|Ensure the project has at least one commit.')) if project.empty_repo? # rubocop:disable Cop/BanCatchThrow
-
- Gitlab::DataBuilder::Push.build_sample(project, current_user)
- end
-
- alias_method :tag_push_events_data, :push_events_data
-
- def note_events_data
- note = project.notes.first
- throw(:validation_error, s_('TestHooks|Ensure the project has notes.')) unless note.present? # rubocop:disable Cop/BanCatchThrow
-
- Gitlab::DataBuilder::Note.build(note, current_user)
- end
-
- def issues_events_data
- issue = project.issues.first
- throw(:validation_error, s_('TestHooks|Ensure the project has issues.')) unless issue.present? # rubocop:disable Cop/BanCatchThrow
-
- issue.to_hook_data(current_user)
- end
-
- alias_method :confidential_issues_events_data, :issues_events_data
-
- def merge_requests_events_data
- merge_request = project.merge_requests.first
- throw(:validation_error, s_('TestHooks|Ensure the project has merge requests.')) unless merge_request.present? # rubocop:disable Cop/BanCatchThrow
-
- merge_request.to_hook_data(current_user)
- end
-
- def job_events_data
- build = project.builds.first
- throw(:validation_error, s_('TestHooks|Ensure the project has CI jobs.')) unless build.present? # rubocop:disable Cop/BanCatchThrow
-
- Gitlab::DataBuilder::Build.build(build)
- end
-
- def pipeline_events_data
- pipeline = project.ci_pipelines.first
- throw(:validation_error, s_('TestHooks|Ensure the project has CI pipelines.')) unless pipeline.present? # rubocop:disable Cop/BanCatchThrow
-
- Gitlab::DataBuilder::Pipeline.build(pipeline)
- end
-
- def wiki_page_events_data
- page = project.wiki.list_pages(limit: 1).first
- if !project.wiki_enabled? || page.blank?
- throw(:validation_error, s_('TestHooks|Ensure the wiki is enabled and has pages.')) # rubocop:disable Cop/BanCatchThrow
+ def data
+ strong_memoize(:data) do
+ case trigger
+ when 'push_events', 'tag_push_events'
+ push_events_data
+ when 'note_events'
+ note_events_data
+ when 'issues_events', 'confidential_issues_events'
+ issues_events_data
+ when 'merge_requests_events'
+ merge_requests_events_data
+ when 'job_events'
+ job_events_data
+ when 'pipeline_events'
+ pipeline_events_data
+ when 'wiki_page_events'
+ wiki_page_events_data
+ end
end
-
- Gitlab::DataBuilder::WikiPage.build(page, current_user, 'create')
end
end
end
diff --git a/app/services/test_hooks/system_service.rb b/app/services/test_hooks/system_service.rb
index 5c7961f417d..66d78bfc578 100644
--- a/app/services/test_hooks/system_service.rb
+++ b/app/services/test_hooks/system_service.rb
@@ -2,23 +2,26 @@
module TestHooks
class SystemService < TestHooks::BaseService
- private
-
- def push_events_data
- Gitlab::DataBuilder::Push.sample_data
- end
+ include Gitlab::Utils::StrongMemoize
- def tag_push_events_data
- Gitlab::DataBuilder::Push.sample_data
- end
+ private
- def repository_update_events_data
- Gitlab::DataBuilder::Repository.sample_data
+ def data
+ strong_memoize(:data) do
+ case trigger
+ when 'push_events', 'tag_push_events'
+ Gitlab::DataBuilder::Push.sample_data
+ when 'repository_update_events'
+ Gitlab::DataBuilder::Repository.sample_data
+ when 'merge_requests_events'
+ merge_requests_events_data
+ end
+ end
end
def merge_requests_events_data
merge_request = MergeRequest.of_projects(current_user.projects.select(:id)).first
- throw(:validation_error, s_('TestHooks|Ensure one of your projects has merge requests.')) unless merge_request.present? # rubocop:disable Cop/BanCatchThrow
+ return { error: s_('TestHooks|Ensure one of your projects has merge requests.') } unless merge_request.present?
merge_request.to_hook_data(current_user)
end
diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb
index 55f888d5664..e6fb0d3c72e 100644
--- a/app/services/todo_service.rb
+++ b/app/services/todo_service.rb
@@ -30,7 +30,7 @@ class TodoService
# * mark all pending todos related to the target for the current user as done
#
def close_issue(issue, current_user)
- mark_pending_todos_as_done(issue, current_user)
+ resolve_todos_for_target(issue, current_user)
end
# When we destroy a todo target we should:
@@ -79,7 +79,7 @@ class TodoService
# * mark all pending todos related to the target for the current user as done
#
def close_merge_request(merge_request, current_user)
- mark_pending_todos_as_done(merge_request, current_user)
+ resolve_todos_for_target(merge_request, current_user)
end
# When merge a merge request we should:
@@ -87,7 +87,7 @@ class TodoService
# * mark all pending todos related to the target for the current user as done
#
def merge_merge_request(merge_request, current_user)
- mark_pending_todos_as_done(merge_request, current_user)
+ resolve_todos_for_target(merge_request, current_user)
end
# When a build fails on the HEAD of a merge request we should:
@@ -105,7 +105,7 @@ class TodoService
# * mark all pending todos related to the merge request for that user as done
#
def merge_request_push(merge_request, current_user)
- mark_pending_todos_as_done(merge_request, current_user)
+ resolve_todos_for_target(merge_request, current_user)
end
# When a build is retried to a merge request we should:
@@ -114,7 +114,7 @@ class TodoService
#
def merge_request_build_retried(merge_request)
merge_request.merge_participants.each do |user|
- mark_pending_todos_as_done(merge_request, user)
+ resolve_todos_for_target(merge_request, user)
end
end
@@ -151,76 +151,68 @@ class TodoService
# * mark all pending todos related to the awardable for the current user as done
#
def new_award_emoji(awardable, current_user)
- mark_pending_todos_as_done(awardable, current_user)
+ resolve_todos_for_target(awardable, current_user)
end
- # When marking pending todos as done we should:
+ # When assigning an alert we should:
#
- # * mark all pending todos related to the target for the current user as done
+ # * create a pending todo for new assignee if alert is assigned
#
- def mark_pending_todos_as_done(target, user)
- attributes = attributes_for_target(target)
- pending_todos(user, attributes).update_all(state: :done)
- user.update_todos_count_cache
+ def assign_alert(alert, current_user)
+ create_assignment_todo(alert, current_user, [])
end
- # When user marks some todos as done
- def mark_todos_as_done(todos, current_user)
- update_todos_state(todos, current_user, :done)
+ # When user marks an issue as todo
+ def mark_todo(issuable, current_user)
+ attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
+ create_todos(current_user, attributes)
end
- def mark_todos_as_done_by_ids(ids, current_user)
- todos = todos_by_ids(ids, current_user)
- mark_todos_as_done(todos, current_user)
+ def todo_exist?(issuable, current_user)
+ TodosFinder.new(current_user).any_for_target?(issuable, :pending)
end
- def mark_all_todos_as_done_by_user(current_user)
- todos = TodosFinder.new(current_user).execute
- mark_todos_as_done(todos, current_user)
- end
+ # Resolves all todos related to target
+ def resolve_todos_for_target(target, current_user)
+ attributes = attributes_for_target(target)
- def mark_todo_as_done(todo, current_user)
- return if todo.done?
+ resolve_todos(pending_todos(current_user, attributes), current_user)
+ end
- todo.update(state: :done)
+ def resolve_todos(todos, current_user, resolution: :done, resolved_by_action: :system_done)
+ todos_ids = todos.batch_update(state: resolution, resolved_by_action: resolved_by_action)
current_user.update_todos_count_cache
- end
- # When user marks some todos as pending
- def mark_todos_as_pending(todos, current_user)
- update_todos_state(todos, current_user, :pending)
+ todos_ids
end
- def mark_todos_as_pending_by_ids(ids, current_user)
- todos = todos_by_ids(ids, current_user)
- mark_todos_as_pending(todos, current_user)
- end
+ def resolve_todo(todo, current_user, resolution: :done, resolved_by_action: :system_done)
+ return if todo.done?
- # When user marks an issue as todo
- def mark_todo(issuable, current_user)
- attributes = attributes_for_todo(issuable.project, issuable, current_user, Todo::MARKED)
- create_todos(current_user, attributes)
- end
+ todo.update(state: resolution, resolved_by_action: resolved_by_action)
- def todo_exist?(issuable, current_user)
- TodosFinder.new(current_user).any_for_target?(issuable, :pending)
+ current_user.update_todos_count_cache
end
- private
+ def restore_todos(todos, current_user)
+ todos_ids = todos.batch_update(state: :pending)
- def todos_by_ids(ids, current_user)
- current_user.todos_limited_to(Array(ids))
+ current_user.update_todos_count_cache
+
+ todos_ids
end
- def update_todos_state(todos, current_user, state)
- todos_ids = todos.update_state(state)
+ def restore_todo(todo, current_user)
+ return if todo.pending?
- current_user.update_todos_count_cache
+ todo.update(state: :pending)
- todos_ids
+ current_user.update_todos_count_cache
end
+ private
+
def create_todos(users, attributes)
Array(users).map do |user|
next if pending_todos(user, attributes).exists?
@@ -252,16 +244,16 @@ class TodoService
return unless note.can_create_todo?
project = note.project
- target = note.noteable
+ target = note.noteable
- mark_pending_todos_as_done(target, author)
+ resolve_todos_for_target(target, author)
create_mention_todos(project, target, author, note, skip_users)
end
- def create_assignment_todo(issuable, author, old_assignees = [])
- if issuable.assignees.any?
- assignees = issuable.assignees - old_assignees
- attributes = attributes_for_todo(issuable.project, issuable, author, Todo::ASSIGNED)
+ def create_assignment_todo(target, author, old_assignees = [])
+ if target.assignees.any?
+ assignees = target.assignees - old_assignees
+ attributes = attributes_for_todo(target.project, target, author, Todo::ASSIGNED)
create_todos(assignees, attributes)
end
end
diff --git a/app/services/user_project_access_changed_service.rb b/app/services/user_project_access_changed_service.rb
index 66f1ccfab70..11727f05f35 100644
--- a/app/services/user_project_access_changed_service.rb
+++ b/app/services/user_project_access_changed_service.rb
@@ -19,7 +19,8 @@ class UserProjectAccessChangedService
if priority == HIGH_PRIORITY
AuthorizedProjectsWorker.bulk_perform_async(bulk_args) # rubocop:disable Scalability/BulkPerformWithContext
else
- AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker.bulk_perform_in(DELAY, bulk_args) # rubocop:disable Scalability/BulkPerformWithContext
+ AuthorizedProjectUpdate::UserRefreshWithLowUrgencyWorker.bulk_perform_in( # rubocop:disable Scalability/BulkPerformWithContext
+ DELAY, bulk_args, batch_size: 100, batch_delay: 30.seconds)
end
end
end
diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb
index 3938d675596..f06f00a5c3f 100644
--- a/app/services/users/build_service.rb
+++ b/app/services/users/build_service.rb
@@ -82,7 +82,8 @@ module Users
:organization,
:location,
:public_email,
- :user_type
+ :user_type,
+ :note
]
end
diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb
index 587a8516394..436d4fb3985 100644
--- a/app/services/users/destroy_service.rb
+++ b/app/services/users/destroy_service.rb
@@ -56,7 +56,7 @@ module Users
MigrateToGhostUserService.new(user).execute unless options[:hard_delete]
- response = Snippets::BulkDestroyService.new(current_user, user.snippets).execute
+ response = Snippets::BulkDestroyService.new(current_user, user.snippets).execute(options)
raise DestroyError, response.message if response.error?
# Rails attempts to load all related records into memory before
diff --git a/app/services/users/migrate_to_ghost_user_service.rb b/app/services/users/migrate_to_ghost_user_service.rb
index 5ca9ed67e56..1b46edd4d7d 100644
--- a/app/services/users/migrate_to_ghost_user_service.rb
+++ b/app/services/users/migrate_to_ghost_user_service.rb
@@ -53,6 +53,7 @@ module Users
migrate_abuse_reports
migrate_award_emoji
migrate_snippets
+ migrate_reviews
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -85,6 +86,10 @@ module Users
snippets = user.snippets.only_project_snippets
snippets.update_all(author_id: ghost_user.id)
end
+
+ def migrate_reviews
+ user.reviews.update_all(author_id: ghost_user.id)
+ end
end
end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 178a321e20c..91a26ff45b1 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -63,7 +63,7 @@ class WebHookService
error_message: e.to_s
)
- Rails.logger.error("WebHook Error => #{e}") # rubocop:disable Gitlab/RailsLogger
+ Gitlab::AppLogger.error("WebHook Error => #{e}")
{
status: :error,
diff --git a/app/services/wiki_pages/create_service.rb b/app/services/wiki_pages/create_service.rb
index 4ef19676d82..63107445782 100644
--- a/app/services/wiki_pages/create_service.rb
+++ b/app/services/wiki_pages/create_service.rb
@@ -22,7 +22,7 @@ module WikiPages
end
def event_action
- Event::CREATED
+ :created
end
end
end
diff --git a/app/services/wiki_pages/destroy_service.rb b/app/services/wiki_pages/destroy_service.rb
index eb162223723..d59c27bb92a 100644
--- a/app/services/wiki_pages/destroy_service.rb
+++ b/app/services/wiki_pages/destroy_service.rb
@@ -19,7 +19,7 @@ module WikiPages
end
def event_action
- Event::DESTROYED
+ :destroyed
end
end
end
diff --git a/app/services/wiki_pages/update_service.rb b/app/services/wiki_pages/update_service.rb
index 0a056f1ec33..5ac6902e0b0 100644
--- a/app/services/wiki_pages/update_service.rb
+++ b/app/services/wiki_pages/update_service.rb
@@ -22,7 +22,7 @@ module WikiPages
end
def event_action
- Event::UPDATED
+ :updated
end
def slug_for_page(page)