shopify_transporter 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/README.md +39 -31
  4. data/exe/shopify_transporter +33 -2
  5. data/lib/shopify_transporter/exporters/exporter.rb +103 -0
  6. data/lib/shopify_transporter/exporters/magento/customer_exporter.rb +44 -0
  7. data/lib/shopify_transporter/exporters/magento/database_cache.rb +21 -0
  8. data/lib/shopify_transporter/exporters/magento/database_table_exporter.rb +68 -0
  9. data/lib/shopify_transporter/exporters/magento/magento_exporter.rb +28 -0
  10. data/lib/shopify_transporter/exporters/magento/order_exporter.rb +46 -0
  11. data/lib/shopify_transporter/exporters/magento/product_exporter.rb +129 -0
  12. data/lib/shopify_transporter/exporters/magento/product_options.rb +98 -0
  13. data/lib/shopify_transporter/exporters/magento/soap.rb +101 -0
  14. data/lib/shopify_transporter/exporters/magento/sql.rb +38 -0
  15. data/lib/shopify_transporter/exporters/magento.rb +5 -0
  16. data/lib/shopify_transporter/exporters.rb +3 -0
  17. data/lib/shopify_transporter/generators/generate.rb +4 -4
  18. data/lib/shopify_transporter/generators/new.rb +2 -2
  19. data/lib/shopify_transporter/pipeline/magento/order/line_items.rb +7 -5
  20. data/lib/shopify_transporter/pipeline/magento/product/top_level_attributes.rb +73 -4
  21. data/lib/shopify_transporter/pipeline/magento/product/top_level_variant_attributes.rb +50 -0
  22. data/lib/shopify_transporter/pipeline/magento/product/variant_attributes.rb +28 -0
  23. data/lib/shopify_transporter/pipeline/magento/product/variant_image.rb +50 -0
  24. data/lib/shopify_transporter/record_builder/product_record_builder.rb +33 -0
  25. data/lib/shopify_transporter/{record_builder.rb → record_builder/record_builder.rb} +0 -0
  26. data/lib/shopify_transporter/shopify/attributes_helpers.rb +1 -1
  27. data/lib/shopify_transporter/shopify/order.rb +1 -1
  28. data/lib/shopify_transporter/shopify/product.rb +4 -3
  29. data/lib/shopify_transporter/shopify/record.rb +1 -1
  30. data/lib/shopify_transporter/version.rb +1 -1
  31. data/lib/shopify_transporter.rb +15 -7
  32. data/lib/tasks/factory_bot.rake +1 -1
  33. data/lib/templates/magento/config.tt +19 -0
  34. data/shopify_transporter.gemspec +3 -0
  35. metadata +61 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e731ee32bf65a13db82226613fcdfe4de06e5dcd
4
- data.tar.gz: 6f52f02f93b67cc3bb96a709ccc895126bd2dfe6
3
+ metadata.gz: 51ea8531c355faaa542a5cac769df64be88ab1c7
4
+ data.tar.gz: 8b46bab5dade9319600c9da397967f55aa7724ad
5
5
  SHA512:
6
- metadata.gz: 624777e136f3cb0d659935f6fbe5152ccba71e17fa38e0b884bad43074f5e730315916aa52250758255dd28aec3c47fabc2042567f22b5a217a40d6a5ed47c2c
7
- data.tar.gz: 505991f408682f301e7af3be5e4805e74ed2130bbb3c8ed3d42ae85ec1f5159bf6ea49350979e374924163d85ddb0db9fc43432415b5ad6e096129405a3a55dd
6
+ metadata.gz: 60200c955aa3dd02d952835992f0b59e0cf1a8667daf45f7907ec346ed0b6df0b10cd3ff1b466d3a989c4ebf37bff1668a348f932e3308cbc6e31454107f0b87
7
+ data.tar.gz: ff9d1c553e7617b9d84ceb0c933bbcfa3477a91e13e92986ef0e93f953ad69c030f5f92e5e5e90825f74e7b6fd672593a5b074fccb5873009f4b6e70a7a552b2
data/Gemfile CHANGED
@@ -6,7 +6,7 @@ gemspec
6
6
 
7
7
  group :development do
8
8
  gem 'package_cloud'
9
- gem 'rubocop'
9
+ gem 'rubocop', '~> 0.56.0'
10
10
  gem 'rubocop-git', '~> 0.1'
11
11
  end
12
12
 
data/README.md CHANGED
@@ -9,29 +9,25 @@ data, will be made available in the near future.
9
9
 
10
10
  Note: the Transporter app is available to Shopify Plus plans only.
11
11
 
12
- ## Installation
12
+ ## Submitting Issues
13
13
 
14
- ### Building and installing locally
15
- To build locally run the following command:
14
+ Please open an issue here if you encounter a specific bug with this library or if something is documented
15
+ incorrectly.
16
16
 
17
- ```
18
- $ bundle exec rake build
17
+ When filing an issue, please ensure that:
19
18
 
20
- shopify_transporter 1.0.0 built to pkg/shopify_transporter-1.0.0.gem.
21
- ```
19
+ - The issue is not already covered in another open issue
20
+ - The issue does not contain any confidential or personally identifiable information
21
+ - The issue is specifically regarding the `shopify_transporter` gem and not related to the Transporter App.
22
22
 
23
- Then, you can install the gem system-wide by running:
24
23
 
25
- ```
26
- $ gem install pkg/shopify_transporter-1.0.0.gem
24
+ ## Installation
27
25
 
28
- Successfully installed shopify_transporter-1.0.0
29
- Parsing documentation for shopify_transporter-1.0.0
30
- Installing ri documentation for shopify_transporter-1.0.0
31
- Done installing documentation for shopify_transporter after 0 seconds
32
- 1 gem installed
33
- ```
34
- Your locally built gem is now installed system-wide.
26
+ ### Requirements:
27
+ 1. A Ruby version of 2.4.0 or higher
28
+ 2. You are able to install Ruby gems. Please visit [the Bundler website](https://bundler.io/) to troubleshoot issues with installing gems.
29
+
30
+ We test and support the gem for Mac OS environments. While not officially supported, the gem may work on other operating systems provided they meet the above requirements.
35
31
 
36
32
  ### Installing the Transporter tool gem from rubygems:
37
33
 
@@ -60,7 +56,7 @@ Commands:
60
56
  shopify_transporter new PROJECTNAME --platform=PLATFORM # Generates a new project structure for a platform
61
57
  ```
62
58
 
63
- ## Create a conversion project
59
+ ### Create a conversion project
64
60
 
65
61
  It's convenient to create a conversion project for each store that you want to migrate to Shopify.
66
62
  To create it, use the `new` sub-command:
@@ -96,7 +92,7 @@ $ bundle install
96
92
  ```
97
93
 
98
94
 
99
- ## Convert records from the third-party platform to Shopify
95
+ ### Convert records from the third-party platform to Shopify
100
96
 
101
97
  Run `shopify_transporter` and use the `convert` command to convert your objects from the
102
98
  third-party platform to the Shopify format. For example, the following command converts a
@@ -109,7 +105,7 @@ shopify_transporter convert --config=config.yml --object=customer magento_custom
109
105
  In this example, the converted customer objects are saved to *shopify_customers.csv*. If errors occur during
110
106
  the conversion, then they appear in your terminal.
111
107
 
112
- ## Convert multiple files
108
+ ### Convert multiple files
113
109
 
114
110
  To convert multiple files to Shopify, separate the file names with a space:
115
111
 
@@ -117,7 +113,7 @@ To convert multiple files to Shopify, separate the file names with a space:
117
113
  shopify_transporter convert --config=config.yml --object=customer magento_customers_1.json magento_customers_2.json ...
118
114
  ```
119
115
 
120
- ## Configuration file (_config.yml_)
116
+ ### Configuration file (_config.yml_)
121
117
 
122
118
  The configuraton file is generated when you create your conversion project. This file is specific to the
123
119
  third-party platform that you are converting to Shopify.
@@ -209,7 +205,6 @@ Existing pipeline stages and the attributes are populated below.
209
205
 
210
206
  ### Magento v1.x customer
211
207
 
212
-
213
208
  ### AddressesAttribute
214
209
 
215
210
  Addresses are built from the `shipping_` and `billing_` prefixed fields. The Shopify object's `addresses`
@@ -266,25 +261,38 @@ A file named `your_custom_stage.rb` is added to the `lib/magento/custom_pipeline
266
261
  The `convert` command currently only converts customer and order JSON objects that have been exported by SOAP API
267
262
  from Magento 1.x.
268
263
 
269
- ## Running unit tests
264
+ ## Contributing
265
+
266
+ ### Running unit tests
270
267
 
271
268
  We use rspec to run our test suite:
272
269
  `bundle exec rspec`
273
270
 
274
- ## Running the linter
271
+ ### Running the linter
275
272
 
276
273
  It's important that all of the code introduced passes linting. To run it manually:
277
274
  `bundle exec rake rubocop`
278
275
  To automatically resolve any basic linting issues:
279
276
  `bundle exec rake rubocop:autocorrect`
280
277
 
281
- # Submitting Issues
278
+ ### Building and installing locally
279
+ To build locally run the following command:
282
280
 
283
- Please open an issue here if you encounter a specific bug with this library or if something is documented
284
- incorrectly.
281
+ ```
282
+ $ bundle exec rake build
285
283
 
286
- When filing an issue, please ensure that:
284
+ shopify_transporter 1.0.0 built to pkg/shopify_transporter-1.0.0.gem.
285
+ ```
287
286
 
288
- - The issue is not already covered in another open issue
289
- - The issue does not contain any confidential or personally identifying information
290
- - The issue is specifically regarding the `shopify_transporter` gem and not related to the Transporter App.
287
+ Then, you can install the gem system-wide by running:
288
+
289
+ ```
290
+ $ gem install pkg/shopify_transporter-1.0.0.gem
291
+
292
+ Successfully installed shopify_transporter-1.0.0
293
+ Parsing documentation for shopify_transporter-1.0.0
294
+ Installing ri documentation for shopify_transporter-1.0.0
295
+ Done installing documentation for shopify_transporter after 0 seconds
296
+ 1 gem installed
297
+ ```
298
+ Your locally built gem is now installed system-wide.
@@ -7,6 +7,8 @@ require 'thor'
7
7
  require 'thor/group'
8
8
  require 'shopify_transporter'
9
9
  require 'yaml'
10
+ require 'io/console'
11
+ require 'active_support/core_ext/string'
10
12
 
11
13
  module ShopifyTransporter
12
14
  class CLI < Thor
@@ -15,9 +17,9 @@ module ShopifyTransporter
15
17
  end
16
18
 
17
19
  method_option :config, type: :string, default: 'config.yml',
18
- desc: "The config file that lists the conversions to run."
20
+ desc: 'The config file that lists the conversions to run.'
19
21
  method_option :object, type: :string, required: true,
20
- desc: "The object type (customer, product, or order) to convert.",
22
+ desc: 'The object type (customer, product, or order) to convert.',
21
23
  enum: %w(customer product order)
22
24
 
23
25
  desc 'convert FILE_NAMES', 'Converts objects into a Shopify-format. (accepts a list of space-separated files).'
@@ -29,15 +31,44 @@ module ShopifyTransporter
29
31
  conversion_error(error.message)
30
32
  end
31
33
 
34
+ method_option :config, type: :string, default: 'config.yml',
35
+ desc: 'The config file that describes the third party platform to extract from.'
36
+ method_option :object, type: :string, required: true,
37
+ desc: 'The object type (customer, product, or order) to extract.',
38
+ enum: %w(customer product order)
39
+ method_option :first_id, type: :numeric, required: true,
40
+ desc: 'The first product_id, customer_id, or order_id for the range of objects you are trying to export.'
41
+ method_option :last_id, type: :numeric, required: true,
42
+ desc: 'The last product_id, customer_id, or order_id (inclusive) for the range of objects you are trying' \
43
+ ' to export.'
44
+ method_option :batch_size, type: :numeric, default: 1000,
45
+ desc: 'The number of records that are retrieved from the source platform at one time. ' \
46
+ 'Use a smaller batch size if you are running into performance issues extracting your data.'
47
+
48
+ desc 'extract', 'Extracts objects from a third-party platform into a format compatible for conversion.'
49
+ def extract
50
+ batch_config = options.slice('first_id', 'last_id', 'batch_size')
51
+ exporter = Exporters::Exporter.new(options[:config], options[:object], batch_config)
52
+ exporter.run
53
+ rescue Exporters::ExportError => error
54
+ extract_error(error.message)
55
+ end
56
+
32
57
  no_commands do
33
58
  def conversion_error(message)
34
59
  say("Conversion error: #{message}", :red)
35
60
  exit 1
36
61
  end
62
+
63
+ def extract_error(message)
64
+ say("Extract error: #{message}", :red)
65
+ exit 1
66
+ end
37
67
  end
38
68
 
39
69
  register ShopifyTransporter::New,
40
70
  'new', 'new PROJECTNAME --platform=PLATFORM', 'Generate a project for the platform'
71
+
41
72
  register ShopifyTransporter::Generate,
42
73
  'generate', 'generate STAGE_NAME --object=OBJECT',
43
74
  'Generate a custom pipeline stage for the object'
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyTransporter
4
+ module Exporters
5
+ class ExportError < StandardError; end
6
+
7
+ class InvalidConfigError < ExportError
8
+ def initialize(error_message)
9
+ super("Invalid configuration: #{error_message}")
10
+ end
11
+ end
12
+
13
+ class Exporter
14
+ def initialize(config_filename, object_type, batch_config)
15
+ @object_type = object_type
16
+ @batch_config = batch_config
17
+
18
+ load_config(config_filename)
19
+ end
20
+
21
+ def run
22
+ print_exported_objects
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :config, :object_type
28
+
29
+ def print_exported_objects
30
+ $stderr.puts 'Starting extraction...'
31
+ puts '['
32
+ first = true
33
+ object_exporter.export do |object|
34
+ puts ',' unless first
35
+ first = false
36
+ $stderr.puts "Extracting #{object_type}: #{object[object_exporter.key]}..."
37
+ print ' ' + JSON.pretty_generate(object, object_nl: "\n ")
38
+ end
39
+ ensure
40
+ print "\n]"
41
+ end
42
+
43
+ def object_exporter
44
+ @exporter ||= Magento::MagentoExporter
45
+ .for(type: object_type)
46
+ .new(
47
+ soap_client: soap_client,
48
+ database_adapter: database_adapter
49
+ )
50
+ end
51
+
52
+ def soap_client
53
+ Magento::Soap.new(
54
+ hostname: config['extract_configuration']['soap']['hostname'],
55
+ username: config['extract_configuration']['soap']['username'],
56
+ api_key: config['extract_configuration']['soap']['api_key'],
57
+ batch_config: @batch_config
58
+ )
59
+ end
60
+
61
+ def database_adapter
62
+ Magento::SQL.new(
63
+ database: config['extract_configuration']['database']['database'],
64
+ host: config['extract_configuration']['database']['host'],
65
+ user: config['extract_configuration']['database']['user'],
66
+ port: config['extract_configuration']['database']['port'],
67
+ password: config['extract_configuration']['database']['password'],
68
+ )
69
+ end
70
+
71
+ def load_config(config_filename)
72
+ @config ||= begin
73
+ raise InvalidConfigError, "cannot find file name '#{config_filename}'" unless File.exist?(config_filename)
74
+ YAML.load_file(config_filename)
75
+ end
76
+ ensure_config_has_required_keys
77
+ end
78
+
79
+ def ensure_config_has_required_keys
80
+ base_required_keys = [
81
+ %w(extract_configuration),
82
+ %w(extract_configuration soap hostname),
83
+ %w(extract_configuration soap username),
84
+ %w(extract_configuration soap api_key),
85
+ ]
86
+
87
+ product_required_keys = [
88
+ %w(extract_configuration database host),
89
+ %w(extract_configuration database port),
90
+ %w(extract_configuration database database),
91
+ %w(extract_configuration database user),
92
+ %w(extract_configuration database password),
93
+ ]
94
+
95
+ required_keys = base_required_keys + (@object_type == 'product' ? product_required_keys : [])
96
+
97
+ required_keys.each do |keys|
98
+ raise InvalidConfigError, "missing required key '#{keys.join(' > ')}'" unless config.dig(*keys)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyTransporter
4
+ module Exporters
5
+ module Magento
6
+ class CustomerExporter
7
+ def initialize(soap_client: nil, database_adapter: nil)
8
+ @client = soap_client
9
+ @database_adapter = database_adapter
10
+ end
11
+
12
+ def key
13
+ :customer_id
14
+ end
15
+
16
+ def export
17
+ base_customers.each do |customer|
18
+ yield with_attributes(customer)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def with_attributes(base_customer)
25
+ base_customer.merge(address_list: customer_address_list(base_customer[:customer_id]))
26
+ end
27
+
28
+ def base_customers
29
+ Enumerator.new do |enumerator|
30
+ @client.call_in_batches(method: :customer_customer_list, batch_index_column: 'customer_id').each do |batch|
31
+ result = batch.body[:customer_customer_list_response][:store_view][:item] || []
32
+ result = [result] unless result.is_a? Array
33
+ result.each { |customer| enumerator << customer }
34
+ end
35
+ end
36
+ end
37
+
38
+ def customer_address_list(customer_id)
39
+ @client.call(:customer_address_list, customer_id: customer_id).body
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ require 'csv'
3
+ require 'sequel'
4
+
5
+ module ShopifyTransporter
6
+ module Exporters
7
+ module Magento
8
+ class DatabaseCache
9
+ DB_CACHE_FOLDER = './cache/magento_db'
10
+
11
+ def initialize
12
+ @cache = {}
13
+ end
14
+
15
+ def table(table_name)
16
+ @cache[table_name] ||= CSV.read("#{DB_CACHE_FOLDER}/#{table_name}.csv", headers: true)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+ require 'sequel'
3
+
4
+ module ShopifyTransporter
5
+ module Exporters
6
+ module Magento
7
+ class DatabaseTableExporter
8
+ BATCH_SIZE = 1000
9
+ DB_CACHE_FOLDER = './cache/magento_db'
10
+
11
+ def initialize(database_adapter)
12
+ @database_adapter = database_adapter
13
+ FileUtils.mkdir_p(DB_CACHE_FOLDER)
14
+ end
15
+
16
+ def export_table(table_name, index_column)
17
+ export_file_path = "#{DB_CACHE_FOLDER}/#{table_name}.csv"
18
+
19
+ return if File.file? export_file_path
20
+
21
+ index_key = index_column.to_sym
22
+
23
+ @database_adapter.connect do |db|
24
+ ordered_table = db
25
+ .from(table_name)
26
+ .order(index_key)
27
+
28
+ headers = ordered_table.columns
29
+
30
+ write_headers(export_file_path, headers)
31
+
32
+ in_batches(ordered_table, index_key) do |batch|
33
+ write_batch(export_file_path, headers, batch)
34
+ end
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def write_headers(export_file_path, headers)
41
+ File.open(export_file_path, 'w') do |file|
42
+ file << headers.to_csv
43
+ end
44
+ end
45
+
46
+ def write_batch(export_file_path, headers, batch)
47
+ File.open(export_file_path, 'a') do |file|
48
+ batch.each do |row|
49
+ data = headers.map { |header| row[header] }
50
+ file << data.to_csv
51
+ end
52
+ end
53
+ end
54
+
55
+ def in_batches(table, index_key)
56
+ current_id = table.first[index_key]
57
+ max_id = table.last[index_key]
58
+
59
+ while current_id <= max_id
60
+ batch = table.where(index_key => current_id...(current_id + BATCH_SIZE))
61
+ yield batch
62
+ current_id += BATCH_SIZE
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end