Skip to content

Commit

Permalink
Add command to find unmapped external categories (#504)
Browse files Browse the repository at this point in the history
Added a new CLI command to identify external taxonomy categories that aren't mapped from the Shopify taxonomy. This can be useful for helping identify gaps in the current taxonomy version.

- Added a new `unmapped_external_categories` command to the CLI
- Created `FindUnmappedExternalCategoriesCommand` to handle the logic
- Added `unmapped_external_category_ids` method to `IntegrationVersion` to identify unmapped categories
- Modified `Command` class to accept arguments in `run` and `execute` methods
- Made `current_shopify_version` parameter optional in relevant methods

### Tophatting
1. Run the new CLI command:
```bash
bin/product_taxonomy unmapped_external_categories [integration_name]/[version]
```
2. Verify the output shows a list of category names that aren't mapped from the Shopify taxonomy
  • Loading branch information
danielpgross authored Dec 16, 2024
2 parents e765b01 + 857f3dd commit 957fe66
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 8 deletions.
1 change: 1 addition & 0 deletions dev/lib/product_taxonomy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ module ProductTaxonomy
require_relative "product_taxonomy/models/integration_version"
require_relative "product_taxonomy/commands/command"
require_relative "product_taxonomy/commands/generate_dist_command"
require_relative "product_taxonomy/commands/find_unmapped_external_categories_command"
7 changes: 7 additions & 0 deletions dev/lib/product_taxonomy/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,12 @@ class Cli < Thor
def dist
GenerateDistCommand.new(options).run
end

desc "unmapped_external_categories",
"Find categories in an external taxonomy that are not mapped from the Shopify taxonomy"
option :external_taxonomy, type: :string, desc: "The external taxonomy to find unmapped categories in"
def unmapped_external_categories(name_and_version)
FindUnmappedExternalCategoriesCommand.new(options).run(name_and_version)
end
end
end
6 changes: 3 additions & 3 deletions dev/lib/product_taxonomy/commands/command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ def initialize(options)
@logger.level = :error if options[:quiet]
end

def run
def run(...)
elapsed = Benchmark.realtime do
execute
execute(...)
end
logger.info("Completed in #{elapsed.round(2)} seconds")
end

def execute
def execute(...)
raise NotImplementedError, "#{self.class}#execute must be implemented"
end

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module ProductTaxonomy
class FindUnmappedExternalCategoriesCommand < Command
def execute(name_and_version)
load_taxonomy # not actually used in this operation, but required by IntegrationVersion to resolve categories

unless name_and_version.match?(%r{\A[a-z0-9_-]+/[^/]+\z})
raise ArgumentError, "Invalid format. Expected 'name/version', got: #{name_and_version}"
end

# Load relevant IntegrationVersion using CLI argument
integration_path = File.join(IntegrationVersion::INTEGRATIONS_PATH, name_and_version)
integration_version = IntegrationVersion.load_from_source(integration_path:)

# Output the unmapped external categories
integration_version.unmapped_external_category_ids.each do |id|
puts integration_version.full_names_by_id[id]["full_name"]
end
end
end
end
21 changes: 16 additions & 5 deletions dev/lib/product_taxonomy/models/integration_version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class << self
# @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: INTEGRATIONS_PATH)
def generate_all_distributions(output_path:, logger:, current_shopify_version: nil, base_path: INTEGRATIONS_PATH)
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}")
Expand All @@ -29,7 +29,7 @@ def generate_all_distributions(output_path:, logger:, current_shopify_version:,
# Load all integration versions from the source data directory.
#
# @return [Array<IntegrationVersion>]
def load_all_from_source(current_shopify_version:, base_path: INTEGRATIONS_PATH)
def load_all_from_source(current_shopify_version: nil, base_path: INTEGRATIONS_PATH)
integrations = YAML.safe_load_file(File.expand_path("integrations.yml", base_path))
integrations.pluck("available_versions").flatten.map do |integration_version|
integration_path = File.expand_path(integration_version, base_path)
Expand All @@ -41,7 +41,7 @@ def load_all_from_source(current_shopify_version:, base_path: INTEGRATIONS_PATH)
#
# @param integration_path [String] The path to the integration version source data directory.
# @return [IntegrationVersion]
def load_from_source(integration_path:, current_shopify_version:)
def load_from_source(integration_path:, current_shopify_version: nil)
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 }

Expand Down Expand Up @@ -101,7 +101,7 @@ def integrations_output_path(base_output_path)
end
end

attr_reader :name, :version, :from_shopify_mappings, :to_shopify_mappings
attr_reader :name, :version, :from_shopify_mappings, :to_shopify_mappings, :full_names_by_id

# @param name [String] The name of the integration.
# @param version [String] The version of the integration.
Expand All @@ -115,7 +115,7 @@ def initialize(
name:,
version:,
full_names_by_id:,
current_shopify_version:,
current_shopify_version: nil,
from_shopify_mappings: nil,
to_shopify_mappings: nil
)
Expand Down Expand Up @@ -155,6 +155,17 @@ def generate_distribution(output_path:, direction:)
)
end

# For a mapping to an external taxonomy, get the IDs of external categories that are not mapped from Shopify.
#
# @return [Array<String>] IDs of external categories not mapped from the Shopify taxonomy. Empty if there are no
# mappings from Shopify.
def unmapped_external_category_ids
return [] if @from_shopify_mappings.blank?

mappings_by_output_category_id = @from_shopify_mappings.index_by { _1.output_category["id"].to_s }
@full_names_by_id.keys - mappings_by_output_category_id.keys
end

# 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).
Expand Down
23 changes: 23 additions & 0 deletions dev/test/models/integration_version_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -254,5 +254,28 @@ class IntegrationVersionTest < TestCase
base_path: File.expand_path("integrations", DATA_PATH),
)
end

test "unmapped_external_category_ids returns IDs of external categories not mapped from the Shopify taxonomy" do
external_category1 = { "id" => "1", "full_name" => "External category 1" }
from_shopify_mappings = [
MappingRule.new(
input_category: Category.new(id: "aa", name: "aa"),
output_category: external_category1,
),
]
full_names_by_id = {
"1" => external_category1,
"2" => { "id" => "2", "full_name" => "External category 2" },
"3" => { "id" => "3", "full_name" => "External category 3" },
}
integration_version = IntegrationVersion.new(
name: "test",
version: "1.0.0",
from_shopify_mappings:,
full_names_by_id:,
)

assert_equal ["2", "3"], integration_version.unmapped_external_category_ids
end
end
end

0 comments on commit 957fe66

Please sign in to comment.