From 9b586b080d99037e16acaa332b3bccbe7f560a40 Mon Sep 17 00:00:00 2001 From: Brad Hover Date: Sun, 9 Jun 2024 21:11:06 -0700 Subject: [PATCH] new task: auto-create collections by metafield --- docs/README.md | 5 + .../README.md | 68 +++ .../script.liquid | 481 ++++++++++++++++++ ...reate-collections-by-metafield-values.json | 61 +++ 4 files changed, 615 insertions(+) create mode 100644 docs/auto-create-collections-by-metafield-values/README.md create mode 100644 docs/auto-create-collections-by-metafield-values/script.liquid create mode 100644 tasks/auto-create-collections-by-metafield-values.json diff --git a/docs/README.md b/docs/README.md index 724944ce..200a9105 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,6 +18,7 @@ This directory is built automatically. Each task's documentation is generated fr * [Archive orders when fulfilled](./archive-orders-when-fulfilled) * [Archive orders when tagged](./archive-orders-when-tagged) * [Ask for reviews a week after order fulfillment](./ask-for-reviews-a-week-after-order-fulfillment) +* [Auto create collections by metafield values](./auto-create-collections-by-metafield-values) * [Auto publish products by tag](./auto-publish-products-by-tag) * [Auto-add a note for new orders having a certain tag](./auto-add-order-note-for-new-tagged-orders) * [Auto-add an order note for customers having a certain tag](./auto-add-an-order-note-for-customers-having-a-certain-tag) @@ -617,6 +618,7 @@ This directory is built automatically. Each task's documentation is generated fr ### Collections +* [Auto create collections by metafield values](./auto-create-collections-by-metafield-values) * [Auto-add products to a custom collection when tagged](./auto-add-products-to-a-custom-collection-when-tagged) * [Auto-create collections by product type or vendor](./auto-create-collections-by-product-type-or-vendor) * [Auto-sort collections by inventory levels](./auto-sort-collections-by-inventory-levels) @@ -1095,6 +1097,7 @@ This directory is built automatically. Each task's documentation is generated fr ### Metafields * [Add Option Name as a Variant Metafield for In Stock Variants](./add-option-names-as-variant-metafields-for-in-stock-variants) +* [Auto create collections by metafield values](./auto-create-collections-by-metafield-values) * [Auto-associate variants with a delivery profile, by metafield value](./auto-associate-variants-with-a-delivery-profile-by-metafield-value) * [Auto-copy customer metafields to new orders](./auto-copy-customer-metafields-to-new-orders) * [Auto-delete customer metafields older than X days](./auto-delete-customer-metafields-older-than-x-days) @@ -1338,6 +1341,7 @@ This directory is built automatically. Each task's documentation is generated fr * [Add all products to a certain sales channel](./add-all-products-to-a-certain-sales-channel) * [Advanced: Scheduled Price Changes](./advanced-scheduled-price-changes) +* [Auto create collections by metafield values](./auto-create-collections-by-metafield-values) * [Auto publish products by tag](./auto-publish-products-by-tag) * [Auto-add products to a custom collection when tagged](./auto-add-products-to-a-custom-collection-when-tagged) * [Auto-associate products with a delivery profile, by product tag](./auto-associate-products-with-a-delivery-profile-by-product-tag) @@ -1437,6 +1441,7 @@ This directory is built automatically. Each task's documentation is generated fr ### Publish * [Advanced: Scheduled section publishing](./advanced-scheduled-section-publishing) +* [Auto create collections by metafield values](./auto-create-collections-by-metafield-values) * [Auto publish products by tag](./auto-publish-products-by-tag) * [Auto-create collections by product type or vendor](./auto-create-collections-by-product-type-or-vendor) * [Auto-publish new products](./auto-publish-new-products) diff --git a/docs/auto-create-collections-by-metafield-values/README.md b/docs/auto-create-collections-by-metafield-values/README.md new file mode 100644 index 00000000..db598fc9 --- /dev/null +++ b/docs/auto-create-collections-by-metafield-values/README.md @@ -0,0 +1,68 @@ +# Auto create collections by metafield values + +Tags: Collections, Metafields, Products, Publish + +Running on a schedule or manually, this task will create automated collections based on the validation values of the configured product metafield definitions. Additionally, configuring one or more exact sales channel names will enable publishing of any newly created collections by this task to those sales channels. + +* View in the task library: [tasks.mechanic.dev/auto-create-collections-by-metafield-values](https://tasks.mechanic.dev/auto-create-collections-by-metafield-values) +* Task JSON, for direct import: [task.json](../../tasks/auto-create-collections-by-metafield-values.json) +* Preview task code: [script.liquid](./script.liquid) + +## Default options + +```json +{ + "product_metafields_and_collection_title_prefixes__keyval_required": { + "custom.product_color": "Color: " + }, + "names_of_sales_channels_to_publish_collections_to__array": [ + "Online Store" + ], + "run_daily__boolean": null, + "run_hourly__boolean": null, + "test_mode__boolean": true +} +``` + +[Learn about task options in Mechanic](https://learn.mechanic.dev/core/tasks/options) + +## Subscriptions + +```liquid +{% if options.run_hourly__boolean %} + mechanic/scheduler/hourly +{% elsif options.run_daily__boolean %} + mechanic/scheduler/daily +{% endif %} +mechanic/user/trigger +mechanic/actions/perform +``` + +[Learn about event subscriptions in Mechanic](https://learn.mechanic.dev/core/tasks/subscriptions) + +## Documentation + +Running on a schedule or manually, this task will create automated collections based on the validation values of the configured product metafield definitions. Additionally, configuring one or more exact sales channel names will enable publishing of any newly created collections by this task to those sales channels. + +To configure the 'Product metafields and collection title prefixes' field, use the left-hand side to enter metafield definition identifiers in the format of *namespace.key* (e.g. "custom.product_colors"), and the right-hand side to enter the optional title prefix (e.g. "Color: ") for any collections created for that metafield's validation values. + +Upon creation by this task, an automated collection will have a single condition set which auto-includes any products that use the paired metafield definition and value. All other aspects of the collection (e.g. title, handle, sort order, additional conditions, sales channels, etc.) can be manually changed after creation if needed. + +To work with this task, product metafields will need to be [activated](https://help.shopify.com/en/manual/custom-data/metafields/automated-collections#activating-automated-collections) as a collection condition. + +Notes: +- This task only supports metafields of type "Single line text" or "Single line text (List)". +- Before creating a new collection, this task will verify that there are no existing collections that use that specific metafield value as a product metafield condition. +- This task will log any collections found that use an outdated value from a metafield definition. + +## Installing this task + +Find this task [in the library at tasks.mechanic.dev](https://tasks.mechanic.dev/auto-create-collections-by-metafield-values), and use the "Try this task" button. Or, import [this task's JSON export](../../tasks/auto-create-collections-by-metafield-values.json) – see [Importing and exporting tasks](https://learn.mechanic.dev/core/tasks/import-and-export) to learn how imports work. + +## Contributions + +Found a bug? Got an improvement to add? Start here: [../../CONTRIBUTING.md](../../CONTRIBUTING.md). + +## Task requests + +Submit your [task requests](https://mechanic.canny.io/task-requests) for consideration by the Mechanic community, and they may be chosen for development and inclusion in the [task library](https://tasks.mechanic.dev/)! diff --git a/docs/auto-create-collections-by-metafield-values/script.liquid b/docs/auto-create-collections-by-metafield-values/script.liquid new file mode 100644 index 00000000..b9600ed0 --- /dev/null +++ b/docs/auto-create-collections-by-metafield-values/script.liquid @@ -0,0 +1,481 @@ +{% assign product_metafields_and_collection_title_prefixes = options.product_metafields_and_collection_title_prefixes__keyval_required %} +{% assign sales_channel_names = options.names_of_sales_channels_to_publish_collections_to__array %} +{% assign run_daily = options.run_daily__boolean %} +{% assign run_hourly = options.run_hourly__boolean %} +{% assign test_mode = options.test_mode__boolean %} + +{% if event.preview %} + {% assign product_metafields_and_collection_title_prefixes = hash %} + {% assign product_metafields_and_collection_title_prefixes["custom.product_colors"] = "Color: " %} +{% endif %} + +{% assign product_metafields = product_metafields_and_collection_title_prefixes | keys %} + +{% if event.topic == "mechanic/user/trigger" or event.topic contains "mechanic/scheduler/" %} + {% if sales_channel_names != blank %} + {% comment %} + -- check if the configured sales channels exist in this shop by name; save the publication IDs for lookup later + {% endcomment %} + + {% assign publication_ids = array %} + + {% capture query %} + query { + publications(first: 250) { + nodes { + id + name + } + } + } + {% endcapture %} + + {% assign result = query | shopify %} + + {% if event.preview %} + {% capture result_json %} + { + "data": { + "publications": { + "nodes": [ + { + "id": "gid://shopify/Publication/1234567890", + "name": {{ sales_channel_names[0] | json }} + } + ] + } + } + } + {% endcapture %} + + {% assign result = result_json | parse_json %} + {% endif %} + + {% assign publications = result.data.publications.nodes %} + {% assign publication_names = publications | map: "name" | sort %} + {% assign publications_indexed_by_name = publications | index_by: "name" %} + + {% for sales_channel_name in sales_channel_names %} + {% if publication_names contains sales_channel_name %} + {% assign publication_ids = publication_ids | push: publications_indexed_by_name[sales_channel_name].id %} + + {% else %} + {% unless event.preview %} + {% comment %} + -- using action error here so the task will continue with any other configured and matched sales channels + {% endcomment %} + + {% action "echo" + __error: "A configured sales channel name does not match any of the publication names available in this shop.", + sales_channel_name: sales_channel_name, + publication_names: publication_names + %} + {% endunless %} + {% endif %} + {% endfor %} + {% endif %} + + {% comment %} + -- loop through all configured metafields to get their metafield definition and validation values to be used as collection conditions + {% endcomment %} + + {% assign metafield_definitions = array %} + + {% for product_metafield in product_metafields %} + {% capture query %} + query { + metafieldDefinitions( + first: 1 + ownerType: PRODUCT + namespace: {{ product_metafield | split: "." | first | json }} + key: {{ product_metafield | split: "." | last | json }} + ) { + nodes { + id + name + namespace + key + ownerType + type { + name + } + useAsCollectionCondition + validations { + value + } + } + } + } + {% endcapture %} + + {% assign result = query | shopify %} + + {% if event.preview %} + {% capture result_json %} + { + "data": { + "metafieldDefinitions": { + "nodes": [ + { + "id": "gid://shopify/MetafieldDefinition/1234567890", + "name": "Product Colors", + "namespace": "custom", + "key": "product_colors", + "ownerType": "PRODUCT", + "type": { + "name": "list.single_line_text_field" + }, + "useAsCollectionCondition": true, + "validations": [ + { + "value": "[\"Blue\",\"Red\"]" + } + ] + } + ] + } + } + } + {% endcapture %} + + {% assign result = result_json | parse_json %} + {% endif %} + + {% assign metafield_definition = result.data.metafieldDefinitions.nodes.first %} + {% assign metafield_values + = metafield_definition.validations.first.value + | default: "[]" + | parse_json + %} + + {% comment %} + -- validate that the metafield definition exists and is configured correctly + -- using action errors for validation so the task will continue running and check all configured metafields + {% endcomment %} + + {% if metafield_definition == blank %} + {% action "echo" + __error: "Metafield definition not found for this configured metafield and owner type (PRODUCT).", + product_metafield: product_metafield + %} + {% continue %} + {% endif %} + + {% unless metafield_definition.type.name contains "single_line_text_field" %} + {% action "echo" + __error: "Metafield definition for this configured metafield must be a type of 'Single line text' or 'Single line text (List)'.", + metafield_definition: metafield_definition, + product_metafield: product_metafield + %} + {% continue %} + {% endunless %} + + {% unless metafield_definition.useAsCollectionCondition %} + {% action "echo" + __error: "Metafield definition for this configured metafield must be set for use by 'Automated collections'.", + metafield_definition: metafield_definition, + product_metafield: product_metafield + %} + {% continue %} + {% endunless %} + + {% if metafield_values == blank %} + {% action "echo" + __error: "Metafield definition validation values not found for this configured metafield.", + metafield_definition: metafield_definition, + product_metafield: product_metafield + %} + {% continue %} + {% endif %} + + {% comment %} + -- all validation checks passed; extend the metafield definition object to store collection data + {% endcomment %} + + {% assign metafield_definition["metafield_values"] = metafield_values %} + {% assign metafield_definition["collection_title_prefix"] = product_metafields_and_collection_title_prefixes[product_metafield] %} + {% assign metafield_definition["collections_by_value"] = hash %} + + {% for metafield_value in metafield_values %} + {% assign metafield_definition["collections_by_value"][metafield_value] = array %} + {% endfor %} + + {% assign metafield_definitions = metafield_definitions | push: metafield_definition %} + {% endfor %} + + {% if metafield_definitions == blank %} + {% log "None of the configured metafields meet all of the necessary conditions for usage by this task." %} + {% break %} + {% endif %} + + {% comment %} + -- query automated collections; save collection details for any that use a metafield configured in this task as a condition + {% endcomment %} + + {% assign cursor = nil %} + + {% for n in (1..100) %} + {% capture query %} + query { + collections( + first: 250 + after: {{ cursor | json }} + query: "collection_type:smart" + ) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + title + handle + templateSuffix + sortOrder + ruleSet { + appliedDisjunctively + rules { + column + relation + condition + conditionObject { + __typename + ... on CollectionRuleMetafieldCondition { + metafieldDefinition { + id + } + } + } + } + } + } + } + + } + {% endcapture %} + + {% assign result = query | shopify %} + + {% if event.preview %} + {% capture result_json %} + { + "data": { + "collections": { + "nodes": [ + { + "id": "gid://shopify/Collection/1234567890", + "ruleSet": { + "rules": [ + { + "column": "PRODUCT_METAFIELD_DEFINITION", + "relation": "EQUALS", + "condition": "Blue", + "conditionObject": { + "metafieldDefinition": { + "id": "gid://shopify/MetafieldDefinition/1234567890" + } + } + } + ] + } + }, + { + "id": "gid://shopify/Collection/2345678901", + "ruleSet": { + "rules": [ + { + "column": "PRODUCT_METAFIELD_DEFINITION", + "relation": "EQUALS", + "condition": "Grey", + "conditionObject": { + "metafieldDefinition": { + "id": "gid://shopify/MetafieldDefinition/1234567890" + } + } + } + ] + } + } + ] + } + } + } + {% endcapture %} + + {% assign result = result_json | parse_json %} + {% endif %} + + {% for collection in result.data.collections.nodes %} + {% if collection.ruleSet == blank %} + {% continue %} + {% endif %} + + {% for rule in collection.ruleSet.rules %} + {% unless rule.column == "PRODUCT_METAFIELD_DEFINITION" and rule.relation == "EQUALS" %} + {% continue %} + {% endunless %} + + {% assign matched_metafield_definition + = metafield_definitions + | where: "id", rule.conditionObject.metafieldDefinition.id + | first + %} + + {% unless matched_metafield_definition %} + {% continue %} + {% endunless %} + + {% unless matched_metafield_definition.metafield_values contains rule.condition %} + {% unless event.preview %} + {% log + message: "Found collection using outdated metafield value condition for the matched metafield definition.", + collection: collection, + matched_metafield_definition: matched_metafield_definition + %} + {% endunless %} + + {% continue %} + {% endunless %} + + {% assign matched_metafield_definition["collections_by_value"][rule.condition] + = matched_metafield_definition["collections_by_value"][rule.condition] + | push: collection.id + %} + {% endfor %} + {% endfor %} + + {% if result.data.collections.pageInfo.hasNextPage %} + {% assign cursor = result.data.collections.pageInfo.endCursor %} + {% else %} + {% break %} + {% endif %} + {% endfor %} + + {% unless event.preview %} + {% log metafield_definitions_with_extended_data: metafield_definitions %} + {% endunless %} + + {% comment %} + -- check all extended metafield definition objects to see if any new collections need to be made on this task run + {% endcomment %} + + {% assign collections_to_create = array %} + + {% for metafield_definition in metafield_definitions %} + {% for metafield_value in metafield_definition.metafield_values %} + {% if metafield_definition.collections_by_value[metafield_value] == blank %} + {% assign product_metafield = metafield_definition.namespace | append: "." | append: metafield_definition.key %} + + {% assign collection_to_create = hash %} + {% assign collection_to_create["product_metafield"] = product_metafield %} + {% assign collection_to_create["collection_title"] = metafield_value | prepend: metafield_definition.collection_title_prefix %} + {% assign collection_to_create["metafield_value"] = metafield_value %} + {% assign collection_to_create["metafield_definition_id"] = metafield_definition.id %} + {% assign collections_to_create = collections_to_create | push: collection_to_create %} + {% endif %} + {% endfor %} + {% endfor %} + + {% unless event.preview %} + {% log collections_to_create: collections_to_create %} + {% endunless %} + + {% if test_mode %} + {% log "This task is set for 'Test mode' and no new collections will be created." %} + {% break %} + {% endif %} + + {% for collection_to_create in collections_to_create %} + {% capture mutation %} + mutation { + collectionCreate( + input: { + title: {{ collection_to_create.collection_title | json }} + ruleSet: { + appliedDisjunctively: false + rules: [ + { + column: PRODUCT_METAFIELD_DEFINITION + relation: EQUALS + condition: {{ collection_to_create.metafield_value | json }} + conditionObjectId: {{ collection_to_create.metafield_definition_id | json }} + } + ] + } + } + ) { + collection { + id + title + handle + } + userErrors { + field + message + } + } + } + {% endcapture %} + + {% action %} + { + "type": "shopify", + "options": {{ mutation | json }}, + "meta": { + "publication_ids": {{ publication_ids | json }} + } + } + {% endaction %} + {% endfor %} + +{% elsif event.topic == "mechanic/actions/perform" %} + {% comment %} + -- only respond to successful creation of collections + {% endcomment %} + + {% unless action.type == "shopify" and action.run.ok and action.run.result.data.collectionCreate %} + {% break %} + {% endunless %} + + {% assign collection_id = action.run.result.data.collectionCreate.collection.id %} + {% assign publication_ids = action.meta.publication_ids %} + + {% comment %} + -- publish the new collection to all of the valid publications configured in the task + {% endcomment %} + + {% assign mutations = array %} + + {% for publication_id in publication_ids %} + {% capture mutation %} + publishablePublish{{ forloop.index }}: publishablePublish( + id: {{ collection_id | json }} + input: { + publicationId: {{ publication_id | json }} + } + ) { + publishable { + ... on Collection { + id + title + handle + } + } + userErrors { + field + message + } + } + {% endcapture %} + + {% assign mutations = mutations | push: mutation %} + {% endfor %} + + {% if mutations != blank %} + {% action "shopify" %} + mutation { + {{ mutations | join: newline }} + } + {% endaction %} + {% endif %} +{% endif %} diff --git a/tasks/auto-create-collections-by-metafield-values.json b/tasks/auto-create-collections-by-metafield-values.json new file mode 100644 index 00000000..12fb356b --- /dev/null +++ b/tasks/auto-create-collections-by-metafield-values.json @@ -0,0 +1,61 @@ +{ + "docs": "Running on a schedule or manually, this task will create automated collections based on the validation values of the configured product metafield definitions. Additionally, configuring one or more exact sales channel names will enable publishing of any newly created collections by this task to those sales channels.\n\nTo configure the 'Product metafields and collection title prefixes' field, use the left-hand side to enter metafield definition identifiers in the format of *namespace.key* (e.g. \"custom.product_colors\"), and the right-hand side to enter the optional title prefix (e.g. \"Color: \") for any collections created for that metafield's validation values.\n\nUpon creation by this task, an automated collection will have a single condition set which auto-includes any products that use the paired metafield definition and value. All other aspects of the collection (e.g. title, handle, sort order, additional conditions, sales channels, etc.) can be manually changed after creation if needed.\n\nTo work with this task, product metafields will need to be [activated](https://help.shopify.com/en/manual/custom-data/metafields/automated-collections#activating-automated-collections) as a collection condition.\n\nNotes:\n- This task only supports metafields of type \"Single line text\" or \"Single line text (List)\".\n- Before creating a new collection, this task will verify that there are no existing collections that use that specific metafield value as a product metafield condition.\n- This task will log any collections found that use an outdated value from a metafield definition.", + "halt_action_run_sequence_on_error": false, + "name": "Auto create collections by metafield values", + "online_store_javascript": null, + "options": { + "product_metafields_and_collection_title_prefixes__keyval_required": { + "custom.product_color": "Color: " + }, + "names_of_sales_channels_to_publish_collections_to__array": [ + "Online Store" + ], + "run_daily__boolean": null, + "run_hourly__boolean": null, + "test_mode__boolean": true + }, + "order_status_javascript": null, + "perform_action_runs_in_sequence": false, + "preview_event_definitions": [ + { + "description": "New collection created", + "event_attributes": { + "data": { + "meta": { + "publication_ids": [ + "gid://shopify/Publication/1234567890" + ] + }, + "run": { + "ok": true, + "result": { + "data": { + "collectionCreate": { + "collection": { + "handle": "color-blue", + "id": "gid://shopify/Collection/1234567890", + "title": "Color: Blue" + } + } + } + } + }, + "type": "shopify" + }, + "topic": "mechanic/actions/perform" + } + } + ], + "script": "{% assign product_metafields_and_collection_title_prefixes = options.product_metafields_and_collection_title_prefixes__keyval_required %}\n{% assign sales_channel_names = options.names_of_sales_channels_to_publish_collections_to__array %}\n{% assign run_daily = options.run_daily__boolean %}\n{% assign run_hourly = options.run_hourly__boolean %}\n{% assign test_mode = options.test_mode__boolean %}\n\n{% if event.preview %}\n {% assign product_metafields_and_collection_title_prefixes = hash %}\n {% assign product_metafields_and_collection_title_prefixes[\"custom.product_colors\"] = \"Color: \" %}\n{% endif %}\n\n{% assign product_metafields = product_metafields_and_collection_title_prefixes | keys %}\n\n{% if event.topic == \"mechanic/user/trigger\" or event.topic contains \"mechanic/scheduler/\" %}\n {% if sales_channel_names != blank %}\n {% comment %}\n -- check if the configured sales channels exist in this shop by name; save the publication IDs for lookup later\n {% endcomment %}\n\n {% assign publication_ids = array %}\n\n {% capture query %}\n query {\n publications(first: 250) {\n nodes {\n id\n name\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"publications\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Publication/1234567890\",\n \"name\": {{ sales_channel_names[0] | json }}\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign publications = result.data.publications.nodes %}\n {% assign publication_names = publications | map: \"name\" | sort %}\n {% assign publications_indexed_by_name = publications | index_by: \"name\" %}\n\n {% for sales_channel_name in sales_channel_names %}\n {% if publication_names contains sales_channel_name %}\n {% assign publication_ids = publication_ids | push: publications_indexed_by_name[sales_channel_name].id %}\n\n {% else %}\n {% unless event.preview %}\n {% comment %}\n -- using action error here so the task will continue with any other configured and matched sales channels\n {% endcomment %}\n\n {% action \"echo\"\n __error: \"A configured sales channel name does not match any of the publication names available in this shop.\",\n sales_channel_name: sales_channel_name,\n publication_names: publication_names\n %}\n {% endunless %}\n {% endif %}\n {% endfor %}\n {% endif %}\n\n {% comment %}\n -- loop through all configured metafields to get their metafield definition and validation values to be used as collection conditions\n {% endcomment %}\n\n {% assign metafield_definitions = array %}\n\n {% for product_metafield in product_metafields %}\n {% capture query %}\n query {\n metafieldDefinitions(\n first: 1\n ownerType: PRODUCT\n namespace: {{ product_metafield | split: \".\" | first | json }}\n key: {{ product_metafield | split: \".\" | last | json }}\n ) {\n nodes {\n id\n name\n namespace\n key\n ownerType\n type {\n name\n }\n useAsCollectionCondition\n validations {\n value\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 \"metafieldDefinitions\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/MetafieldDefinition/1234567890\",\n \"name\": \"Product Colors\",\n \"namespace\": \"custom\",\n \"key\": \"product_colors\",\n \"ownerType\": \"PRODUCT\",\n \"type\": {\n \"name\": \"list.single_line_text_field\"\n },\n \"useAsCollectionCondition\": true,\n \"validations\": [\n {\n \"value\": \"[\\\"Blue\\\",\\\"Red\\\"]\"\n }\n ]\n }\n ]\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign metafield_definition = result.data.metafieldDefinitions.nodes.first %}\n {% assign metafield_values\n = metafield_definition.validations.first.value\n | default: \"[]\"\n | parse_json\n %}\n\n {% comment %}\n -- validate that the metafield definition exists and is configured correctly\n -- using action errors for validation so the task will continue running and check all configured metafields\n {% endcomment %}\n\n {% if metafield_definition == blank %}\n {% action \"echo\"\n __error: \"Metafield definition not found for this configured metafield and owner type (PRODUCT).\",\n product_metafield: product_metafield\n %}\n {% continue %}\n {% endif %}\n\n {% unless metafield_definition.type.name contains \"single_line_text_field\" %}\n {% action \"echo\"\n __error: \"Metafield definition for this configured metafield must be a type of 'Single line text' or 'Single line text (List)'.\",\n metafield_definition: metafield_definition,\n product_metafield: product_metafield\n %}\n {% continue %}\n {% endunless %}\n\n {% unless metafield_definition.useAsCollectionCondition %}\n {% action \"echo\"\n __error: \"Metafield definition for this configured metafield must be set for use by 'Automated collections'.\",\n metafield_definition: metafield_definition,\n product_metafield: product_metafield\n %}\n {% continue %}\n {% endunless %}\n\n {% if metafield_values == blank %}\n {% action \"echo\"\n __error: \"Metafield definition validation values not found for this configured metafield.\",\n metafield_definition: metafield_definition,\n product_metafield: product_metafield\n %}\n {% continue %}\n {% endif %}\n\n {% comment %}\n -- all validation checks passed; extend the metafield definition object to store collection data\n {% endcomment %}\n\n {% assign metafield_definition[\"metafield_values\"] = metafield_values %}\n {% assign metafield_definition[\"collection_title_prefix\"] = product_metafields_and_collection_title_prefixes[product_metafield] %}\n {% assign metafield_definition[\"collections_by_value\"] = hash %}\n\n {% for metafield_value in metafield_values %}\n {% assign metafield_definition[\"collections_by_value\"][metafield_value] = array %}\n {% endfor %}\n\n {% assign metafield_definitions = metafield_definitions | push: metafield_definition %}\n {% endfor %}\n\n {% if metafield_definitions == blank %}\n {% log \"None of the configured metafields meet all of the necessary conditions for usage by this task.\" %}\n {% break %}\n {% endif %}\n\n {% comment %}\n -- query automated collections; save collection details for any that use a metafield configured in this task as a condition\n {% endcomment %}\n\n {% assign cursor = nil %}\n\n {% for n in (1..100) %}\n {% capture query %}\n query {\n collections(\n first: 250\n after: {{ cursor | json }}\n query: \"collection_type:smart\"\n ) {\n pageInfo {\n hasNextPage\n endCursor\n }\n nodes {\n id\n title\n handle\n templateSuffix\n sortOrder\n ruleSet {\n appliedDisjunctively\n rules {\n column\n relation\n condition\n conditionObject {\n __typename\n ... on CollectionRuleMetafieldCondition {\n metafieldDefinition {\n id\n }\n }\n }\n }\n }\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 \"collections\": {\n \"nodes\": [\n {\n \"id\": \"gid://shopify/Collection/1234567890\",\n \"ruleSet\": {\n \"rules\": [\n {\n \"column\": \"PRODUCT_METAFIELD_DEFINITION\",\n \"relation\": \"EQUALS\",\n \"condition\": \"Blue\",\n \"conditionObject\": {\n \"metafieldDefinition\": {\n \"id\": \"gid://shopify/MetafieldDefinition/1234567890\"\n }\n }\n }\n ]\n }\n },\n {\n \"id\": \"gid://shopify/Collection/2345678901\",\n \"ruleSet\": {\n \"rules\": [\n {\n \"column\": \"PRODUCT_METAFIELD_DEFINITION\",\n \"relation\": \"EQUALS\",\n \"condition\": \"Grey\",\n \"conditionObject\": {\n \"metafieldDefinition\": {\n \"id\": \"gid://shopify/MetafieldDefinition/1234567890\"\n }\n }\n }\n ]\n }\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 collection.ruleSet == blank %}\n {% continue %}\n {% endif %}\n\n {% for rule in collection.ruleSet.rules %}\n {% unless rule.column == \"PRODUCT_METAFIELD_DEFINITION\" and rule.relation == \"EQUALS\" %}\n {% continue %}\n {% endunless %}\n\n {% assign matched_metafield_definition\n = metafield_definitions\n | where: \"id\", rule.conditionObject.metafieldDefinition.id\n | first\n %}\n\n {% unless matched_metafield_definition %}\n {% continue %}\n {% endunless %}\n\n {% unless matched_metafield_definition.metafield_values contains rule.condition %}\n {% unless event.preview %}\n {% log\n message: \"Found collection using outdated metafield value condition for the matched metafield definition.\",\n collection: collection,\n matched_metafield_definition: matched_metafield_definition\n %}\n {% endunless %}\n\n {% continue %}\n {% endunless %}\n\n {% assign matched_metafield_definition[\"collections_by_value\"][rule.condition]\n = matched_metafield_definition[\"collections_by_value\"][rule.condition]\n | push: collection.id\n %}\n {% endfor %}\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 {% unless event.preview %}\n {% log metafield_definitions_with_extended_data: metafield_definitions %}\n {% endunless %}\n\n {% comment %}\n -- check all extended metafield definition objects to see if any new collections need to be made on this task run\n {% endcomment %}\n\n {% assign collections_to_create = array %}\n\n {% for metafield_definition in metafield_definitions %}\n {% for metafield_value in metafield_definition.metafield_values %}\n {% if metafield_definition.collections_by_value[metafield_value] == blank %}\n {% assign product_metafield = metafield_definition.namespace | append: \".\" | append: metafield_definition.key %}\n\n {% assign collection_to_create = hash %}\n {% assign collection_to_create[\"product_metafield\"] = product_metafield %}\n {% assign collection_to_create[\"collection_title\"] = metafield_value | prepend: metafield_definition.collection_title_prefix %}\n {% assign collection_to_create[\"metafield_value\"] = metafield_value %}\n {% assign collection_to_create[\"metafield_definition_id\"] = metafield_definition.id %}\n {% assign collections_to_create = collections_to_create | push: collection_to_create %}\n {% endif %}\n {% endfor %}\n {% endfor %}\n\n {% unless event.preview %}\n {% log collections_to_create: collections_to_create %}\n {% endunless %}\n\n {% if test_mode %}\n {% log \"This task is set for 'Test mode' and no new collections will be created.\" %}\n {% break %}\n {% endif %}\n\n {% for collection_to_create in collections_to_create %}\n {% capture mutation %}\n mutation {\n collectionCreate(\n input: {\n title: {{ collection_to_create.collection_title | json }}\n ruleSet: {\n appliedDisjunctively: false\n rules: [\n {\n column: PRODUCT_METAFIELD_DEFINITION\n relation: EQUALS\n condition: {{ collection_to_create.metafield_value | json }}\n conditionObjectId: {{ collection_to_create.metafield_definition_id | json }}\n }\n ]\n }\n }\n ) {\n collection {\n id\n title\n handle\n }\n userErrors {\n field\n message\n }\n }\n }\n {% endcapture %}\n\n {% action %}\n {\n \"type\": \"shopify\",\n \"options\": {{ mutation | json }},\n \"meta\": {\n \"publication_ids\": {{ publication_ids | json }}\n }\n }\n {% endaction %}\n {% endfor %}\n\n{% elsif event.topic == \"mechanic/actions/perform\" %}\n {% comment %}\n -- only respond to successful creation of collections\n {% endcomment %}\n\n {% unless action.type == \"shopify\" and action.run.ok and action.run.result.data.collectionCreate %}\n {% break %}\n {% endunless %}\n\n {% assign collection_id = action.run.result.data.collectionCreate.collection.id %}\n {% assign publication_ids = action.meta.publication_ids %}\n\n {% comment %}\n -- publish the new collection to all of the valid publications configured in the task\n {% endcomment %}\n\n {% assign mutations = array %}\n\n {% for publication_id in publication_ids %}\n {% capture mutation %}\n publishablePublish{{ forloop.index }}: publishablePublish(\n id: {{ collection_id | json }}\n input: {\n publicationId: {{ publication_id | json }}\n }\n ) {\n publishable {\n ... on Collection {\n id\n title\n handle\n }\n }\n userErrors {\n field\n message\n }\n }\n {% endcapture %}\n\n {% assign mutations = mutations | push: mutation %}\n {% endfor %}\n\n {% if mutations != blank %}\n {% action \"shopify\" %}\n mutation {\n {{ mutations | join: newline }}\n }\n {% endaction %}\n {% endif %}\n{% endif %}\n", + "subscriptions": [ + "mechanic/user/trigger", + "mechanic/actions/perform" + ], + "subscriptions_template": "{% if options.run_hourly__boolean %}\n mechanic/scheduler/hourly\n{% elsif options.run_daily__boolean %}\n mechanic/scheduler/daily\n{% endif %}\nmechanic/user/trigger \nmechanic/actions/perform", + "tags": [ + "Collections", + "Metafields", + "Products", + "Publish" + ] +}