shopify_transporter 1.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 +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
|