# Use case to offer the basics to migrate a register by creating new entries # @note # - You can define methods `filters` and `search` to change the target entries of the register # - You can define a json file named after your use case file (i.e. new_training.json) to pair source-destination fields: # - This file format expects an object {} where keys are field types (i.e. "select", "plain_text") # and the value is an array with either: # 1. field label name (String or RegExp body/source), # 2. or an object with key-value being source-destination label names (also supports RegExp body/source) # - Example: {"select": ["severity of .*:", {"type of event:" => "type of incident:"}]} # - "all" as type will target all type of fields. # - Caution with this! Copying between fields of different types is not yet supported. # - You may either # 1. define the `process_ooze` method calling super with a block: super.do || # 2. of define the function `custom_processing(draft, source, fields_tracker: nil)` # - You can define a callback function, `paired_fields_post_callback(src_fld, dst_fld, pairing_tracking = nil)` # - It will be called after copying by using any of the pairing methods, and allows to introduce modifications # such as selection option mappings # - You may define a method called `excluded_field_hooks` with same syntax as the json pairing file # - This will exclude as source and destination targets any fields matching those patterns. # - This is specially useful to discard rich text field guidelines (i.e. `{"rich_text" => [".*guideline:"]}`) # This case expects `options[:source][:register_id]` class Eco::API::UseCases::OozeSamples::RegisterMigrationCase < Eco::API::UseCases::OozeSamples::RegisterUpdateCase name "register-migration-case" type :other #batch_size 2 #DRY_COUNT = 4 #SOURCE_STAGE = 'name_of_source_stage' #TEMPLATE_ID = 'id_of_the_template' # whether or not it should try to pair src <-> dst ximport fields: EXCLUDE_XIMPORT = false # whether or not it should warn on unpaired destination ximport fields: REPORT_DST_XIMPORT = true # whether or not it should warn on unpaired source fields that are empty (no data loss): REPORT_SRC_EMTPY = false # whether or not it should warn on unpaired source fields that are deindexed (low potential of data loss): REPORT_SRC_DEINDEX = false include Eco::API::UseCases::OozeSamples::HelpersMigration attr_reader :csv def main(session, options, usecase, &block) if options[:dry_run] @csv = [] super(session, options, usecase, &block) else CSV.open(out_csv_filename, "w") do |csv| @csv = csv csv << ["src_page_id", "dst_page_id"] super(session, options, usecase, &block) end logger.info("File '#{out_csv_filename}' has been created.") end end # This method is expected to finalize the copying/adjustment of the draft entry. # @note At this stage, the RegisterMigration case has already populated the draft where # source and destination fields(in the draft) could be paired. # def custom_processing(draft, source, fields_tracker: nil) # draft.tags << source.tags.to_a # # More custom logics # end def process_ooze with_target_stage(self.class::SOURCE_STAGE) do |source| drafting_entry(self.class::TEMPLATE_ID) do |draft| draft_reference = object_reference(source) draft.base_tags << ["IMPORTED", "MIGRATED"] pairing_tracking = copy_pairings(source, draft) if respond_to?(:custom_processing, true) custom_processing(draft, source, fields_tracker: pairing_tracking) elsif block_given? yield(draft, source, fields_tracker: pairing_tracking) end if msg = pairing_tracking.report_src_unpaired(only_present: !self.class::REPORT_SRC_EMTPY, only_indexed: !self.class::REPORT_SRC_DEINDEX) logger.warn(msg) end if msg = pairing_tracking.report_multi_pairs(only_present: !self.class::REPORT_SRC_EMTPY, only_indexed: !self.class::REPORT_SRC_DEINDEX, dst_exclude_ximport: !self.class::REPORT_DST_XIMPORT) logger.warn(msg) end if msg = pairing_tracking.report_dst_unpaired(only_required: false, exclude_ximport: !self.class::REPORT_DST_XIMPORT) logger.warn(msg) end if page_id = create_entry(draft, reference: draft_reference) csv << [source.id, page_id] logger.info("Page '#{page_id}' created successfully.") elsif options.dig(:dry_run) logger.info("Simulated launch for #{draft_reference}") end end end end private def copy_pairings(src, dst) after_copy = callback(:paired_fields_post) params = { src_excluded: excluded_fields(src), dst_excluded: excluded_fields(dst) } copy_generic_paired_fields(src, dst, **params.merge(exclude_ximport: self.class::EXCLUDE_XIMPORT), &after_copy).tap do |pairing_tracking| copy_mapped_fields(src, dst, **params.merge(fields_tracker: pairing_tracking), &after_copy) end end def excluded_fields(entry) return [] unless respond_to?(:excluded_field_hooks, true) excluded_field_hooks.each_with_object([]) do |(type, hooks), excluded| hooks.each do |hook| type = (type == "all")? nil : type excluded.push(*with_fields(entry, label: to_regex(hook), type: type)) end end end def script_fullname msg = "You should define this method in the child class with this content:\n" msg << " @script_fullname||= File.expand_path(__FILE__)" raise msg end def copy_mapped_fields(src, dst, **kargs, &after_copy) return [] unless field_maps? field_maps.each do |type, field_hooks| type = (type == "all")? nil : type field_hooks.each do |hook_name| copy_hooked_fields(src, dst, hook: hook_name, type: type, **kargs, &after_copy) end end end def callback?(sym) respond_to?(callback_name(sym), true) end def callback(sym) if callback?(sym) method(callback_name(sym)) end end def callback_name(sym) "#{sym}_callback".to_sym end def create_entry(draft, reference:, from: self.class::TEMPLATE_ID) with_rescue(reference) do if !options[:dry_run] create_ooze(draft, template_id: from).page_id else logger.info("Creation sample follows...") dry_run_feedback(draft) false end end end def drafting_entry(template_id = selfTEMPLATE_ID) draft = apiv2.pages.get_new(template_id) yield(draft) end def with_target_stage(name) if stg = target_stage(name) yield(stg) else logger.warn("Entry '#{target.id}' does not have stage '#{name}'") end end def target_stage(name) stage(name) end def out_csv_filename @out_csv_filename ||= File.expand_path("./#{session.config.active_enviro}_#{script_basename}_#{name_timestamp}.csv") end def name_timestamp @name_timestamp ||= Time.now.strftime("%Y%m%d%H%M%S") end def field_maps @field_maps ||= field_maps? && JSON.parse(File.read(field_maps_file)) end def field_maps? File.exist?(field_maps_file) end def field_maps_file @field_maps_file ||= "#{File.join(script_dirname, script_basename)}.json" end def script_basename @script_basename ||= File.basename(script_fullname, "_case.rb") end def script_dirname @script_dirname ||= File.dirname(script_fullname) end end