diff options
Diffstat (limited to 'app/workers/bulk_imports/transform_references_worker.rb')
-rw-r--r-- | app/workers/bulk_imports/transform_references_worker.rb | 147 |
1 files changed, 147 insertions, 0 deletions
diff --git a/app/workers/bulk_imports/transform_references_worker.rb b/app/workers/bulk_imports/transform_references_worker.rb new file mode 100644 index 00000000000..383ad2fd733 --- /dev/null +++ b/app/workers/bulk_imports/transform_references_worker.rb @@ -0,0 +1,147 @@ +# frozen_string_literal: true + +module BulkImports + class TransformReferencesWorker + include ApplicationWorker + + idempotent! + data_consistency :delayed + sidekiq_options retry: 3, dead: false + feature_category :importers + + # rubocop: disable CodeReuse/ActiveRecord + def perform(object_ids, klass, tracker_id) + @tracker = BulkImports::Tracker.find_by_id(tracker_id) + + return unless tracker + + project = tracker.entity.project + + klass.constantize.where(id: object_ids, project: project).find_each do |object| + transform_and_save(object) + end + end + # rubocop: enable CodeReuse/ActiveRecord + + attr_reader :tracker + + private + + def transform_and_save(object) + body = object_body(object).dup + + return if body.blank? + + object.refresh_markdown_cache! + + body.gsub!(username_regex(mapped_usernames), mapped_usernames) + + if object_has_reference?(body) + matching_urls(object).each do |old_url, new_url| + body.gsub!(old_url, new_url) if body.include?(old_url) + end + end + + object.assign_attributes(body_field(object) => body) + object.save!(touch: false) if object_body_changed?(object) + + object + rescue StandardError => e + log_and_fail(e) + end + + def object_body(object) + call_object_method(object) + end + + def object_body_changed?(object) + call_object_method(object, suffix: '_changed?') + end + + def call_object_method(object, suffix: nil) + method = body_field(object) + method = "#{method}#{suffix}" if suffix.present? + + object.public_send(method) # rubocop:disable GitlabSecurity/PublicSend -- the method being called is dependent on several factors + end + + def body_field(object) + object.is_a?(Note) ? 'note' : 'description' + end + + def mapped_usernames + @mapped_usernames ||= ::BulkImports::UsersMapper.new(context: context) + .map_usernames.transform_keys { |key| "@#{key}" } + .transform_values { |value| "@#{value}" } + end + + def username_regex(mapped_usernames) + @username_regex ||= Regexp.new(mapped_usernames.keys.sort_by(&:length) + .reverse.map { |x| Regexp.escape(x) }.join('|')) + end + + def matching_urls(object) + URI.extract(object_body(object), %w[http https]).each_with_object([]) do |url, array| + parsed_url = URI.parse(url) + + next unless source_host == parsed_url.host + next unless parsed_url.path&.start_with?("/#{source_full_path}") + + array << [url, new_url(object, parsed_url)] + end + end + + def new_url(object, parsed_old_url) + parsed_old_url.host = ::Gitlab.config.gitlab.host + parsed_old_url.port = ::Gitlab.config.gitlab.port + parsed_old_url.scheme = ::Gitlab.config.gitlab.https ? 'https' : 'http' + parsed_old_url.to_s.gsub!(source_full_path, full_path(object)) + end + + def source_host + @source_host ||= URI.parse(context.configuration.url).host + end + + def source_full_path + @source_full_path ||= context.entity.source_full_path + end + + def full_path(object) + object.project.full_path + end + + def object_has_reference?(body) + body.include?(source_full_path) + end + + def log_and_fail(exception) + Gitlab::ErrorTracking.track_exception(exception, log_params) + BulkImports::Failure.create(failure_attributes(exception)) + end + + def log_params + { + message: 'Failed to update references', + bulk_import_id: context.bulk_import_id, + bulk_import_entity_id: tracker.bulk_import_entity_id, + source_full_path: context.entity.source_full_path, + source_version: context.bulk_import.source_version, + importer: 'gitlab_migration' + } + end + + def failure_attributes(exception) + { + bulk_import_entity_id: context.entity.id, + pipeline_class: 'ReferencesPipeline', + exception_class: exception.class.to_s, + exception_message: exception.message.truncate(255), + correlation_id_value: Labkit::Correlation::CorrelationId.current_or_new_id + } + end + + def context + @context ||= BulkImports::Pipeline::Context.new(tracker) + end + end +end |