diff options
Diffstat (limited to 'lib/gitlab/import_export/relation_tree_restorer.rb')
-rw-r--r-- | lib/gitlab/import_export/relation_tree_restorer.rb | 280 |
1 files changed, 0 insertions, 280 deletions
diff --git a/lib/gitlab/import_export/relation_tree_restorer.rb b/lib/gitlab/import_export/relation_tree_restorer.rb deleted file mode 100644 index 1eeacafef53..00000000000 --- a/lib/gitlab/import_export/relation_tree_restorer.rb +++ /dev/null @@ -1,280 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module ImportExport - class RelationTreeRestorer - # Relations which cannot be saved at project level (and have a group assigned) - GROUP_MODELS = [GroupLabel, Milestone, Epic].freeze - - attr_reader :user - attr_reader :shared - attr_reader :importable - attr_reader :relation_reader - - def initialize( # rubocop:disable Metrics/ParameterLists - user:, shared:, relation_reader:, - members_mapper:, object_builder:, - relation_factory:, - reader:, - importable:, - importable_attributes:, - importable_path: - ) - @user = user - @shared = shared - @importable = importable - @relation_reader = relation_reader - @members_mapper = members_mapper - @object_builder = object_builder - @relation_factory = relation_factory - @reader = reader - @importable_attributes = importable_attributes - @importable_path = importable_path - end - - def restore - ActiveRecord::Base.uncached do - ActiveRecord::Base.no_touching do - update_params! - - BulkInsertableAssociations.with_bulk_insert(enabled: project?) do - fix_ci_pipelines_not_sorted_on_legacy_project_json! - create_relations! - end - end - end - - # ensure that we have latest version of the restore - @importable.reload # rubocop:disable Cop/ActiveRecordAssociationReload - - true - rescue StandardError => e - @shared.error(e) - false - end - - private - - def project? - @importable.instance_of?(::Project) - end - - # Loops through the tree of models defined in import_export.yml and - # finds them in the imported JSON so they can be instantiated and saved - # in the DB. The structure and relationships between models are guessed from - # the configuration yaml file too. - # Finally, it updates each attribute in the newly imported project/group. - def create_relations! - relations.each do |relation_key, relation_definition| - process_relation!(relation_key, relation_definition) - end - end - - def process_relation!(relation_key, relation_definition) - @relation_reader.consume_relation(@importable_path, relation_key).each do |data_hash, relation_index| - process_relation_item!(relation_key, relation_definition, relation_index, data_hash) - end - end - - def process_relation_item!(relation_key, relation_definition, relation_index, data_hash) - relation_object = build_relation(relation_key, relation_definition, relation_index, data_hash) - return unless relation_object - return if project? && group_model?(relation_object) - - relation_object.assign_attributes(importable_class_sym => @importable) - - import_failure_service.with_retry(action: 'relation_object.save!', relation_key: relation_key, relation_index: relation_index) do - relation_object.save! - log_relation_creation(@importable, relation_key, relation_object) - end - rescue StandardError => e - import_failure_service.log_import_failure( - source: 'process_relation_item!', - relation_key: relation_key, - relation_index: relation_index, - exception: e) - end - - def import_failure_service - @import_failure_service ||= ImportFailureService.new(@importable) - end - - def relations - @relations ||= - @reader - .attributes_finder - .find_relations_tree(importable_class_sym) - .deep_stringify_keys - end - - def update_params! - params = @importable_attributes.except(*relations.keys.map(&:to_s)) - params = params.merge(present_override_params) - - # Cleaning all imported and overridden params - params = Gitlab::ImportExport::AttributeCleaner.clean( - relation_hash: params, - relation_class: importable_class, - excluded_keys: excluded_keys_for_relation(importable_class_sym)) - - @importable.assign_attributes(params) - - modify_attributes - - Gitlab::Timeless.timeless(@importable) do - @importable.save! - end - end - - def present_override_params - # we filter out the empty strings from the overrides - # keeping the default values configured - override_params&.transform_values do |value| - value.is_a?(String) ? value.presence : value - end&.compact - end - - def override_params - @importable_override_params ||= importable_override_params - end - - def importable_override_params - if @importable.respond_to?(:import_data) - @importable.import_data&.data&.fetch('override_params', nil) || {} - else - {} - end - end - - def modify_attributes - return unless project? - - @importable.reconcile_shared_runners_setting! - @importable.drop_visibility_level! - end - - def build_relations(relation_key, relation_definition, relation_index, data_hashes) - data_hashes - .map { |data_hash| build_relation(relation_key, relation_definition, relation_index, data_hash) } - .tap { |entries| entries.compact! } - end - - def build_relation(relation_key, relation_definition, relation_index, data_hash) - # TODO: This is hack to not create relation for the author - # Rather make `RelationFactory#set_note_author` to take care of that - return data_hash if relation_key == 'author' || already_restored?(data_hash) - - # create relation objects recursively for all sub-objects - relation_definition.each do |sub_relation_key, sub_relation_definition| - transform_sub_relations!(data_hash, sub_relation_key, sub_relation_definition, relation_index) - end - - relation = @relation_factory.create(**relation_factory_params(relation_key, relation_index, data_hash)) - - if relation && !relation.valid? - @shared.logger.warn( - message: "[Project/Group Import] Invalid object relation built", - relation_key: relation_key, - relation_index: relation_index, - relation_class: relation.class.name, - error_messages: relation.errors.full_messages.join(". ") - ) - end - - relation - end - - # Since we update the data hash in place as we restore relation items, - # and since we also de-duplicate items, we might encounter items that - # have already been restored in a previous iteration. - def already_restored?(relation_item) - !relation_item.is_a?(Hash) - end - - def transform_sub_relations!(data_hash, sub_relation_key, sub_relation_definition, relation_index) - sub_data_hash = data_hash[sub_relation_key] - return unless sub_data_hash - - # if object is a hash we can create simple object - # as it means that this is 1-to-1 vs 1-to-many - current_item = - if sub_data_hash.is_a?(Array) - build_relations( - sub_relation_key, - sub_relation_definition, - relation_index, - sub_data_hash).presence - else - build_relation( - sub_relation_key, - sub_relation_definition, - relation_index, - sub_data_hash) - end - - if current_item - data_hash[sub_relation_key] = current_item - else - data_hash.delete(sub_relation_key) - end - end - - def group_model?(relation_object) - GROUP_MODELS.include?(relation_object.class) && relation_object.group_id - end - - def excluded_keys_for_relation(relation) - @reader.attributes_finder.find_excluded_keys(relation) - end - - def importable_class - @importable.class - end - - def importable_class_sym - importable_class.to_s.downcase.to_sym - end - - def relation_factory_params(relation_key, relation_index, data_hash) - { - relation_index: relation_index, - relation_sym: relation_key.to_sym, - relation_hash: data_hash, - importable: @importable, - members_mapper: @members_mapper, - object_builder: @object_builder, - user: @user, - excluded_keys: excluded_keys_for_relation(relation_key) - } - end - - # Temporary fix for https://gitlab.com/gitlab-org/gitlab/-/issues/27883 when import from legacy project.json - # This should be removed once legacy JSON format is deprecated. - # Ndjson export file will fix the order during project export. - def fix_ci_pipelines_not_sorted_on_legacy_project_json! - return unless relation_reader.legacy? - - relation_reader.sort_ci_pipelines_by_id - end - - # Enable logging of each top-level relation creation when Importing - # into a Group if feature flag is enabled - def log_relation_creation(importable, relation_key, relation_object) - root_ancestor_group = importable.try(:root_ancestor) - - return unless root_ancestor_group - return unless root_ancestor_group.instance_of?(::Group) - return unless Feature.enabled?(:log_import_export_relation_creation, root_ancestor_group) - - @shared.logger.info( - importable_type: importable.class.to_s, - importable_id: importable.id, - relation_key: relation_key, - relation_id: relation_object.id, - author_id: relation_object.try(:author_id), - message: '[Project/Group Import] Created new object relation' - ) - end - end - end -end |