forked from lightward/mechanic-tasks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexport-all-products-to-sftp-as-a-shopify-friendly-csv.json
29 lines (29 loc) · 18.4 KB
/
export-all-products-to-sftp-as-a-shopify-friendly-csv.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
"docs": "On a configurable schedule, this task generates a Shopify-friendly CSV of all your products, and uploads it to the SFTP destination of your choice. This is a convenient way to keep regular backups of your entire product catalog: simply import a CSV to restore your products to that point in time. ([Learn more about CSV imports and exports of Shopify products.](https://help.shopify.com/en/manual/products/import-export/using-csv))\r\n\r\nTo only export certain products, set the \"Only export products matching this query\" option to a search query that works with Shopify's product admin area. For example, to only export products tagged \"backmeup\", use the search query \"tag:backmeup\".",
"halt_action_run_sequence_on_error": false,
"name": "Export all products to SFTP, as a Shopify-friendly CSV",
"online_store_javascript": null,
"options": {
"only_export_products_matching_this_query": null,
"run_every_x_hours__number": null,
"sftp_host__required": null,
"sftp_port__required_number": null,
"sftp_user__required": null,
"sftp_password__required": null,
"sftp_upload_directory": null
},
"order_status_javascript": null,
"perform_action_runs_in_sequence": false,
"script": "{% comment %}\n Preferred option order:\n\n {{ options.only_export_products_matching_this_query }}\n {{ options.run_every_x_hours__number }}\n {{ options.sftp_host__required }}\n {{ options.sftp_port__required_number }}\n {{ options.sftp_user__required }}\n {{ options.sftp_password__required }}\n {{ options.sftp_upload_directory }}\n{% endcomment %}\n\n{% comment %}\n-- validate options\n{% endcomment %}\n{% if options.run_every_x_hours__number != blank %}\n {% assign valid_hours = array %}\n {% assign valid_hours[valid_hours.size] = 1 %}\n {% assign valid_hours[valid_hours.size] = 2 %}\n {% assign valid_hours[valid_hours.size] = 3 %}\n {% assign valid_hours[valid_hours.size] = 4 %}\n {% assign valid_hours[valid_hours.size] = 6 %}\n {% assign valid_hours[valid_hours.size] = 12 %}\n {% assign valid_hours[valid_hours.size] = 24 %}\n\n {% unless valid_hours contains options.run_every_x_hours__number %}\n {% error \"If set, 'Run interval in hours' must be 1, 2, 3, 4, 6, 12, or 24.\" %}\n {% endunless %}\n{% endif %}\n\n{% assign ok_to_run = false %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic == \"mechanic/scheduler/daily\" %}\n {% assign ok_to_run = true %}\n\n{% elsif event.topic == \"mechanic/scheduler/hourly\" and options.run_every_x_hours__number != blank %}\n {% assign hour_mod = \"now\" | date: \"%H\" | modulo: options.run_every_x_hours__number %}\n\n {% if event.preview or hour_mod == 0 %}\n {% assign ok_to_run = true %}\n\n {% else %}\n {% log message: \"The current hour does not fall on the configured interval; skipping\", hour_interval: options.run_every_x_hours__number, current_hour: hour_mod %}\n {% endif %}\n{% endif %}\n\n{% if ok_to_run %}\n {% capture bulk_operation_query %}\n query {\n products(reverse: true, query: {{ options.only_export_products_matching_this_query | json }}) {\n edges {\n node {\n id\n __typename\n bodyHtml\n handle\n isGiftCard\n onlineStoreUrl\n options {\n id\n __typename\n name\n position\n values\n }\n productType\n seo {\n description\n title\n }\n tags\n title\n variants {\n edges {\n node {\n id\n __typename\n barcode\n compareAtPrice\n fulfillmentService {\n id\n __typename\n serviceName\n }\n image {\n id\n __typename\n altText\n originalSrc\n }\n inventoryItem {\n id\n __typename\n tracked\n unitCost {\n amount\n }\n }\n inventoryPolicy\n position\n price\n product {\n handle\n }\n requiresShipping\n selectedOptions {\n name\n value\n }\n sku\n taxable\n taxCode\n title\n weight\n weightUnit\n }\n }\n }\n vendor\n images {\n edges {\n node {\n id\n __typename\n altText\n originalSrc\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% action \"shopify\" %}\n mutation {\n bulkOperationRunQuery(\n query: {{ bulk_operation_query | json }}\n ) {\n bulkOperation {\n id\n status\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n{% elsif event.topic == \"mechanic/shopify/bulk_operation\" %}\n {% if event.preview %}\n {% capture jsonl_string %}\n {\"id\":\"gid:\\/\\/shopify\\/Product\\/92223864844\",\"__typename\":\"Product\",\"bodyHtml\":\"\\u003cp\\u003e\\u003cem\\u003eThis is a demonstration store. You can purchase products like this from \\u003ca href=\\\"https:\\/\\/www.purefixcycles.com\\\" target=\\\"_blank\\\"\\u003ePure Fix Cycles\\u003c\\/a\\u003e\\u003c\\/em\\u003e\\u003c\\/p\\u003e\\u003cp\\u003eA favorite of professional mechanics around the world! The 4mm 5mm 6mm Balldriver Y-Wrench allows for quick insertion into bolt heads from a wider variety of angles. This baby is made from heat treated Bondhus Protanium, so it will last a lifetime. Plus, it's made in the USA!\\u003c\\/p\\u003e\\n\\u003cp\\u003eHaving a balldriver allows you to tighten screws at up to a 25 degree angle!\\u003c\\/p\\u003e\\n\\u003cp class=\\\"tertiary\\\"\\u003eNote: Balldrivers are not intended for high torque situations.\\u003c\\/p\\u003e\",\"handle\":\"4mm-5mm-6mm-balldriver-y-wrench\",\"isGiftCard\":false,\"onlineStoreUrl\":null,\"options\":[{\"id\":\"gid:\\/\\/shopify\\/ProductOption\\/141068402700\",\"__typename\":\"ProductOption\",\"name\":\"Title\",\"position\":1,\"values\":[\"Y-Wrench\"]}],\"productType\":\"Tools\",\"seo\":{\"description\":null,\"title\":null},\"tags\":[\"Accessories\",\"Essential\",\"Essentials\",\"Safety Gear\",\"Tool\",\"Tools\",\"Tools and Maintenance\",\"Wrench\"],\"title\":\"4mm 5mm 6mm Balldriver Y-Wrench\",\"vendor\":\"Park Tool\"}\n {\"id\":\"gid:\\/\\/shopify\\/ProductImage\\/711168557068\",\"__typename\":\"Image\",\"altText\":\"Park Tool Wrench\",\"originalSrc\":\"https:\\/\\/cdn.shopify.com\\/s\\/files\\/1\\/1186\\/9366\\/products\\/balldriver.jpg?v=1513060358\",\"__parentId\":\"gid:\\/\\/shopify\\/Product\\/92223864844\"}\n {\"id\":\"gid:\\/\\/shopify\\/ProductVariant\\/971762401292\",\"__typename\":\"ProductVariant\",\"barcode\":null,\"compareAtPrice\":null,\"fulfillmentService\":{\"id\":\"gid:\\/\\/shopify\\/FulfillmentService\\/manual\",\"__typename\":\"FulfillmentService\",\"serviceName\":\"Manual\"},\"image\":{\"id\":\"gid:\\/\\/shopify\\/ProductImage\\/711168557068\",\"__typename\":\"Image\",\"altText\":\"Park Tool Wrench\",\"originalSrc\":\"https:\\/\\/cdn.shopify.com\\/s\\/files\\/1\\/1186\\/9366\\/products\\/balldriver.jpg?v=1513060358\"},\"inventoryItem\":{\"id\":\"gid:\\/\\/shopify\\/InventoryItem\\/981398028300\",\"__typename\":\"InventoryItem\",\"tracked\":true,\"unitCost\":null},\"inventoryPolicy\":\"DENY\",\"position\":1,\"price\":\"9.99\",\"product\":{\"handle\":\"4mm-5mm-6mm-balldriver-y-wrench\"},\"requiresShipping\":true,\"selectedOptions\":[{\"name\":\"Title\",\"value\":\"Y-Wrench\"}],\"sku\":\"Tool - Park Balldriver 456\",\"taxable\":true,\"taxCode\":null,\"title\":\"Y-Wrench\",\"weight\":0.2491,\"weightUnit\":\"POUNDS\",\"__parentId\":\"gid:\\/\\/shopify\\/Product\\/92223864844\"}\n {% endcapture %}\n\n {% assign bulkOperation = hash %}\n {% assign bulkOperation[\"objects\"] = jsonl_string | parse_jsonl %}\n {% endif %}\n\n {% comment %}\n -- csv required fields, in this order\n {% endcomment %}\n\n {% assign columns = \"Handle,Title,Body (HTML),Vendor,Type,Tags,Published,Option1 Name,Option1 Value,Option2 Name,Option2 Value,Option3 Name,Option3 Value,Variant SKU,Variant Grams,Variant Inventory Tracker,Variant Inventory Policy,Variant Fulfillment Service,Variant Price,Variant Compare At Price,Variant Requires Shipping,Variant Taxable,Variant Barcode,Image Src,Image Position,Image Alt Text,Gift Card,SEO Title,SEO Description,Google Shopping / Google Product Category,Google Shopping / Gender,Google Shopping / Age Group,Google Shopping / MPN,Google Shopping / AdWords Grouping,Google Shopping / AdWords Labels,Google Shopping / Condition,Google Shopping / Custom Product,Google Shopping / Custom Label 0,Google Shopping / Custom Label 1,Google Shopping / Custom Label 2,Google Shopping / Custom Label 3,Google Shopping / Custom Label 4,Variant Image,Variant Weight Unit,Variant Tax Code,Cost per item\" | split: \",\" %}\n\n {% comment %}\n -- setup 2d array required by csv filter, and add the columns as a header row\n {% endcomment %}\n\n {% assign rows = array %}\n {% assign rows[0] = columns %}\n\n {% comment %}\n -- loop through the lines (JSONL) returned by the bulk operation\n {% endcomment %}\n\n {% assign products_by_id = hash %}\n\n {% for object in bulkOperation.objects %}\n {% case object.__typename %}\n {% when \"Product\" %}\n {% comment %}-- clone the object to allow modification --{% endcomment %}\n {% assign product = object | json | parse_json %}\n {% assign product[\"variants\"] = array %}\n {% assign product[\"images\"] = array %}\n {% assign products_by_id[product.id] = product %}\n\n {% when \"ProductVariant\" %}\n {% assign variant = object %}\n {% assign product_id = variant.__parentId %}\n {% assign product = products_by_id[product_id] %}\n {% assign product[\"variants\"][product.variants.size] = variant %}\n\n {% when \"Image\" %}\n {% assign image = object %}\n {% assign product_id = image.__parentId %}\n {% assign product = products_by_id[product_id] %}\n {% assign images = product.images %}\n {% assign product[\"images\"][images.size] = image %}\n\n {% else %}\n {% log message: \"Unexpected object type in JSONL\", object_type: object.__typename, object: object %}\n\n {% endcase %}\n {% endfor %}\n\n {% comment %}\n -- loop through products_by_id to build csv rows\n {% endcomment %}\n\n {% for pair in products_by_id %}\n {% assign product = pair[1] %}\n {% assign product_rows = array %}\n\n {% comment %}\n -- loop through product variants to build csv rows, one row per variant\n {% endcomment %}\n\n {% for variant in product.variants %}\n {% assign variant_row = hash %}\n {% for column in columns %}\n {% assign variant_row[column] = nil %}\n {% endfor %}\n\n {% comment %}\n -- the first variant row contains the details for the product itself\n {% endcomment %}\n\n {% if variant.position == 1 %}\n {% assign variant_row[\"Body (HTML)\"] = product.bodyHtml %}\n {% assign variant_row[\"Gift Card\"] = product.isGiftCard %}\n {% assign variant_row[\"Handle\"] = product.handle %}\n {% assign variant_row[\"SEO Description\"] = product.seo.description %}\n {% assign variant_row[\"SEO Title\"] = product.seo.title %}\n {% assign variant_row[\"Tags\"] = product.tags | join: \", \" %}\n {% assign variant_row[\"Title\"] = product.title %}\n {% assign variant_row[\"Type\"] = product.productType %}\n {% assign variant_row[\"Vendor\"] = product.vendor %}\n\n {% assign published = false %}\n {% if product.onlineStoreUrl != blank %}\n {% assign published = true %}\n {% endif%}\n {% assign variant_row[\"Published\"] = published %}\n\n {% for option in product.options %}\n {% assign option_name_key = \"Option\" | append: option.position | append: \" Name\" %}\n {% assign option_value_key = \"Option\" | append: option.position | append: \" Value\" %}\n {% assign variant_row[option_name_key] = option.name %}\n {% assign variant_row[option_value_key] = option.values[0] %}\n {% endfor %}\n {% endif %}\n\n {% assign variant_row[\"Cost per item\"] = variant.inventoryItem.unitCost.amount %}\n {% assign variant_row[\"Handle\"] = variant.product.handle %}\n {% assign variant_row[\"Variant Barcode\"] = variant.barcode %}\n {% assign variant_row[\"Variant Compare At Price\"] = variant.compareAtPrice %}\n {% assign variant_row[\"Variant Fulfillment Service\"] = variant.fulfillmentService.serviceName | downcase %}\n {% assign variant_row[\"Variant Inventory Policy\"] = variant.inventoryPolicy | downcase %}\n {% assign variant_row[\"Variant Price\"] = variant.price %}\n {% assign variant_row[\"Variant Requires Shipping\"] = variant.requiresShipping %}\n {% assign variant_row[\"Variant SKU\"] = variant.sku %}\n {% assign variant_row[\"Variant Taxable\"] = variant.taxable %}\n {% assign variant_row[\"Variant Tax Code\"] = variant.taxCode %}\n\n {% for option in variant.selectedOptions %}\n {% assign option_value_key = \"Option\" | append: forloop.index | append: \" Value\" %}\n {% assign variant_row[option_value_key] = option.value %}\n {% endfor %}\n\n {% if variant.image %}\n {% assign variant_row[\"Variant Image\"] = variant.image.originalSrc %}\n {% endif %}\n\n {% if variant.inventoryItem.tracked %}\n {% assign variant_row[\"Variant Inventory Tracker\"] = \"shopify\" %}\n {% endif %}\n\n {% case variant.weightUnit %}\n {% when \"GRAMS\" %}\n {% assign variant_row[\"Variant Weight Unit\"] = \"g\" %}\n {% assign variant_row[\"Variant Grams\"] = variant.weight %}\n {% when \"KILOGRAMS\" %}\n {% assign variant_row[\"Variant Weight Unit\"] = \"kg\" %}\n {% assign variant_row[\"Variant Grams\"] = variant.weight | times: 1000 %}\n {% when \"OUNCES\" %}\n {% assign variant_row[\"Variant Weight Unit\"] = \"oz\" %}\n {% assign variant_row[\"Variant Grams\"] = variant.weight | times: 28.34952 %}\n {% when \"POUNDS\" %}\n {% assign variant_row[\"Variant Weight Unit\"] = \"lb\" %}\n {% assign variant_row[\"Variant Grams\"] = variant.weight | times: 453.59237 %}\n {% endcase %}\n\n {% assign product_rows[product_rows.size] = variant_row %}\n {% endfor %}{% comment %}-- end variant loop --{% endcomment %}\n\n {% comment %}\n -- loop through product images to build csv rows\n {% endcomment %}\n\n {% for image in product.images %}\n {% comment %}\n -- images should be assigned sequentially to existing product rows, regardless of any\n -- pairing with variant images, because... ¯\\_(ツ)_/¯\n {% endcomment %}\n\n {% if product_rows[forloop.index0] %}\n {% assign image_row = product_rows[forloop.index0] %}\n {% assign image_row[\"Image Src\"] = image.originalSrc %}\n {% assign image_row[\"Image Position\"] = forloop.index %}\n {% assign image_row[\"Image Alt Text\"] = image.altText %}\n {% assign product_rows[forloop.index0] = image_row %}\n\n {% else %}\n {% assign image_row = hash %}\n {% for column in columns %}\n {% assign image_row[column] = nil %}\n {% endfor %}\n\n {% assign image_row[\"Handle\"] = product.handle %}\n {% assign image_row[\"Image Src\"] = image.originalSrc %}\n {% assign image_row[\"Image Position\"] = forloop.index %}\n {% assign image_row[\"Image Alt Text\"] = image.altText %}\n\n {% assign product_rows[product_rows.size] = image_row %}\n {% endif %}\n {% endfor %}{% comment %}-- end images loop --{% endcomment %}\n\n {% for product_row in product_rows %}\n {% comment %}\n -- flatten the product rows hash into an array of values\n {% endcomment %}\n\n {% assign row = array %}\n {% for pair in product_row %}\n {% assign row[forloop.index0] = pair[1] %}\n {% endfor %}\n\n {% comment %}\n -- add the row to 2d rows array\n {% endcomment %}\n\n {% assign rows[rows.size] = row %}\n {% endfor %}\n {% endfor %}{% comment %}-- end product loop --{% endcomment %}\n\n {% comment %}\n -- convert 2d array into csv format\n {% endcomment %}\n\n {% assign csv = rows | csv %}\n\n {% if event.preview %}\n {% action \"echo\" csv %}\n {% endif %}\n\n {% capture upload_path %}products__{{ \"now\" | date: \"%Y-%m-%d_T%H-%M-%S_%Z\", tz: \"UTC\" }}.csv{% endcapture %}\n\n {% comment %}\n -- directory paths may or may not have a leading slash (if they do, they're absolute;\n -- if they don't, they're relative), but we always need a trailing slash\n {% endcomment %}\n\n {% if options.sftp_upload_directory != blank %}\n {% assign directory = options.sftp_upload_directory %}\n\n {% if directory.last != \"/\" %}\n {% assign directory = directory | append: \"/\" %}\n {% endif %}\n\n {% assign upload_path = directory | append: upload_path %}\n {% endif%}\n\n {% action \"ftp\" %}\n {\n \"protocol\": \"sftp\",\n \"host\": {{ options.sftp_host__required | json }},\n \"port\": {{ options.sftp_port__required_number | json }},\n \"user\": {{ options.sftp_user__required | json }},\n \"password\": {{ options.sftp_password__required | json }},\n \"uploads\": {\n {{ upload_path | json }}: {{ csv | json }}\n }\n }\n {% endaction %}\n{% endif %}",
"subscriptions": [
"mechanic/user/trigger",
"mechanic/shopify/bulk_operation"
],
"subscriptions_template": "mechanic/user/trigger\n{% if options.run_every_x_hours__number == 24 %}\n mechanic/scheduler/daily\n{% elsif options.run_every_x_hours__number %}\n mechanic/scheduler/hourly\n{% endif %}\nmechanic/shopify/bulk_operation",
"tags": [
"Backups",
"CSV",
"Export",
"FTP"
]
}