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.
- 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
|