From e8f78f6aa969c2332fdde684fcc3fbbce9a60b82 Mon Sep 17 00:00:00 2001 From: Brad Hover Date: Tue, 17 Sep 2024 14:11:01 -0700 Subject: [PATCH] fix looping errors (#402) --- .../script.liquid | 12 ++++++------ ...of-stock-products-to-the-end-of-a-collection.json | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/move-out-of-stock-products-to-the-end-of-a-collection/script.liquid b/docs/move-out-of-stock-products-to-the-end-of-a-collection/script.liquid index c2afafdf..94c77d3f 100644 --- a/docs/move-out-of-stock-products-to-the-end-of-a-collection/script.liquid +++ b/docs/move-out-of-stock-products-to-the-end-of-a-collection/script.liquid @@ -293,10 +293,10 @@ } {% endcapture %} - {% assign result = query | shopify %} + {% assign variants_result = query | shopify %} {% if event.preview %} - {% capture result_json %} + {% capture variants_result_json %} { "data": { "product": { @@ -314,18 +314,18 @@ } {% endcapture %} - {% assign result = result_json | parse_json %} + {% assign variants_result = variants_result_json | parse_json %} {% endif %} - {% for variant in result.data.product.variants.nodes %} + {% for variant in variants_result.data.product.variants.nodes %} {% if variant.inventoryPolicy == "CONTINUE" or variant.inventoryQuantity > 0 %} {% assign has_in_stock_variant = true %} {% break %} {% endif %} {% endfor %} - {% if result.data.products.variants.pageInfo.hasNextPage and has_in_stock_variant != true %} - {% assign variants_cursor = result.data.products.variants.pageInfo.endCursor %} + {% if variants_result.data.product.variants.pageInfo.hasNextPage and has_in_stock_variant != true %} + {% assign variants_cursor = variants_result.data.product.variants.pageInfo.endCursor %} {% else %} {% break %} {% endif %} diff --git a/tasks/move-out-of-stock-products-to-the-end-of-a-collection.json b/tasks/move-out-of-stock-products-to-the-end-of-a-collection.json index 4d153540..ce8bc99e 100644 --- a/tasks/move-out-of-stock-products-to-the-end-of-a-collection.json +++ b/tasks/move-out-of-stock-products-to-the-end-of-a-collection.json @@ -13,7 +13,7 @@ }, "order_status_javascript": null, "perform_action_runs_in_sequence": true, - "script": "{% assign base_sort_order = options.base_sort_order__required %}\n{% assign collection_handles_or_ids_to_include = options.collection_handles_or_ids_to_include__array %}\n{% assign collection_handles_or_ids_to_exclude = options.collection_handles_or_ids_to_exclude__array %}\n{% assign force_manual_sorting_on_collections = options.force_manual_sorting_on_collections__boolean %}\n\n{% assign allowed_base_sort_orders = \"MANUAL,BEST_SELLING,ALPHA_ASC,ALPHA_DESC,PRICE_DESC,PRICE_ASC,CREATED_DESC,CREATED\" | split: \",\" %}\n\n{% unless allowed_base_sort_orders contains base_sort_order %}\n {% error %}\n {{ allowed_base_sort_orders | join: \", \" | prepend: \"Base sort order must be one of: \" | json }}\n {% enderror %}\n{% endunless %}\n\n{% log %}\n {{ base_sort_order | prepend: \"Base sort order for this task run: \" | json }}\n{% endlog %}\n\n{% assign product_sort_order = base_sort_order %}\n{% assign reverse_sort = nil %}\n\n{% case product_sort_order %}\n {% when \"ALPHA_ASC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n\n {% when \"ALPHA_DESC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n {% assign reverse_sort = true %}\n\n {% when \"CREATED_DESC\" %}\n {% assign product_sort_order = \"CREATED\" %}\n {% assign reverse_sort = true %}\n\n {% when \"PRICE_ASC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n\n {% when \"PRICE_DESC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n {% assign reverse_sort = true %}\n{% endcase %}\n\n{% comment %}\n -- query all collections in the shop; do not use a query filter for IDs or handles here since those configuration fields are optional\n{% endcomment %}\n\n{% assign cursor = nil %}\n{% assign collections = array %}\n\n{% for n in (1..100) %}\n {% capture query %}\n query {\n collections(\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n legacyResourceId\n title\n handle\n sortOrder\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 \"id\": \"gid://shopify/Collection/1234567890\",\n \"legacyResourceId\": {{ collection_handles_or_ids_to_include.first | default: \"1234567890\" | json }},\n \"title\": \"Samples\",\n \"handle\": {{ collection_handles_or_ids_to_include.first | json }},\n \"sortOrder\": \"MANUAL\"\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign collections = collections | concat: result.data.collections.nodes %}\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{% comment %}\n -- loop through collections and filter by ID and/or handle as configured\n{% endcomment %}\n\n{% for collection in result.data.collections.nodes %}\n {% if collection_handles_or_ids_to_include != blank %}\n {% unless collection_handles_or_ids_to_include contains collection.legacyResourceId\n or collection_handles_or_ids_to_include contains collection.handle %}\n {% continue %}\n {% endunless %}\n\n {% elsif collection_handles_or_ids_to_exclude != blank %}\n {% if collection_handles_or_ids_to_exclude contains collection.legacyResourceId\n or collection_handles_or_ids_to_exclude contains collection.handle %}\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% comment %}\n -- make sure collection is configured for manual sorting, and optionally update it if not\n {% endcomment %}\n\n {% if collection.sortOrder != \"MANUAL\" %}\n {% if force_manual_sorting_on_collections %}\n {% action \"shopify\" %}\n mutation {\n collectionUpdate(\n input: {\n id: {{ collection.id | json }}\n sortOrder: MANUAL\n }\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log %}\n {{ collection.title | json | append: \" is not configured for manual sorting; skipping.\" | json }}\n {% endlog %}\n\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% comment %}\n -- get all product IDs in this collection using the current sort order\n {% endcomment %}\n\n {% assign all_product_ids_current_sort = array %}\n {% assign cursor = nil %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n collection(id: {{ collection.id | json }}) {\n products(\n sortKey: COLLECTION_DEFAULT\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign product_ids_batch = result.data.collection.products.nodes | map: \"id\" %}\n {% assign all_product_ids_current_sort = all_product_ids_current_sort | concat: product_ids_batch %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- get all products in this collection using the configured sort order; get variants later if needed in order to support 2K variant limit\n {% endcomment %}\n\n {% assign in_stock_product_ids = array %}\n {% assign out_of_stock_product_ids = array %}\n {% assign cursor = nil %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n collection(id: {{ collection.id | json }}) {\n products(\n sortKey: {{ product_sort_order }}\n reverse: {{ reverse_sort | json }}\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n tracksInventory\n hasOutOfStockVariants\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"collection\": {\n \"products\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"tracksInventory\": true,\n \"hasOutOfStockVariants\": true\n },\n {\n \"id\": \"gid://shopify/Product/2345678901\",\n \"tracksInventory\": true,\n \"hasOutOfStockVariants\": false\n },\n {\n \"id\": \"gid://shopify/Product/3456789012\",\n \"tracksInventory\": false\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% comment %}\n -- split products into in-stock and out-of-stock buckets, keeping the configured sort order\n {% endcomment %}\n\n {% for product in result.data.collection.products.nodes %}\n {% assign has_in_stock_variant = nil %}\n\n {% unless product.tracksInventory and product.hasOutOfStockVariants %}\n {% assign has_in_stock_variant = true %}\n\n {% else %}\n {% comment %}\n -- query up to 2K variants to see if any of them are in stock; can break as soon as one is found\n {% endcomment %}\n\n {% assign variants_cursor = nil %}\n\n {% for n in (1..8) %}\n {% capture query %}\n query {\n product(id: {{ product.id | json }}) {\n variants(\n first: 250\n after: {{ variants_cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n inventoryPolicy\n inventoryQuantity\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"product\": {\n \"variants\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/ProductVariant/1234567890\",\n \"inventoryPolicy\": \"DENY\",\n \"inventoryQuantity\": 0\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% for variant in result.data.product.variants.nodes %}\n {% if variant.inventoryPolicy == \"CONTINUE\" or variant.inventoryQuantity > 0 %}\n {% assign has_in_stock_variant = true %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.products.variants.pageInfo.hasNextPage and has_in_stock_variant != true %}\n {% assign variants_cursor = result.data.products.variants.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n {% endunless %}\n\n {% if has_in_stock_variant %}\n {% assign in_stock_product_ids = in_stock_product_ids | push: product.id %}\n {% else %}\n {% assign out_of_stock_product_ids = out_of_stock_product_ids | push: product.id %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- combine product IDs back into single array with all out of stock variants following the in stock ones, but otherwise keeping the configured sort order\n {% endcomment %}\n\n {% assign all_product_ids = in_stock_product_ids | concat: out_of_stock_product_ids %}\n\n {% comment %}\n -- determine which product IDs need to be moved by comparing to original sort order\n {% endcomment %}\n\n {% assign moves = array %}\n\n {% for product_id in all_product_ids %}\n {% if all_product_ids_current_sort[forloop.index0] != product_id %}\n {% assign move = hash %}\n {% assign move[\"id\"] = product_id %}\n {% assign move[\"newPosition\"] = \"\" | append: forloop.index0 %}\n {% assign moves = moves | push: move %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- move to next collection if no moves are necessary\n {% endcomment %}\n\n {% if moves == blank %}\n {% log\n message: \"No position moves necessary for this collection, everything is already in its appropriate sort order.\",\n collection: collection.title\n %}\n {% continue %}\n {% endif %}\n\n {% log\n message: \"Scheduling job(s) to reorder products for this collection.\",\n collection: collection.title,\n moves_count: moves.size\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 move_groups = moves | reverse | in_groups_of: 250, fill_with: false %}\n\n {% for move_group in move_groups %}\n {% action \"shopify\" %}\n mutation {\n collectionReorderProducts(\n id: {{ collection.id | json }}\n moves: {{ move_group | graphql_arguments }}\n ) {\n job {\n id\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endfor %}\n{% endfor %}\n", + "script": "{% assign base_sort_order = options.base_sort_order__required %}\n{% assign collection_handles_or_ids_to_include = options.collection_handles_or_ids_to_include__array %}\n{% assign collection_handles_or_ids_to_exclude = options.collection_handles_or_ids_to_exclude__array %}\n{% assign force_manual_sorting_on_collections = options.force_manual_sorting_on_collections__boolean %}\n\n{% assign allowed_base_sort_orders = \"MANUAL,BEST_SELLING,ALPHA_ASC,ALPHA_DESC,PRICE_DESC,PRICE_ASC,CREATED_DESC,CREATED\" | split: \",\" %}\n\n{% unless allowed_base_sort_orders contains base_sort_order %}\n {% error %}\n {{ allowed_base_sort_orders | join: \", \" | prepend: \"Base sort order must be one of: \" | json }}\n {% enderror %}\n{% endunless %}\n\n{% log %}\n {{ base_sort_order | prepend: \"Base sort order for this task run: \" | json }}\n{% endlog %}\n\n{% assign product_sort_order = base_sort_order %}\n{% assign reverse_sort = nil %}\n\n{% case product_sort_order %}\n {% when \"ALPHA_ASC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n\n {% when \"ALPHA_DESC\" %}\n {% assign product_sort_order = \"TITLE\" %}\n {% assign reverse_sort = true %}\n\n {% when \"CREATED_DESC\" %}\n {% assign product_sort_order = \"CREATED\" %}\n {% assign reverse_sort = true %}\n\n {% when \"PRICE_ASC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n\n {% when \"PRICE_DESC\" %}\n {% assign product_sort_order = \"PRICE\" %}\n {% assign reverse_sort = true %}\n{% endcase %}\n\n{% comment %}\n -- query all collections in the shop; do not use a query filter for IDs or handles here since those configuration fields are optional\n{% endcomment %}\n\n{% assign cursor = nil %}\n{% assign collections = array %}\n\n{% for n in (1..100) %}\n {% capture query %}\n query {\n collections(\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n legacyResourceId\n title\n handle\n sortOrder\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 \"id\": \"gid://shopify/Collection/1234567890\",\n \"legacyResourceId\": {{ collection_handles_or_ids_to_include.first | default: \"1234567890\" | json }},\n \"title\": \"Samples\",\n \"handle\": {{ collection_handles_or_ids_to_include.first | json }},\n \"sortOrder\": \"MANUAL\"\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign collections = collections | concat: result.data.collections.nodes %}\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{% comment %}\n -- loop through collections and filter by ID and/or handle as configured\n{% endcomment %}\n\n{% for collection in result.data.collections.nodes %}\n {% if collection_handles_or_ids_to_include != blank %}\n {% unless collection_handles_or_ids_to_include contains collection.legacyResourceId\n or collection_handles_or_ids_to_include contains collection.handle %}\n {% continue %}\n {% endunless %}\n\n {% elsif collection_handles_or_ids_to_exclude != blank %}\n {% if collection_handles_or_ids_to_exclude contains collection.legacyResourceId\n or collection_handles_or_ids_to_exclude contains collection.handle %}\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% comment %}\n -- make sure collection is configured for manual sorting, and optionally update it if not\n {% endcomment %}\n\n {% if collection.sortOrder != \"MANUAL\" %}\n {% if force_manual_sorting_on_collections %}\n {% action \"shopify\" %}\n mutation {\n collectionUpdate(\n input: {\n id: {{ collection.id | json }}\n sortOrder: MANUAL\n }\n ) {\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n\n {% else %}\n {% log %}\n {{ collection.title | json | append: \" is not configured for manual sorting; skipping.\" | json }}\n {% endlog %}\n\n {% continue %}\n {% endif %}\n {% endif %}\n\n {% comment %}\n -- get all product IDs in this collection using the current sort order\n {% endcomment %}\n\n {% assign all_product_ids_current_sort = array %}\n {% assign cursor = nil %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n collection(id: {{ collection.id | json }}) {\n products(\n sortKey: COLLECTION_DEFAULT\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% assign product_ids_batch = result.data.collection.products.nodes | map: \"id\" %}\n {% assign all_product_ids_current_sort = all_product_ids_current_sort | concat: product_ids_batch %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- get all products in this collection using the configured sort order; get variants later if needed in order to support 2K variant limit\n {% endcomment %}\n\n {% assign in_stock_product_ids = array %}\n {% assign out_of_stock_product_ids = array %}\n {% assign cursor = nil %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n collection(id: {{ collection.id | json }}) {\n products(\n sortKey: {{ product_sort_order }}\n reverse: {{ reverse_sort | json }}\n first: 250\n after: {{ cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n tracksInventory\n hasOutOfStockVariants\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"collection\": {\n \"products\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Product/1234567890\",\n \"tracksInventory\": true,\n \"hasOutOfStockVariants\": true\n },\n {\n \"id\": \"gid://shopify/Product/2345678901\",\n \"tracksInventory\": true,\n \"hasOutOfStockVariants\": false\n },\n {\n \"id\": \"gid://shopify/Product/3456789012\",\n \"tracksInventory\": false\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% comment %}\n -- split products into in-stock and out-of-stock buckets, keeping the configured sort order\n {% endcomment %}\n\n {% for product in result.data.collection.products.nodes %}\n {% assign has_in_stock_variant = nil %}\n\n {% unless product.tracksInventory and product.hasOutOfStockVariants %}\n {% assign has_in_stock_variant = true %}\n\n {% else %}\n {% comment %}\n -- query up to 2K variants to see if any of them are in stock; can break as soon as one is found\n {% endcomment %}\n\n {% assign variants_cursor = nil %}\n\n {% for n in (1..8) %}\n {% capture query %}\n query {\n product(id: {{ product.id | json }}) {\n variants(\n first: 250\n after: {{ variants_cursor | json }}\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n inventoryPolicy\n inventoryQuantity\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign variants_result = query | shopify %}\n\n {% if event.preview %}\n {% capture variants_result_json %}\n {\n \"data\": {\n \"product\": {\n \"variants\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/ProductVariant/1234567890\",\n \"inventoryPolicy\": \"DENY\",\n \"inventoryQuantity\": 0\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign variants_result = variants_result_json | parse_json %}\n {% endif %}\n\n {% for variant in variants_result.data.product.variants.nodes %}\n {% if variant.inventoryPolicy == \"CONTINUE\" or variant.inventoryQuantity > 0 %}\n {% assign has_in_stock_variant = true %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% if variants_result.data.product.variants.pageInfo.hasNextPage and has_in_stock_variant != true %}\n {% assign variants_cursor = variants_result.data.product.variants.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n {% endunless %}\n\n {% if has_in_stock_variant %}\n {% assign in_stock_product_ids = in_stock_product_ids | push: product.id %}\n {% else %}\n {% assign out_of_stock_product_ids = out_of_stock_product_ids | push: product.id %}\n {% endif %}\n {% endfor %}\n\n {% if result.data.collection.products.pageInfo.hasNextPage %}\n {% assign cursor = result.data.collection.products.pageInfo.endCursor %}\n {% else %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- combine product IDs back into single array with all out of stock variants following the in stock ones, but otherwise keeping the configured sort order\n {% endcomment %}\n\n {% assign all_product_ids = in_stock_product_ids | concat: out_of_stock_product_ids %}\n\n {% comment %}\n -- determine which product IDs need to be moved by comparing to original sort order\n {% endcomment %}\n\n {% assign moves = array %}\n\n {% for product_id in all_product_ids %}\n {% if all_product_ids_current_sort[forloop.index0] != product_id %}\n {% assign move = hash %}\n {% assign move[\"id\"] = product_id %}\n {% assign move[\"newPosition\"] = \"\" | append: forloop.index0 %}\n {% assign moves = moves | push: move %}\n {% endif %}\n {% endfor %}\n\n {% comment %}\n -- move to next collection if no moves are necessary\n {% endcomment %}\n\n {% if moves == blank %}\n {% log\n message: \"No position moves necessary for this collection, everything is already in its appropriate sort order.\",\n collection: collection.title\n %}\n {% continue %}\n {% endif %}\n\n {% log\n message: \"Scheduling job(s) to reorder products for this collection.\",\n collection: collection.title,\n moves_count: moves.size\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 move_groups = moves | reverse | in_groups_of: 250, fill_with: false %}\n\n {% for move_group in move_groups %}\n {% action \"shopify\" %}\n mutation {\n collectionReorderProducts(\n id: {{ collection.id | json }}\n moves: {{ move_group | graphql_arguments }}\n ) {\n job {\n id\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endaction %}\n {% endfor %}\n{% endfor %}\n", "subscriptions": [ "mechanic/user/trigger" ],