shopify_transporter 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +39 -31
- data/exe/shopify_transporter +33 -2
- data/lib/shopify_transporter/exporters/exporter.rb +103 -0
- data/lib/shopify_transporter/exporters/magento/customer_exporter.rb +44 -0
- data/lib/shopify_transporter/exporters/magento/database_cache.rb +21 -0
- data/lib/shopify_transporter/exporters/magento/database_table_exporter.rb +68 -0
- data/lib/shopify_transporter/exporters/magento/magento_exporter.rb +28 -0
- data/lib/shopify_transporter/exporters/magento/order_exporter.rb +46 -0
- data/lib/shopify_transporter/exporters/magento/product_exporter.rb +129 -0
- data/lib/shopify_transporter/exporters/magento/product_options.rb +98 -0
- data/lib/shopify_transporter/exporters/magento/soap.rb +101 -0
- data/lib/shopify_transporter/exporters/magento/sql.rb +38 -0
- data/lib/shopify_transporter/exporters/magento.rb +5 -0
- data/lib/shopify_transporter/exporters.rb +3 -0
- data/lib/shopify_transporter/generators/generate.rb +4 -4
- data/lib/shopify_transporter/generators/new.rb +2 -2
- data/lib/shopify_transporter/pipeline/magento/order/line_items.rb +7 -5
- data/lib/shopify_transporter/pipeline/magento/product/top_level_attributes.rb +73 -4
- data/lib/shopify_transporter/pipeline/magento/product/top_level_variant_attributes.rb +50 -0
- data/lib/shopify_transporter/pipeline/magento/product/variant_attributes.rb +28 -0
- data/lib/shopify_transporter/pipeline/magento/product/variant_image.rb +50 -0
- data/lib/shopify_transporter/record_builder/product_record_builder.rb +33 -0
- data/lib/shopify_transporter/{record_builder.rb → record_builder/record_builder.rb} +0 -0
- data/lib/shopify_transporter/shopify/attributes_helpers.rb +1 -1
- data/lib/shopify_transporter/shopify/order.rb +1 -1
- data/lib/shopify_transporter/shopify/product.rb +4 -3
- data/lib/shopify_transporter/shopify/record.rb +1 -1
- data/lib/shopify_transporter/version.rb +1 -1
- data/lib/shopify_transporter.rb +15 -7
- data/lib/tasks/factory_bot.rake +1 -1
- data/lib/templates/magento/config.tt +19 -0
- data/shopify_transporter.gemspec +3 -0
- metadata +61 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51ea8531c355faaa542a5cac769df64be88ab1c7
|
4
|
+
data.tar.gz: 8b46bab5dade9319600c9da397967f55aa7724ad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60200c955aa3dd02d952835992f0b59e0cf1a8667daf45f7907ec346ed0b6df0b10cd3ff1b466d3a989c4ebf37bff1668a348f932e3308cbc6e31454107f0b87
|
7
|
+
data.tar.gz: ff9d1c553e7617b9d84ceb0c933bbcfa3477a91e13e92986ef0e93f953ad69c030f5f92e5e5e90825f74e7b6fd672593a5b074fccb5873009f4b6e70a7a552b2
|
data/Gemfile
CHANGED
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
|
-
##
|
12
|
+
## Submitting Issues
|
13
13
|
|
14
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
##
|
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
|
-
|
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
|
-
|
278
|
+
### Building and installing locally
|
279
|
+
To build locally run the following command:
|
282
280
|
|
283
|
-
|
284
|
-
|
281
|
+
```
|
282
|
+
$ bundle exec rake build
|
285
283
|
|
286
|
-
|
284
|
+
shopify_transporter 1.0.0 built to pkg/shopify_transporter-1.0.0.gem.
|
285
|
+
```
|
287
286
|
|
288
|
-
|
289
|
-
|
290
|
-
|
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.
|
data/exe/shopify_transporter
CHANGED
@@ -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:
|
20
|
+
desc: 'The config file that lists the conversions to run.'
|
19
21
|
method_option :object, type: :string, required: true,
|
20
|
-
desc:
|
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
|