shopify_transporter 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +23 -0
- data/LICENSE +20 -0
- data/README.md +290 -0
- data/RELEASING +16 -0
- data/Rakefile +11 -0
- data/exe/shopify_transporter +47 -0
- data/lib/shopify_transporter/generators/base_group.rb +25 -0
- data/lib/shopify_transporter/generators/generate.rb +64 -0
- data/lib/shopify_transporter/generators/new.rb +23 -0
- data/lib/shopify_transporter/generators.rb +3 -0
- data/lib/shopify_transporter/pipeline/all_platforms/metafields.rb +61 -0
- data/lib/shopify_transporter/pipeline/magento/customer/addresses_attribute.rb +60 -0
- data/lib/shopify_transporter/pipeline/magento/customer/top_level_attributes.rb +47 -0
- data/lib/shopify_transporter/pipeline/magento/order/addresses_attribute.rb +46 -0
- data/lib/shopify_transporter/pipeline/magento/order/line_items.rb +55 -0
- data/lib/shopify_transporter/pipeline/magento/order/top_level_attributes.rb +39 -0
- data/lib/shopify_transporter/pipeline/magento/product/top_level_attributes.rb +36 -0
- data/lib/shopify_transporter/pipeline/stage.rb +16 -0
- data/lib/shopify_transporter/pipeline.rb +4 -0
- data/lib/shopify_transporter/record_builder.rb +51 -0
- data/lib/shopify_transporter/shopify/attributes_accumulator.rb +43 -0
- data/lib/shopify_transporter/shopify/attributes_helpers.rb +53 -0
- data/lib/shopify_transporter/shopify/customer.rb +79 -0
- data/lib/shopify_transporter/shopify/order.rb +107 -0
- data/lib/shopify_transporter/shopify/product.rb +118 -0
- data/lib/shopify_transporter/shopify/record.rb +60 -0
- data/lib/shopify_transporter/shopify.rb +7 -0
- data/lib/shopify_transporter/version.rb +4 -0
- data/lib/shopify_transporter.rb +247 -0
- data/lib/tasks/factory_bot.rake +9 -0
- data/lib/templates/custom_stage.tt +31 -0
- data/lib/templates/gemfile.tt +5 -0
- data/lib/templates/magento/config.tt +23 -0
- data/shopify_transporter.gemspec +36 -0
- metadata +152 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e731ee32bf65a13db82226613fcdfe4de06e5dcd
|
4
|
+
data.tar.gz: 6f52f02f93b67cc3bb96a709ccc895126bd2dfe6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 624777e136f3cb0d659935f6fbe5152ccba71e17fa38e0b884bad43074f5e730315916aa52250758255dd28aec3c47fabc2042567f22b5a217a40d6a5ed47c2c
|
7
|
+
data.tar.gz: 505991f408682f301e7af3be5e4805e74ed2130bbb3c8ed3d42ae85ec1f5159bf6ea49350979e374924163d85ddb0db9fc43432415b5ad6e096129405a3a55dd
|
data/Gemfile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
source "https://rubygems.org"
|
3
|
+
|
4
|
+
# Specify your gem's dependencies in shopify_transporter.gemspec
|
5
|
+
gemspec
|
6
|
+
|
7
|
+
group :development do
|
8
|
+
gem 'package_cloud'
|
9
|
+
gem 'rubocop'
|
10
|
+
gem 'rubocop-git', '~> 0.1'
|
11
|
+
end
|
12
|
+
|
13
|
+
group :test do
|
14
|
+
gem 'codecov', require: false
|
15
|
+
gem 'simplecov', require: false
|
16
|
+
end
|
17
|
+
|
18
|
+
group :development, :test do
|
19
|
+
gem 'factory_bot', '~> 4.8'
|
20
|
+
gem 'pry', '~> 0.11'
|
21
|
+
gem 'pry-byebug'
|
22
|
+
gem 'rspec', '~> 3.0'
|
23
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2018 "Shopify Inc."
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
# Transporter Tools
|
2
|
+
|
3
|
+
The Transporter tool helps converts data from a third-party platform format into a format that can
|
4
|
+
be imported into Shopify via the [Transporter app](https://help.shopify.com/manual/migrating-to-shopify/transporter-app).
|
5
|
+
|
6
|
+
The conversions are currently limited to JSON structured data that has been exported from Magento 1.x via its
|
7
|
+
SOAP API. More details about the exact structure of the JSON data, and additional scripts for procuding such
|
8
|
+
data, will be made available in the near future.
|
9
|
+
|
10
|
+
Note: the Transporter app is available to Shopify Plus plans only.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
### Building and installing locally
|
15
|
+
To build locally run the following command:
|
16
|
+
|
17
|
+
```
|
18
|
+
$ bundle exec rake build
|
19
|
+
|
20
|
+
shopify_transporter 1.0.0 built to pkg/shopify_transporter-1.0.0.gem.
|
21
|
+
```
|
22
|
+
|
23
|
+
Then, you can install the gem system-wide by running:
|
24
|
+
|
25
|
+
```
|
26
|
+
$ gem install pkg/shopify_transporter-1.0.0.gem
|
27
|
+
|
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.
|
35
|
+
|
36
|
+
### Installing the Transporter tool gem from rubygems:
|
37
|
+
|
38
|
+
```
|
39
|
+
$ gem install shopify_transporter
|
40
|
+
```
|
41
|
+
|
42
|
+
### Running
|
43
|
+
After you install the gem, you should find the executable `shopify_transporter` available in your path:
|
44
|
+
|
45
|
+
```
|
46
|
+
$ which shopify_transporter
|
47
|
+
/usr/local/bin/shopify_transporter
|
48
|
+
```
|
49
|
+
## Help and usage
|
50
|
+
|
51
|
+
To view the usage and help for the `shopify_transporter` run the following command:
|
52
|
+
|
53
|
+
```
|
54
|
+
$ shopify_transporter -h
|
55
|
+
|
56
|
+
Commands:
|
57
|
+
shopify_transporter convert --config=CONFIG --object=OBJECT file1.csv file2.csv ... # Converts your files into shopify formatted files.
|
58
|
+
shopify_transporter generate STAGE_NAME --object=OBJECT # Generates a new pipeline stage for the specified object type
|
59
|
+
shopify_transporter help [COMMAND] # Describe available commands or one specific command
|
60
|
+
shopify_transporter new PROJECTNAME --platform=PLATFORM # Generates a new project structure for a platform
|
61
|
+
```
|
62
|
+
|
63
|
+
## Create a conversion project
|
64
|
+
|
65
|
+
It's convenient to create a conversion project for each store that you want to migrate to Shopify.
|
66
|
+
To create it, use the `new` sub-command:
|
67
|
+
|
68
|
+
```
|
69
|
+
$ shopify_transporter new example-magento-conversion --platform=magento
|
70
|
+
create example_magento_migration/Gemfile
|
71
|
+
create example_magento_migration/config.yml
|
72
|
+
create example_magento_migration/lib/magento/custom_pipeline_stages
|
73
|
+
```
|
74
|
+
|
75
|
+
The `new` sub-command creates a project folder with the following:
|
76
|
+
|
77
|
+
* a `Gemfile` that references the `shopify_transporter` gem. This Gemfile allows custom pipeline stages
|
78
|
+
to refer to the base classes that are defined in the `shopify_transporter` gem.
|
79
|
+
|
80
|
+
* a configuration file (`config.yml`) that provides the configuration required for each pipeline stage
|
81
|
+
in the conversion process.
|
82
|
+
|
83
|
+
* a folder to hold additional custom pipeline stages you may define later
|
84
|
+
|
85
|
+
Switch to the project directory. For example:
|
86
|
+
|
87
|
+
```
|
88
|
+
cd example_magento_migration
|
89
|
+
```
|
90
|
+
|
91
|
+
As with any Ruby project, you need to run the following command before you create and run any custom
|
92
|
+
pipeline stages:
|
93
|
+
|
94
|
+
```
|
95
|
+
$ bundle install
|
96
|
+
```
|
97
|
+
|
98
|
+
|
99
|
+
## Convert records from the third-party platform to Shopify
|
100
|
+
|
101
|
+
Run `shopify_transporter` and use the `convert` command to convert your objects from the
|
102
|
+
third-party platform to the Shopify format. For example, the following command converts a
|
103
|
+
JSON file that contains customers (*magento_customers.json*) from Magento to Shopify:
|
104
|
+
|
105
|
+
```
|
106
|
+
shopify_transporter convert --config=config.yml --object=customer magento_customers.json > shopify_customers.csv
|
107
|
+
```
|
108
|
+
|
109
|
+
In this example, the converted customer objects are saved to *shopify_customers.csv*. If errors occur during
|
110
|
+
the conversion, then they appear in your terminal.
|
111
|
+
|
112
|
+
## Convert multiple files
|
113
|
+
|
114
|
+
To convert multiple files to Shopify, separate the file names with a space:
|
115
|
+
|
116
|
+
```
|
117
|
+
shopify_transporter convert --config=config.yml --object=customer magento_customers_1.json magento_customers_2.json ...
|
118
|
+
```
|
119
|
+
|
120
|
+
## Configuration file (_config.yml_)
|
121
|
+
|
122
|
+
The configuraton file is generated when you create your conversion project. This file is specific to the
|
123
|
+
third-party platform that you are converting to Shopify.
|
124
|
+
|
125
|
+
Here's an example of a _config.yml_ file for converting customers from Magento:
|
126
|
+
|
127
|
+
```
|
128
|
+
platform_type: Magento
|
129
|
+
object_types:
|
130
|
+
customer:
|
131
|
+
record_key: email
|
132
|
+
pipeline_stages:
|
133
|
+
- TopLevelAttributes
|
134
|
+
- AddressesAttribute
|
135
|
+
- Metafields:
|
136
|
+
type: all_platforms
|
137
|
+
params:
|
138
|
+
# Specify a custom namespace for your metafields with metafield_namespace.
|
139
|
+
# Uses migrated_data by default.
|
140
|
+
# metafield_namespace: migrated_data
|
141
|
+
metafields:
|
142
|
+
- website
|
143
|
+
- group
|
144
|
+
- free_trial_start_at
|
145
|
+
```
|
146
|
+
|
147
|
+
### record_key
|
148
|
+
|
149
|
+
The `config.yml` file allows you to define the object type to convert. An object type needs a `record_key`,
|
150
|
+
whose values must be unique among the other records in the file. For example, the default `record_key` for
|
151
|
+
customers is the customer's email address.
|
152
|
+
|
153
|
+
```
|
154
|
+
platform_type: Magento
|
155
|
+
object_types:
|
156
|
+
customer:
|
157
|
+
record_key: email
|
158
|
+
...
|
159
|
+
```
|
160
|
+
|
161
|
+
When you run `shopify_transporter` with the `convert` command, the input (third-party platform) files are
|
162
|
+
read one-by-one and line-by-line. Each object in the input file must have a `record_key` value. Rows that
|
163
|
+
have the same `record_key` value are considered to be part of the same object.
|
164
|
+
|
165
|
+
### Pipeline_stages
|
166
|
+
|
167
|
+
The Transporter tool processes the input (third-party platform objects) through a series of pipeline
|
168
|
+
stages. These stages, and the order in which they are to be processed, are defined in the `config.yml` file.
|
169
|
+
|
170
|
+
In the example below, there are two pipeline stages for converting objects from Magento to
|
171
|
+
Shopify: TopLevelAttributes and AddressAttributes.
|
172
|
+
|
173
|
+
```
|
174
|
+
platform_type: Magento
|
175
|
+
object_types:
|
176
|
+
customer:
|
177
|
+
record_key: email
|
178
|
+
pipeline_stages:
|
179
|
+
- TopLevelAttributes
|
180
|
+
- AddressesAttribute
|
181
|
+
```
|
182
|
+
|
183
|
+
Each pipeline stage's _convert_ method receives the row currently being processed, as well as the
|
184
|
+
current state of the corresponding Shopify object being converted. The method's responsibility is to
|
185
|
+
inject into the Shopify object the relevant attributes from the input row.
|
186
|
+
|
187
|
+
The role of a pipeline stage is to examine the input rows and populate attributes on the Shopify object.
|
188
|
+
For example, the `TopLevelAttributes` stage of a Magento customer migration looks for a column
|
189
|
+
named `firstname` on the input, and then populates the Shopify object accordingly:
|
190
|
+
|
191
|
+
|
192
|
+
```
|
193
|
+
record['first_name'] = input['firstname']
|
194
|
+
```
|
195
|
+
|
196
|
+
Any changes that are made to the this record in a pipeline stage are permanent to the Shopify record
|
197
|
+
associated with the `record_key`.
|
198
|
+
|
199
|
+
The next pipeline stage that receives this record, receives the same input and the existing record which
|
200
|
+
consist of:
|
201
|
+
|
202
|
+
```
|
203
|
+
{
|
204
|
+
'first_name' => 'John',
|
205
|
+
}
|
206
|
+
```
|
207
|
+
|
208
|
+
Existing pipeline stages and the attributes are populated below.
|
209
|
+
|
210
|
+
### Magento v1.x customer
|
211
|
+
|
212
|
+
|
213
|
+
### AddressesAttribute
|
214
|
+
|
215
|
+
Addresses are built from the `shipping_` and `billing_` prefixed fields. The Shopify object's `addresses`
|
216
|
+
attribute is an array that consists of the `shipping_` prefixed attributes as the first address, and
|
217
|
+
the `billing_` prefixed attributes as the second.
|
218
|
+
|
219
|
+
```
|
220
|
+
{
|
221
|
+
'addresses': [
|
222
|
+
{
|
223
|
+
'first_name': ...,
|
224
|
+
'last_name': ...,
|
225
|
+
'address1': ...,
|
226
|
+
'address2': ...,
|
227
|
+
'city': ...,
|
228
|
+
'province': ...,
|
229
|
+
'country_code': ...,
|
230
|
+
'zip': ...,
|
231
|
+
'company': ...,
|
232
|
+
'phone': ...,
|
233
|
+
},
|
234
|
+
]
|
235
|
+
}
|
236
|
+
```
|
237
|
+
|
238
|
+
### Metafields
|
239
|
+
|
240
|
+
See the metafields section in the All Platforms section below.
|
241
|
+
|
242
|
+
### All Platforms
|
243
|
+
|
244
|
+
Some stages are a little more versatile and can support input rows from arbitrary third-party platforms. These stages
|
245
|
+
are usually extensible through defining parameters in the `config.yml`.
|
246
|
+
|
247
|
+
#### Metafields
|
248
|
+
|
249
|
+
The `convert` command converts the most popular metafields or custom fields from the third-party platforms into
|
250
|
+
Shopify [metafields](https://help.shopify.com/manual/products/metafields). You can view the default metafields and
|
251
|
+
add others in your `config.yml` file.
|
252
|
+
|
253
|
+
## Adding customized stages
|
254
|
+
|
255
|
+
If the Shopify object that is generated is missing attributes or if a pipeline stage fails, it could be because there are
|
256
|
+
unexpected headers defined in the third-party CSV data.
|
257
|
+
|
258
|
+
To define your own customized stages, run the following command:
|
259
|
+
|
260
|
+
`shopify_transporter generate YourCustomStage --object customer`
|
261
|
+
|
262
|
+
A file named `your_custom_stage.rb` is added to the `lib/magento/custom_pipeline_stages` directory. You can modify this file to add your custom conversion logic.
|
263
|
+
|
264
|
+
## Limitations
|
265
|
+
|
266
|
+
The `convert` command currently only converts customer and order JSON objects that have been exported by SOAP API
|
267
|
+
from Magento 1.x.
|
268
|
+
|
269
|
+
## Running unit tests
|
270
|
+
|
271
|
+
We use rspec to run our test suite:
|
272
|
+
`bundle exec rspec`
|
273
|
+
|
274
|
+
## Running the linter
|
275
|
+
|
276
|
+
It's important that all of the code introduced passes linting. To run it manually:
|
277
|
+
`bundle exec rake rubocop`
|
278
|
+
To automatically resolve any basic linting issues:
|
279
|
+
`bundle exec rake rubocop:autocorrect`
|
280
|
+
|
281
|
+
# Submitting Issues
|
282
|
+
|
283
|
+
Please open an issue here if you encounter a specific bug with this library or if something is documented
|
284
|
+
incorrectly.
|
285
|
+
|
286
|
+
When filing an issue, please ensure that:
|
287
|
+
|
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.
|
data/RELEASING
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Releasing ShopifyTransporter
|
2
|
+
|
3
|
+
1. Check the Semantic Versioning page for info on how to version the new release: http://semver.org
|
4
|
+
2. Update the version of ShopifyTransporter in lib/shopify_transporter/version.rb
|
5
|
+
3. Add a CHANGELOG entry for the new release with the date
|
6
|
+
4. Commit the changes with a commit message like "Packaging for release X.Y.Z"
|
7
|
+
5. Tag the release with the version (Leave REV blank for HEAD or provide a SHA)
|
8
|
+
$ git tag vX.Y.Z REV
|
9
|
+
6. Push out the changes
|
10
|
+
$ git push
|
11
|
+
7. Push out the tags
|
12
|
+
$ git push --tags
|
13
|
+
8. Build the new .gem from the updated .gemspec
|
14
|
+
$ gem build shopify_transporter.gemspec
|
15
|
+
9. Publish the Gem to gemcutter
|
16
|
+
$ gem push shopify_transporter-X.Y.Z.gem
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'rubocop/rake_task'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
RuboCop::RakeTask.new
|
8
|
+
|
9
|
+
task default: :spec
|
10
|
+
|
11
|
+
Dir.glob('lib/tasks/*.rake').each { |r| import r }
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift("#{__dir__}/../lib")
|
5
|
+
|
6
|
+
require 'thor'
|
7
|
+
require 'thor/group'
|
8
|
+
require 'shopify_transporter'
|
9
|
+
require 'yaml'
|
10
|
+
|
11
|
+
module ShopifyTransporter
|
12
|
+
class CLI < Thor
|
13
|
+
def self.exit_on_failure?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
method_option :config, type: :string, default: 'config.yml',
|
18
|
+
desc: "The config file that lists the conversions to run."
|
19
|
+
method_option :object, type: :string, required: true,
|
20
|
+
desc: "The object type (customer, product, or order) to convert.",
|
21
|
+
enum: %w(customer product order)
|
22
|
+
|
23
|
+
desc 'convert FILE_NAMES', 'Converts objects into a Shopify-format. (accepts a list of space-separated files).'
|
24
|
+
def convert(*files)
|
25
|
+
conversion_error('Missing file name at the end of the command.') if files.empty?
|
26
|
+
migration_tool = TransporterTool.new(*files, options[:config], options[:object])
|
27
|
+
migration_tool.run
|
28
|
+
rescue TransporterTool::ConversionError => error
|
29
|
+
conversion_error(error.message)
|
30
|
+
end
|
31
|
+
|
32
|
+
no_commands do
|
33
|
+
def conversion_error(message)
|
34
|
+
say("Conversion error: #{message}", :red)
|
35
|
+
exit 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
register ShopifyTransporter::New,
|
40
|
+
'new', 'new PROJECTNAME --platform=PLATFORM', 'Generate a project for the platform'
|
41
|
+
register ShopifyTransporter::Generate,
|
42
|
+
'generate', 'generate STAGE_NAME --object=OBJECT',
|
43
|
+
'Generate a custom pipeline stage for the object'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
ShopifyTransporter::CLI.start(ARGV)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ShopifyTransporter
|
3
|
+
class BaseGroup < Thor::Group
|
4
|
+
include Thor::Actions
|
5
|
+
argument :name
|
6
|
+
|
7
|
+
SUPPORTED_PLATFORMS_MAPPING = {
|
8
|
+
'Magento' => 'magento',
|
9
|
+
}
|
10
|
+
|
11
|
+
def name_components
|
12
|
+
@name_components ||= name.scan(/[[:alnum:]]+/)
|
13
|
+
end
|
14
|
+
|
15
|
+
def config_filename
|
16
|
+
'config.yml'
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def source_root
|
21
|
+
File.expand_path('../../', File.dirname(__FILE__))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative './base_group.rb'
|
3
|
+
|
4
|
+
module ShopifyTransporter
|
5
|
+
class Generate < BaseGroup
|
6
|
+
class_option :object, type: :string, required: :true,
|
7
|
+
enum: %w(customer order), aliases: '-O',
|
8
|
+
desc: 'The object to add to the pipeline stage'
|
9
|
+
|
10
|
+
def object_type
|
11
|
+
@object_type ||= options[:object].capitalize
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate_config_exists
|
15
|
+
unless File.exist?(config_filename)
|
16
|
+
say("Cannot find config.yml at project root", :red)
|
17
|
+
exit 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def pipeline_snake_name
|
22
|
+
name_components.map(&:underscore).join('_')
|
23
|
+
end
|
24
|
+
|
25
|
+
def pipeline_class
|
26
|
+
@pipeline_class ||= @name_components.map(&:classify).join('')
|
27
|
+
end
|
28
|
+
|
29
|
+
def platform
|
30
|
+
platform_type = YAML.load_file(config_filename)['platform_type']
|
31
|
+
@platform_file_path ||= SUPPORTED_PLATFORMS_MAPPING[platform_type]
|
32
|
+
@platform ||= platform_type.underscore
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_stage
|
36
|
+
template(
|
37
|
+
"templates/custom_stage.tt",
|
38
|
+
"lib/custom_pipeline_stages/#{pipeline_snake_name}.rb"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_stage_to_config
|
43
|
+
config_file = YAML.load_file(config_filename)
|
44
|
+
unless class_included_in?(config_file)
|
45
|
+
pipeline_class_hash = { @pipeline_class.to_s => nil, "type" => "custom" }
|
46
|
+
config_file['object_types'][@object_type.downcase]['pipeline_stages'] << pipeline_class_hash
|
47
|
+
File.open(config_filename, 'w') { |f| f.write config_file.to_yaml }
|
48
|
+
say('Updated config.yml with the new pipeline stage', :green)
|
49
|
+
return
|
50
|
+
end
|
51
|
+
|
52
|
+
say("Warning: The pipeline stage #{@pipeline_class} already exists in the config.yml", :blue)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def class_included_in?(config_file)
|
58
|
+
hash_stages = config_file['object_types'][@object_type.downcase]['pipeline_stages'].select do |stage|
|
59
|
+
stage.is_a? Hash
|
60
|
+
end
|
61
|
+
hash_stages.find { |h| h.keys.include? @pipeline_class.to_s }.present?
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative './base_group.rb'
|
3
|
+
|
4
|
+
module ShopifyTransporter
|
5
|
+
class New < BaseGroup
|
6
|
+
include Thor::Actions
|
7
|
+
class_option :platform, type: :string, required: :true, enum: %w(magento)
|
8
|
+
|
9
|
+
def snake_name
|
10
|
+
@snake_name ||= name_components.map(&:downcase).join("_")
|
11
|
+
end
|
12
|
+
|
13
|
+
def platform
|
14
|
+
@platform ||= options[:platform]
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_config
|
18
|
+
template("templates/#{@platform}/config.tt", "#{@snake_name}/config.yml")
|
19
|
+
template("templates/gemfile.tt", "#{@snake_name}/Gemfile")
|
20
|
+
empty_directory("#{@snake_name}/lib/custom_pipeline_stages")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative '../stage'
|
3
|
+
require_relative '../../shopify'
|
4
|
+
|
5
|
+
module ShopifyTransporter
|
6
|
+
module Pipeline
|
7
|
+
module AllPlatforms
|
8
|
+
class Metafields < ShopifyTransporter::Pipeline::Stage
|
9
|
+
def convert(input, record)
|
10
|
+
raise 'Metafields not specified.' unless metafields_specified(params)
|
11
|
+
|
12
|
+
accumulator = MetafieldAttributesAccumulator.new(
|
13
|
+
initial_value: record,
|
14
|
+
metafields_to_extract: params['metafields'],
|
15
|
+
metafield_namespace: params['metafield_namespace'] || ShopifyTransporter::DEFAULT_METAFIELD_NAMESPACE,
|
16
|
+
)
|
17
|
+
accumulator.accumulate(input)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def metafields_specified(params)
|
23
|
+
params['metafields'].class == Array && params['metafields'].any?
|
24
|
+
end
|
25
|
+
|
26
|
+
class MetafieldAttributesAccumulator < ShopifyTransporter::Shopify::AttributesAccumulator
|
27
|
+
attr_reader :metafields_to_extract, :metafield_namespace
|
28
|
+
|
29
|
+
def initialize(initial_value:, metafields_to_extract:, metafield_namespace:)
|
30
|
+
@metafields_to_extract = metafields_to_extract
|
31
|
+
@metafield_namespace = metafield_namespace
|
32
|
+
super(initial_value)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def input_applies?(input)
|
38
|
+
attributes_present?(input, *metafields_to_extract)
|
39
|
+
end
|
40
|
+
|
41
|
+
def attributes_from(input)
|
42
|
+
{
|
43
|
+
'metafields' => extract_metafields(input),
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def extract_metafields(input)
|
48
|
+
metafields_to_extract.map do |metafield_key|
|
49
|
+
next if input[metafield_key].nil?
|
50
|
+
shopify_metafield_hash(
|
51
|
+
key: metafield_key,
|
52
|
+
value: input[metafield_key],
|
53
|
+
namespace: metafield_namespace
|
54
|
+
)
|
55
|
+
end.compact
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'shopify_transporter/pipeline/stage'
|
3
|
+
require 'shopify_transporter/shopify'
|
4
|
+
|
5
|
+
module ShopifyTransporter
|
6
|
+
module Pipeline
|
7
|
+
module Magento
|
8
|
+
module Customer
|
9
|
+
class AddressesAttribute < Pipeline::Stage
|
10
|
+
def convert(input, record)
|
11
|
+
addresses = magento_addresses_from(input)
|
12
|
+
return unless addresses
|
13
|
+
|
14
|
+
record.merge!(convert_addresses(addresses))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
COLUMN_MAPPING = {
|
20
|
+
'firstname' => 'first_name',
|
21
|
+
'lastname' => 'last_name',
|
22
|
+
'street' => 'address1',
|
23
|
+
'city' => 'city',
|
24
|
+
'region' => 'province',
|
25
|
+
'country_id' => 'country_code',
|
26
|
+
'postcode' => 'zip',
|
27
|
+
'company' => 'company',
|
28
|
+
'telephone' => 'phone',
|
29
|
+
}
|
30
|
+
|
31
|
+
def convert_addresses(addresses)
|
32
|
+
{
|
33
|
+
'addresses' => [
|
34
|
+
*addresses_from(addresses.select { |address| address['is_default_shipping'] }),
|
35
|
+
*addresses_from(addresses.select { |address| !address['is_default_shipping'] }),
|
36
|
+
].compact,
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def addresses_from(addresses)
|
41
|
+
return nil unless addresses.present?
|
42
|
+
|
43
|
+
addresses.map do |address|
|
44
|
+
COLUMN_MAPPING.each_with_object({}) do |(key, value), obj|
|
45
|
+
obj[value] = address[key] if address[key]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def magento_addresses_from(input)
|
51
|
+
addresses = input.dig('address_list', 'customer_address_list_response', 'result', 'item')
|
52
|
+
return nil unless addresses
|
53
|
+
|
54
|
+
addresses.is_a?(Array) ? addresses : [addresses]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|