diff --git a/dev/lib/product_taxonomy/commands/generate_dist_command.rb b/dev/lib/product_taxonomy/commands/generate_dist_command.rb index f31a8814b..731a3f4d0 100644 --- a/dev/lib/product_taxonomy/commands/generate_dist_command.rb +++ b/dev/lib/product_taxonomy/commands/generate_dist_command.rb @@ -27,7 +27,11 @@ def execute @locales.each { generate_dist_files(_1) } - IntegrationVersion.generate_all_distributions(output_path: OUTPUT_PATH, logger:) + IntegrationVersion.generate_all_distributions( + output_path: OUTPUT_PATH, + logger:, + current_shopify_version: @version, + ) end private diff --git a/dev/lib/product_taxonomy/models/integration_version.rb b/dev/lib/product_taxonomy/models/integration_version.rb index a21fbbd3f..22d07972b 100644 --- a/dev/lib/product_taxonomy/models/integration_version.rb +++ b/dev/lib/product_taxonomy/models/integration_version.rb @@ -11,19 +11,28 @@ class << self # Generate all distribution files for all integration versions. # # @param output_path [String] The path to the output directory. - def generate_all_distributions(output_path:, logger:, base_path: nil) - load_all_from_source(base_path:).each { _1.generate_distributions(output_path:, logger:) } + # @param logger [Logger] The logger to use for logging messages. + # @param current_shopify_version [String] The current version of the Shopify taxonomy. + # @param base_path [String] The path to the base directory containing integration versions. + def generate_all_distributions(output_path:, logger:, current_shopify_version:, base_path: nil) + integration_versions = load_all_from_source(current_shopify_version:, base_path:) + all_mappings = integration_versions.each_with_object([]) do |integration_version, all_mappings| + logger.info("Generating integration mappings for #{integration_version.name}/#{integration_version.version}") + integration_version.generate_distributions(output_path:) + all_mappings.concat(integration_version.to_json(direction: :both)) + end + generate_all_mappings_file(mappings: all_mappings, current_shopify_version:, output_path:) end # Load all integration versions from the source data directory. # # @return [Array] - def load_all_from_source(base_path: nil) + def load_all_from_source(current_shopify_version:, base_path: nil) base_path ||= File.expand_path("integrations", ProductTaxonomy::DATA_PATH) integration_versions = Dir.glob("*/*", base: base_path) integration_versions.map do |integration_version| integration_path = File.expand_path(integration_version, base_path) - load_from_source(integration_path:) + load_from_source(integration_path:, current_shopify_version:) end end @@ -31,7 +40,7 @@ def load_all_from_source(base_path: nil) # # @param integration_path [String] The path to the integration version source data directory. # @return [IntegrationVersion] - def load_from_source(integration_path:) + def load_from_source(integration_path:, current_shopify_version:) full_names = YAML.safe_load_file(File.expand_path("full_names.yml", integration_path)) full_names_by_id = full_names.each_with_object({}) { |data, hash| hash[data["id"].to_s] = data } @@ -54,25 +63,66 @@ def load_from_source(integration_path:) full_names_by_id:, from_shopify_mappings:, to_shopify_mappings:, + current_shopify_version:, ) end + + # Generate a JSON file containing all mappings for all integration versions. + # + # @param mappings [Array] The mappings to include in the file. + # @param version [String] The current version of the Shopify taxonomy. + # @param output_path [String] The path to the output directory. + def generate_all_mappings_file(mappings:, current_shopify_version:, output_path:) + File.write( + File.expand_path("all_mappings.json", integrations_output_path(output_path)), + JSON.pretty_generate(to_json(mappings:, current_shopify_version:)), + ) + end + + # Generate a JSON representation for a given set of mappings and version of the Shopify taxonomy. + # + # @param version [String] The current version of the Shopify taxonomy. + # @param mappings [Array] The mappings to include in the file. + # @return [Hash] + def to_json(current_shopify_version:, mappings:) + { + version: current_shopify_version, + mappings:, + } + end + + # Generate the path to the integrations output directory. + # + # @param base_output_path [String] The base path to the output directory. + # @return [String] + def integrations_output_path(base_output_path) + File.expand_path("en/integrations", base_output_path) + end end - attr_reader :name, :version + attr_reader :name, :version, :from_shopify_mappings, :to_shopify_mappings - def initialize(name:, version:, full_names_by_id:, from_shopify_mappings: nil, to_shopify_mappings: nil) + def initialize( + name:, + version:, + full_names_by_id:, + current_shopify_version:, + from_shopify_mappings: nil, + to_shopify_mappings: nil + ) @name = name @version = version @full_names_by_id = full_names_by_id + @current_shopify_version = current_shopify_version @from_shopify_mappings = from_shopify_mappings @to_shopify_mappings = to_shopify_mappings + @to_json = {} # memoized by direction end # Generate all distribution files for the integration version. # # @param output_path [String] The path to the output directory. - def generate_distributions(output_path:, logger:) - logger.info("Generating integration mappings for #{@name}/#{@version}") + def generate_distributions(output_path:) generate_distribution(output_path:, direction: :from_shopify) if @from_shopify_mappings.present? generate_distribution(output_path:, direction: :to_shopify) if @to_shopify_mappings.present? end @@ -82,12 +132,13 @@ def generate_distributions(output_path:, logger:) # @param output_path [String] The path to the output directory. # @param direction [Symbol] The direction of the distribution file to generate (:from_shopify or :to_shopify). def generate_distribution(output_path:, direction:) - output_dir = File.expand_path("en/integrations/#{@name}", output_path) + output_dir = File.expand_path(@name, self.class.integrations_output_path(output_path)) FileUtils.mkdir_p(output_dir) + json = self.class.to_json(mappings: [to_json(direction:)], current_shopify_version: @current_shopify_version) File.write( File.expand_path("#{distribution_filename(direction:)}.json", output_dir), - JSON.pretty_generate(to_json(direction:)), + JSON.pretty_generate(json), ) File.write( File.expand_path("#{distribution_filename(direction:)}.txt", output_dir), @@ -98,17 +149,22 @@ def generate_distribution(output_path:, direction:) # Generate a JSON representation of the integration version for a single direction. # # @param direction [Symbol] The direction of the distribution file to generate (:from_shopify or :to_shopify). - # @return [Hash] + # @return [Hash, Array, nil] def to_json(direction:) - mappings = direction == :from_shopify ? @from_shopify_mappings : @to_shopify_mappings - { - version: current_shopify_version, - mappings: [{ - input_taxonomy: input_name_and_version(direction:), - output_taxonomy: output_name_and_version(direction:), - rules: mappings.map(&:to_json), - }], - } + if @to_json.key?(direction) + @to_json[direction] + elsif direction == :both + [to_json(direction: :from_shopify), to_json(direction: :to_shopify)].compact + else + mappings = direction == :from_shopify ? @from_shopify_mappings : @to_shopify_mappings + @to_json[direction] = if mappings.present? + { + input_taxonomy: input_name_and_version(direction:), + output_taxonomy: output_name_and_version(direction:), + rules: mappings.map(&:to_json), + } + end + end end # Generate a TXT representation of the integration version for a single direction. @@ -141,11 +197,7 @@ def integration_name_and_version end def shopify_name_and_version - "shopify/#{current_shopify_version}" - end - - def current_shopify_version - @current_shopify_version ||= File.read(File.expand_path("../VERSION", ProductTaxonomy::DATA_PATH)).strip + "shopify/#{@current_shopify_version}" end def input_name_and_version(direction:) diff --git a/dev/test/models/integration_version_test.rb b/dev/test/models/integration_version_test.rb index 7fc3dd692..97b3faffe 100644 --- a/dev/test/models/integration_version_test.rb +++ b/dev/test/models/integration_version_test.rb @@ -12,11 +12,14 @@ class IntegrationVersionTest < ActiveSupport::TestCase ap.add_child(ap_1) Category.add(ap) Category.add(ap_1) + @current_shopify_version = "2025-01-unstable" @shopify_integration = IntegrationVersion.load_from_source( integration_path: File.expand_path("integrations/shopify/2020-01", DATA_PATH), + current_shopify_version: @current_shopify_version, ) @external_integration = IntegrationVersion.load_from_source( integration_path: File.expand_path("integrations/foocommerce/1.0.0", DATA_PATH), + current_shopify_version: @current_shopify_version, ) end @@ -27,7 +30,10 @@ class IntegrationVersionTest < ActiveSupport::TestCase test "load_all_from_source loads all integration versions" do integrations_path = File.expand_path("integrations", DATA_PATH) - integration_versions = IntegrationVersion.load_all_from_source(base_path: integrations_path) + integration_versions = IntegrationVersion.load_all_from_source( + base_path: integrations_path, + current_shopify_version: @current_shopify_version, + ) assert_equal 2, integration_versions.size assert_equal "foocommerce", integration_versions.first.name @@ -38,72 +44,66 @@ class IntegrationVersionTest < ActiveSupport::TestCase test "to_json returns the JSON representation of a mapping to Shopify" do expected_json = { - version: "2025-01-unstable", - mappings: [{ - input_taxonomy: "shopify/2020-01", - output_taxonomy: "shopify/2025-01-unstable", - rules: [ - { - input: { category: { "id" => 1, "full_name" => "Animals & Pet Supplies (old shopify)" } }, - output: { - category: [{ - id: "gid://shopify/TaxonomyCategory/ap", - full_name: "Animals & Pet Supplies", - }], - }, + input_taxonomy: "shopify/2020-01", + output_taxonomy: "shopify/2025-01-unstable", + rules: [ + { + input: { category: { "id" => 1, "full_name" => "Animals & Pet Supplies (old shopify)" } }, + output: { + category: [{ + id: "gid://shopify/TaxonomyCategory/ap", + full_name: "Animals & Pet Supplies", + }], }, - { - input: { - category: { - "id" => 2, - "full_name" => "Animals & Pet Supplies > Live Animals (old shopify)", - }, - }, - output: { - category: [{ - id: "gid://shopify/TaxonomyCategory/ap-1", - full_name: "Animals & Pet Supplies > Live Animals", - }], + }, + { + input: { + category: { + "id" => 2, + "full_name" => "Animals & Pet Supplies > Live Animals (old shopify)", }, }, - ], - }], + output: { + category: [{ + id: "gid://shopify/TaxonomyCategory/ap-1", + full_name: "Animals & Pet Supplies > Live Animals", + }], + }, + }, + ], } assert_equal expected_json, @shopify_integration.to_json(direction: :to_shopify) end test "to_json returns the JSON representation of a mapping from Shopify" do expected_json = { - version: "2025-01-unstable", - mappings: [{ - input_taxonomy: "shopify/2025-01-unstable", - output_taxonomy: "foocommerce/1.0.0", - rules: [ - { - input: { category: { id: "gid://shopify/TaxonomyCategory/ap", full_name: "Animals & Pet Supplies" } }, - output: { - category: [{ - "id" => 1, - "full_name" => "Animals & Pet Supplies (foocommerce)", - }], - }, + input_taxonomy: "shopify/2025-01-unstable", + output_taxonomy: "foocommerce/1.0.0", + rules: [ + { + input: { category: { id: "gid://shopify/TaxonomyCategory/ap", full_name: "Animals & Pet Supplies" } }, + output: { + category: [{ + "id" => 1, + "full_name" => "Animals & Pet Supplies (foocommerce)", + }], }, - { - input: { - category: { - id: "gid://shopify/TaxonomyCategory/ap-1", - full_name: "Animals & Pet Supplies > Live Animals", - }, - }, - output: { - category: [{ - "id" => 2, - "full_name" => "Animals & Pet Supplies > Live Animals (foocommerce)", - }], + }, + { + input: { + category: { + id: "gid://shopify/TaxonomyCategory/ap-1", + full_name: "Animals & Pet Supplies > Live Animals", }, }, - ], - }], + output: { + category: [{ + "id" => 2, + "full_name" => "Animals & Pet Supplies > Live Animals (foocommerce)", + }], + }, + }, + ], } assert_equal expected_json, @external_integration.to_json(direction: :from_shopify) end @@ -142,9 +142,13 @@ class IntegrationVersionTest < ActiveSupport::TestCase test "generate_distribution generates the distribution files for a mapping to Shopify" do FileUtils.expects(:mkdir_p).with("/tmp/fake/en/integrations/shopify") + expected_shopify_json = { + version: "2025-01-unstable", + mappings: [@shopify_integration.to_json(direction: :to_shopify)], + } File.expects(:write).with( "/tmp/fake/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.json", - JSON.pretty_generate(@shopify_integration.to_json(direction: :to_shopify)), + JSON.pretty_generate(expected_shopify_json), ) File.expects(:write).with( "/tmp/fake/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.txt", @@ -158,9 +162,13 @@ class IntegrationVersionTest < ActiveSupport::TestCase test "generate_distribution generates the distribution files for a mapping from Shopify" do FileUtils.expects(:mkdir_p).with("/tmp/fake/en/integrations/foocommerce") + expected_external_json = { + version: "2025-01-unstable", + mappings: [@external_integration.to_json(direction: :from_shopify)], + } File.expects(:write).with( "/tmp/fake/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.json", - JSON.pretty_generate(@external_integration.to_json(direction: :from_shopify)), + JSON.pretty_generate(expected_external_json), ) File.expects(:write).with( "/tmp/fake/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.txt", @@ -174,12 +182,77 @@ class IntegrationVersionTest < ActiveSupport::TestCase test "generate_distributions only calls generate_distribution with to_shopify for Shopify integration version" do @shopify_integration.expects(:generate_distribution).with(output_path: "/tmp/fake", direction: :to_shopify) - @shopify_integration.generate_distributions(output_path: "/tmp/fake", logger: stub("logger", info: nil)) + @shopify_integration.generate_distributions(output_path: "/tmp/fake") end test "generate_distributions only calls generate_distribution with from_shopify for external integration version" do @external_integration.expects(:generate_distribution).with(output_path: "/tmp/fake", direction: :from_shopify) - @external_integration.generate_distributions(output_path: "/tmp/fake", logger: stub("logger", info: nil)) + @external_integration.generate_distributions(output_path: "/tmp/fake") + end + + test "IntegrationVersion.to_json returns the JSON representation of a list of mappings" do + mappings = [ + { + input_taxonomy: "shopify/2020-01", + output_taxonomy: "shopify/2025-01-unstable", + rules: [ + { + input: { category: { id: 1, full_name: "Animals & Pet Supplies (old shopify)" } }, + }, + ], + }, + ] + expected_json = { + version: "2025-01-unstable", + mappings:, + } + assert_equal expected_json, IntegrationVersion.to_json(mappings:, current_shopify_version: "2025-01-unstable") + end + + test "generate_all_distributions generates all_mappings.json and distribution files for all integration versions" do + FileUtils.expects(:mkdir_p).with("/tmp/fake/en/integrations/foocommerce") + FileUtils.expects(:mkdir_p).with("/tmp/fake/en/integrations/shopify") + expected_external_json = IntegrationVersion.to_json( + mappings: [@external_integration.to_json(direction: :from_shopify)], + current_shopify_version: @current_shopify_version, + ) + File.expects(:write).with( + "/tmp/fake/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.json", + JSON.pretty_generate(expected_external_json), + ) + File.expects(:write).with( + "/tmp/fake/en/integrations/foocommerce/shopify_2025-01_to_foocommerce_1.0.0.txt", + @external_integration.to_txt(direction: :from_shopify), + ) + expected_shopify_json = IntegrationVersion.to_json( + mappings: [@shopify_integration.to_json(direction: :to_shopify)], + current_shopify_version: @current_shopify_version, + ) + File.expects(:write).with( + "/tmp/fake/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.json", + JSON.pretty_generate(expected_shopify_json), + ) + File.expects(:write).with( + "/tmp/fake/en/integrations/shopify/shopify_2020-01_to_shopify_2025-01.txt", + @shopify_integration.to_txt(direction: :to_shopify), + ) + all_mappings_json = IntegrationVersion.to_json( + mappings: [ + @external_integration.to_json(direction: :from_shopify), + @shopify_integration.to_json(direction: :to_shopify), + ], + current_shopify_version: @current_shopify_version, + ) + File.expects(:write).with( + "/tmp/fake/en/integrations/all_mappings.json", + JSON.pretty_generate(all_mappings_json), + ) + IntegrationVersion.generate_all_distributions( + output_path: "/tmp/fake", + current_shopify_version: @current_shopify_version, + logger: stub("logger", info: nil), + base_path: File.expand_path("integrations", DATA_PATH), + ) end end end