forked from lightward/mechanic-tasks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathauto-sort-collections-by-product-properties.json
29 lines (29 loc) · 11.8 KB
/
auto-sort-collections-by-product-properties.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": "This task re-sorts your collections by any product property that you choose. Use the \"Product property lookups\" option to control what attribute the task \"looks up\". For example, using \"published_at\" will result in sorting by the date and time the product was published. Add more than one lookup to dive more deeply into product data: using the lookups \"metafields\", \"store\", \"priority\", and \"value\" will result in a collection sorted by the `store.priority` metafield value on each product.\n\nRefer to [Shopify's API documentation](https://help.shopify.com/en/api/reference/products/product) to find the product property you're looking for.\n\nRun this task manually to re-sort your collections on demand, or choose to run it hourly or nightly.\n\nThis task will scan all collections in the shop on each run, unless you configure it to only sort certain collections using each collection's title or ID. The collections processed by this task must be configured for manual sorting, otherwise they will be ignored. [Learn how to change the sort order of your collections.](https://help.shopify.com/en/manual/products/collections/collection-layout#change-the-sort-order-for-the-products-in-a-collection)",
"halt_action_run_sequence_on_error": false,
"name": "Auto-sort collections by product properties",
"online_store_javascript": null,
"options": {
"product_property_lookups__array_required": [
"published_at"
],
"only_sort_these_collections__array": null,
"reverse_sort__boolean": false,
"run_hourly__boolean": false,
"run_daily__boolean": false
},
"order_status_javascript": null,
"perform_action_runs_in_sequence": false,
"script": "{% assign product_property_lookups = options.product_property_lookups__array_required %}\n{% assign only_sort_these_collections = options.only_sort_these_collections__array %}\n{% assign reverse_sort = options.reverse_sort__boolean %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% comment %}\n -- get IDs for all manually sorted collections, optionally restricted to specific collections by ID or title\n {% endcomment %}\n\n {% assign collection_ids_to_sort = array %}\n {% assign cursor = nil %}\n\n {% for n in (1..50) %}\n {% capture query %}\n query {\n collections(\n first: 250\n after: {{ cursor | json }}\n query: {{ search_query | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n legacyResourceId\n sortOrder\n title\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"collections\": {\n \"nodes\": [\n {\n \"legacyResourceId\": \"1234567890\",\n \"sortOrder\": \"MANUAL\"\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% for collection in result.data.collections.nodes %}\n {% if only_sort_these_collections != blank %}\n {% unless only_sort_these_collections contains collection.legacyResourceId\n or only_sort_these_collections contains collection.title\n or event.preview\n %}\n {% continue %}\n {% endunless %}\n {% endif %}\n\n {% if collection.sortOrder != \"MANUAL\" %}\n {% log\n message: \"Collection is not configured for manual sorting; skipping.\",\n collection: collection\n %}\n {% continue %}\n {% endif %}\n\n {% assign collection_ids_to_sort = collection_ids_to_sort | push: collection.legacyResourceId %}\n {% endfor %}\n\n {% if result.data.collections.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collections.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if collection_ids_to_sort == blank %}\n {% log \"No collections qualified to be sorted on this task run.\" %}\n {% break %}\n {% endif %}\n\n {% comment %}\n -- save the qualified collection IDs to the cache for lookup in later child events\n -- NOTE: using event ID instead of task ID in case one instance of this task runs concurrent with another\n {% endcomment %}\n\n {% assign cache_key = event.id | prepend: \"collection_ids_to_sort_\" %}\n {% action \"cache\", \"set\", cache_key, collection_ids_to_sort %}\n\n {% log\n message: \"Begin processing collections loop using sequential child events.\",\n total_collections_to_sort: collection_ids_to_sort.size\n %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/collection_sort/process\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"cache_key\": {{ cache_key | json }},\n \"cache_index\": 0\n }\n }\n {% endaction %}\n\n{% elsif event.topic == \"user/collection_sort/process\" %}\n {% assign cache_key = event.data.cache_key %}\n {% assign cache_index = event.data.cache_index %}\n {% assign collection_ids_to_sort = cache[cache_key] %}\n {% assign collection_id = collection_ids_to_sort[cache_index] %}\n {% assign collection = shop.collections[collection_id] %}\n\n {% if event.preview %}\n {% comment %}\n -- use stub data instead of a preview event to incorporate the configured product properties\n {% endcomment %}\n\n {% assign product_ids = array | push: \"1234567890\", \"3456789012\", \"2345678901\" %}\n {% assign products = array %}\n\n {% for product_id in product_ids %}\n {% assign product = hash %}\n\n {% for lookup in product_property_lookups reversed %}\n {% if forloop.first %}\n {% assign product[lookup] = product_id %}\n\n {% else %}\n {% assign temp = product %}\n {% assign product = hash %}\n {% assign product[lookup] = temp %}\n {% endif %}\n {% endfor %}\n\n {% assign product[\"id\"] = product_id %}\n {% assign product[\"admin_graphql_api_id\"] = \"gid://shopify/Product/\" | append: product_id %}\n {% assign products[products.size] = product %}\n {% endfor %}\n\n {% capture collection_json %}\n {\n \"title\": {{ only_sort_these_collections[0] | default: \"Some collection\" | json }},\n \"admin_graphql_api_id\": \"gid://shopify/Collection/1234567890\",\n \"products\": {{ products | json }}\n }\n {% endcapture %}\n\n {% assign collection = collection_json | parse_json %}\n {% endif %}\n\n {% comment %}\n -- process products using REST resources to align with configured product properties\n {% endcomment %}\n\n {% assign moves = array %}\n {% assign product_ids_and_positions = hash %}\n {% assign product_ids_and_values = array %}\n\n {% for product in collection.products %}\n {% assign product_ids_and_positions[product.admin_graphql_api_id] = forloop.index0 %}\n\n {% assign product_id_and_value = hash %}\n {% assign product_id_and_value[\"id\"] = product.admin_graphql_api_id %}\n\n {% assign value = product %}\n\n {% for lookup in options.product_property_lookups__array_required %}\n {% comment %}\n -- NOTE: command methods (size, first, last) can only be invoked via dot notation\n {% endcomment %}\n\n {% case lookup %}\n {% when \"size\" %}\n {% assign value = value.size %}\n {% when \"first\" %}\n {% assign value = value.first %}\n {% when \"last\" %}\n {% assign value = value.last %}\n {% else %}\n {% assign value = value[lookup] %}\n {% endcase %}\n {% endfor %}\n\n {% comment %}\n -- make sure this is always a serializable/sortable object, defaulting to nil in the case of an empty string so that SKU values which are *either* nil or an empty string always end up at the end of the list.\n {% endcomment %}\n\n {% assign product_id_and_value[\"value\"] = value | json | parse_json | default: nil %}\n\n {% assign product_ids_and_values[product_ids_and_values.size] = product_id_and_value %}\n {% endfor %}\n\n {% assign sorted_product_values = product_ids_and_values | sort: \"value\" %}\n {% assign sorted_product_ids = product_ids_and_values | sort: \"value\" | map: \"id\" %}\n\n {% if reverse_sort %}\n {% assign sorted_product_ids = sorted_product_ids | reverse %}\n {% endif %}\n\n {% comment %}\n -- determine the moves necessary to place products in their sorted positions\n {% endcomment %}\n\n {% for sorted_product_id in sorted_product_ids %}\n {% if forloop.index0 != product_ids_and_positions[sorted_product_id] %}\n {% assign move = hash %}\n {% assign move[\"id\"] = sorted_product_id %}\n {% assign move[\"newPosition\"] = \"\" | append: forloop.index0 %}\n {% assign moves[moves.size] = move %}\n {% endif %}\n {% endfor %}\n\n {% if moves == blank %}\n {% log\n message: \"No moves necessary for this collection, everything is already in its appropriate sort order.\",\n collection: collection.title\n %}\n\n {% else %}\n {% log\n message: \"Collection requires sorting.\",\n collection: collection.title\n %}\n\n {% comment %}\n -- using reverse filter below due to a bug in the collectionReorderProducts mutation\n -- this filter will NOT affect the sort order determined above\n {% endcomment %}\n\n {% assign moves_in_groups = moves | reverse | in_groups_of: 250, fill_with: false %}\n\n {% for move_group in moves_in_groups %}\n {% action \"shopify\" %}\n mutation {\n collectionReorderProducts(\n id: {{ collection.admin_graphql_api_id | json }}\n moves: {{ move_group | graphql_arguments }}\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endfor %}\n {% endif %}\n\n {% assign new_cache_index = cache_index | plus: 1 %}\n\n {% log\n collections_seen: new_cache_index,\n total_collections_to_sort: collection_ids_to_sort.size\n %}\n\n {% comment %}\n -- process next collection in the cache if there is one\n {% endcomment %}\n\n {% if new_cache_index < collection_ids_to_sort.size %}\n {% action \"event\" %}\n {\n \"topic\": \"user/collection_sort/process\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"cache_key\": {{ cache_key | json }},\n \"cache_index\": {{ new_cache_index }}\n }\n }\n {% endaction %}\n\n {% else %}\n {% comment %}\n -- use a distinct user event to indicate the entire task run is complete, so it can be filtered in the event log\n {% endcomment %}\n\n {% action \"event\" %}\n {\n \"topic\": \"user/collection_sort/complete\",\n \"task_id\": {{ task.id | json }},\n \"data\": {\n \"cache_key\": {{ cache_key | json }},\n \"cache_index\": {{ new_cache_index }}\n }\n }\n {% endaction %}\n {% endif %}\n\n{% elsif event.topic == \"user/collection_sort/complete\" %}\n {% assign cache_key = event.data.cache_key %}\n {% assign cache_index = event.data.cache_index %}\n\n {% log\n message: \"Collection sorting complete. Deleting cached collection IDs.\",\n collections_seen: cache_index\n %}\n\n {% action \"cache\", \"del\", cache_key %}\n{% endif %}\n",
"subscriptions": [
"mechanic/user/trigger",
"user/collection_sort/process",
"user/collection_sort/complete"
],
"subscriptions_template": "mechanic/user/trigger\nuser/collection_sort/process\nuser/collection_sort/complete\n{% if options.run_hourly__boolean %}\n mechanic/scheduler/hourly\n{% elsif options.run_daily__boolean %}\n mechanic/scheduler/daily\n{% endif %}",
"tags": [
"Collections",
"Products",
"Sort"
]
}