Skip to content

Commit

Permalink
second batch of customer filter task fixes (#396)
Browse files Browse the repository at this point in the history
  • Loading branch information
tekhaus authored Sep 14, 2024
1 parent cb876ad commit 43afc20
Show file tree
Hide file tree
Showing 16 changed files with 568 additions and 524 deletions.
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ This directory is built automatically. Each task's documentation is generated fr

### Account

* [Auto-tag customers with who have accounts](./auto-tag-customers-with-who-have-accounts)
* [Auto-verify customer email addresses](./auto-verify-customer-email-addresses)
* [Send account invites to all customers in bulk](./send-account-invites-to-all-customers-in-bulk)

Expand Down
6 changes: 3 additions & 3 deletions docs/auto-tag-customers-by-email-domain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Tags: Auto-Tag, Customers

Useful in a million scenarios. The merchant who requested this task has discounts set up based on customer tag, and they're using this task to guarantee that qualifying customers see their discount immediately – even if they just signed up!
This task runs when customers are created, and it applies a tag if they have one of the configured email domains.

* View in the task library: [tasks.mechanic.dev/auto-tag-customers-by-email-domain](https://tasks.mechanic.dev/auto-tag-customers-by-email-domain)
* Task JSON, for direct import: [task.json](../../tasks/auto-tag-customers-by-email-domain.json)
Expand Down Expand Up @@ -32,9 +32,9 @@ mechanic/user/trigger

## Documentation

Useful in a million scenarios. The merchant who requested this task has discounts set up based on customer tag, and they're using this task to guarantee that qualifying customers see their discount immediately – even if they just signed up!
This task runs when customers are created, and it applies a tag if they have one of the configured email domains.

This task runs when customers are created. Use the "Run task" button to scan all customers already registered.
Use the "Run task" button to scan all customers already registered.

## Installing this task

Expand Down
103 changes: 78 additions & 25 deletions docs/auto-tag-customers-by-email-domain/script.liquid
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{% for email_domain in options.customer_email_domains__required_array %}
{% assign customer_email_domains = options.customer_email_domains__required_array %}
{% assign customer_tag_to_apply = options.customer_tag_to_apply__required %}

{% for email_domain in customer_email_domains %}
{% if email_domain contains "@" %}
{% error "Do not include '@' symbols in email domains. Thanks!" %}
{% endif %}
Expand All @@ -10,41 +13,56 @@
{% if event.preview %}
{% assign customer = hash %}
{% assign customer["admin_graphql_api_id"] = "gid://shopify/Customer/1234567890" %}
{% assign customer["email"] = "test@" | append: options.customer_email_domains__required_array.first %}
{% assign customer["email"] = "test@" | append: customer_email_domains.first %}
{% endif %}

{% assign customer_email_domain = customer.email | split: "@" | last | downcase %}
{% assign customer_tags = customer.tags | split: ", " %}

{% if options.customer_email_domains__required_array contains customer_email_domain %}
{% unless customer_tags contains options.customer_tag_to_apply__required %}
{% if customer_email_domains contains customer_email_domain %}
{% unless customer_tags contains customer_tag_to_apply %}
{% assign customer_ids_to_tag[customer_ids_to_tag.size] = customer.admin_graphql_api_id %}
{% endunless %}
{% endif %}

{% elsif event.topic == "mechanic/user/trigger" %}
{% capture customers_query -%}
-tag:{{ options.customer_tag_to_apply__required | json }} AND ({{ options.customer_email_domains__required_array | join: " OR email:" | prepend: "email:" }})
{% assign domain_query_parts = array %}

{% for email_domain in customer_email_domains %}
{% assign domain_query_parts[domain_query_parts.size]
= "customer_email_domain = '"
| append: email_domain
| append: "'"
%}
{% endfor %}

{% capture search_query -%}
customer_tags NOT CONTAINS '{{ customer_tag_to_apply }}' AND ({{ domain_query_parts | join: " OR " }})
{%- endcapture %}

{% log search_query: search_query %}

{% assign cursor = nil %}

{% comment %}
-- customers resource no longer supports tag filters (JUL 2024), so need to use the customerSegmentMembers resource instead
{% endcomment %}

{% for n in (0..100) %}
{% capture query %}
query {
customers(
first: 250
query: {{ customers_query | json }}
customerSegmentMembers(
first: 1000
after: {{ cursor | json }}
query: {{ search_query | json }}
) {
pageInfo {
hasNextPage
endCursor
}
edges {
cursor
node {
id
email
tags
}
}
}
Expand All @@ -57,13 +75,11 @@
{% capture result_json %}
{
"data": {
"customers": {
"customerSegmentMembers": {
"edges": [
{
"node": {
"id": "gid://shopify/Customer/1234567890",
"email": {{ "test@" | append: options.customer_email_domains__required_array.first | json }},
"tags": []
"id": "gid://shopify/CustomerSegmentMember/1234567890"
}
}
]
Expand All @@ -75,31 +91,68 @@
{% assign result = result_json | parse_json %}
{% endif %}

{% for customer_edge in result.data.customers.edges %}
{% assign customer = customer_edge.node %}
{% assign customer_segment_member_ids
= result.data.customerSegmentMembers.edges
| map: "node"
| map: "id"
%}

{% for customer_segment_member_id in customer_segment_member_ids %}
{% comment %}
-- query the customer record in order to verify the domain and absence of the tag, in case the search query filter has "seepage"
{% endcomment %}

{% capture query %}
query {
customer(id: {{ customer_segment_member_id | remove: "SegmentMember" | json }}) {
id
email
tags
}
}
{% endcapture %}

{% assign result = query | shopify %}

{% if event.preview %}
{% capture result_json %}
{
"data": {
"customer": {
"id": "gid://shopify/Customer/1234567890",
"email": {{ "test@" | append: customer_email_domains.first | json }}
}
}
}
{% endcapture %}

{% assign result = result_json | parse_json %}
{% endif %}

{% assign customer = result.data.customer %}
{% assign customer_email_domain = customer.email | split: "@" | last | downcase %}

{% if options.customer_email_domains__required_array contains customer_email_domain %}
{% unless customer.tags contains options.customer_tag_to_apply__required %}
{% assign customer_ids_to_tag[customer_ids_to_tag.size] = customer.id %}
{% if customer_email_domains contains customer_email_domain %}
{% unless customer.tags contains customer_tag_to_apply %}
{% assign customer_ids_to_tag = customer_ids_to_tag | push: customer.id %}
{% endunless %}
{% endif %}
{% endfor %}

{% if result.data.customers.pageInfo.hasNextPage %}
{% assign cursor = result.data.customers.edges.last.cursor %}
{% assign cursor = result.data.customers.pageInfo.endCursor %}
{% else %}
{% break %}
{% endif %}
{% endfor %}
{% endif %}

{% for id in customer_ids_to_tag %}
{% for customer_id in customer_ids_to_tag %}
{% action "shopify" %}
mutation {
tagsAdd(
id: {{ id | json }}
tags: {{ options.customer_tag_to_apply__required | json }}
id: {{ customer_id | json }}
tags: {{ customer_tag_to_apply | json }}
) {
userErrors {
field
Expand Down
15 changes: 4 additions & 11 deletions docs/auto-tag-customers-by-purchased-skus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Tags: Auto-Tag, Customers, SKU

Use this task to keep customers tagged with the SKUs of the products they've purchased, optionally ignoring product purchases that have been refunded. Add a tag prefix to make SKU tags easy to distinguish from your other customer tags. This task also stores all purchased SKUs in a customer metafield, dodging the 250 tag limit that exists for customers.
This task keeps customers tagged with the SKUs of the products they've purchased, optionally ignoring product purchases that have been refunded. Add a tag prefix to make SKU tags easy to distinguish from your other customer tags.

* View in the task library: [tasks.mechanic.dev/auto-tag-customers-by-purchased-skus](https://tasks.mechanic.dev/auto-tag-customers-by-purchased-skus)
* Task JSON, for direct import: [task.json](../../tasks/auto-tag-customers-by-purchased-skus.json)
Expand All @@ -15,7 +15,6 @@ Use this task to keep customers tagged with the SKUs of the products they've pur
"tag_prefix": "",
"ignore_refunded_purchases__boolean": true,
"enable_running_manually__boolean": null,
"customer_query_when_running_manually": null,
"test_mode__boolean": true
}
```
Expand All @@ -26,25 +25,19 @@ Use this task to keep customers tagged with the SKUs of the products they've pur

```liquid
shopify/orders/paid
{% if options.ignore_refunded_purchases__boolean %}
shopify/refunds/create
{% endif %}
{% if options.enable_running_manually__boolean %}
mechanic/user/trigger
mechanic/shopify/bulk_operation
{% endif %}
mechanic/user/trigger
mechanic/shopify/bulk_operation
```

[Learn about event subscriptions in Mechanic](https://learn.mechanic.dev/core/tasks/subscriptions)

## Documentation

Use this task to keep customers tagged with the SKUs of the products they've purchased, optionally ignoring product purchases that have been refunded. Add a tag prefix to make SKU tags easy to distinguish from your other customer tags. This task also stores all purchased SKUs in a customer metafield, dodging the 250 tag limit that exists for customers.
This task keeps customers tagged with the SKUs of the products they've purchased, optionally ignoring product purchases that have been refunded. Add a tag prefix to make SKU tags easy to distinguish from your other customer tags.

This task keeps customers tagged with the SKUs of the products they've purchased, optionally ignoring product purchases that have been refunded. Add a tag prefix to make SKU tags easy to distinguish from your other customer tags.

This task also records all purchased SKUs in a JSON metafield called "mechanic.purchased_skus", associated with the customer. For customers who have purchased more products than would fit inside Shopify's maximum tag limit of 250, this metafield provides a way for you to access _all_ purchased SKUs.

## Installing this task
Expand Down
36 changes: 20 additions & 16 deletions docs/auto-tag-customers-by-purchased-skus/script.liquid
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
{% comment %}
An opinion about option order:
{{ options.tag_prefix }}
{{ options.ignore_refunded_purchases__boolean }}
{{ options.enable_running_manually__boolean }}
{{ options.customer_query_when_running_manually }}
{{ options.test_mode__boolean }}
{% endcomment %}
{% assign tag_prefix = options.tag_prefix %}
{% assign ignore_refunded_purchases = options.ignore_refunded_purchases__boolean %}
{% assign test_mode = options.test_mode__boolean %}

{% assign metafield_namespace = "mechanic" %}
{% assign metafield_key = "purchased_skus" %}
Expand All @@ -17,7 +11,7 @@
{% if event.topic == "mechanic/user/trigger" %}
{% capture bulk_operation_query %}
query {
customers(query: {{ options.customer_query_when_running_manually | json }}) {
customers {
edges {
node {
__typename
Expand Down Expand Up @@ -72,6 +66,7 @@
}
}
{% endaction %}

{% elsif event.topic == "mechanic/shopify/bulk_operation" %}
{% if event.preview %}
{% comment %}
Expand Down Expand Up @@ -175,8 +170,9 @@
{% endif %}

{% assign quantities_by_sku_and_customer_id[customer_id][sku] = quantities_by_sku_and_customer_id[customer_id][sku] | default: 0 | plus: quantity %}

{% when "Order" %}
{% unless options.ignore_refunded_purchases__boolean %}
{% unless ignore_refunded_purchases %}
{% continue %}
{% endunless %}

Expand All @@ -191,6 +187,7 @@
{% endif %}

{% assign refunds = shop.orders[object.legacyResourceId].refunds %}

{% for refund in refunds %}
{% for refund_line_item in refund.refund_line_items %}
{% assign sku = refund_line_item.line_item.sku %}
Expand Down Expand Up @@ -226,14 +223,15 @@

{% for quantities_by_sku in quantities_by_sku_and_customer_id[job.customer_id] %}
{% assign sku = quantities_by_sku[0] %}
{% assign sku_tag = options.tag_prefix | append: sku %}
{% assign sku_tag = tag_prefix | append: sku %}
{% assign quantity = quantities_by_sku[1] %}

{% if quantity > 0 %}
{% unless object.tags contains sku_tag %}
{% assign job["tags_to_add"][job.tags_to_add.size] = sku_tag %}
{% endunless %}
{% assign job["skus_for_metafield"][job.skus_for_metafield.size] = sku_tag %}

{% else %}
{% if object.tags contains sku_tag %}
{% assign job["tags_to_remove"][job.tags_to_remove.size] = sku_tag %}
Expand All @@ -247,6 +245,7 @@
{% endcase %}
{% endfor %}
{% endif %}

{% elsif event.topic contains "shopify/" %}
{% if event.topic contains "shopify/refunds/" %}
{% assign customer = refund.order.customer %}
Expand Down Expand Up @@ -392,7 +391,7 @@
{% assign sku_quantities[sku] = sku_quantities[sku] | default: 0 | plus: lineItem_edge.node.quantity %}
{% endfor %}

{% if options.ignore_refunded_purchases__boolean %}
{% if ignore_refunded_purchases %}
{% for refund in order_edge.node.refunds %}
{% for refundLineItem_edge in refund.refundLineItems.edges %}
{% assign sku = refundLineItem_edge.node.lineItem.sku %}
Expand Down Expand Up @@ -429,14 +428,15 @@

{% for sku_and_quantity in sku_quantities %}
{% assign sku = sku_and_quantity[0] %}
{% assign sku_tag = options.tag_prefix | append: sku %}
{% assign sku_tag = tag_prefix | append: sku %}
{% assign quantity = sku_and_quantity[1] %}

{% if quantity > 0 %}
{% unless customer_tags contains sku_tag %}
{% assign job["tags_to_add"][job.tags_to_add.size] = sku_tag %}
{% endunless %}
{% assign job["skus_for_metafield"][job.skus_for_metafield.size] = sku_tag %}

{% else %}
{% if customer_tags contains sku_tag %}
{% assign job["tags_to_remove"][job.tags_to_remove.size] = sku_tag %}
Expand All @@ -450,8 +450,8 @@
{% endif %}

{% for job in jobs %}
{% if options.test_mode__boolean %}
{% action "echo" job %}
{% if test_mode %}
{% log job %}
{% continue %}
{% endif %}

Expand Down Expand Up @@ -486,8 +486,10 @@
{% endif %}

{% assign tags_net_count = job.customer_tags.size | plus: job.tags_to_add.size | minus: job.tags_to_remove.size %}

{% if tags_net_count > 250 %}
{% assign tag_count_to_drop = tags_net_count | minus: 250 %}

{% for i in (1..tag_count_to_drop) %}
{% assign jobs[forloop.parentloop.index0]["tags_to_add"] = job.tags_to_add | remove_tag: job.tags_to_add.last %}
{% endfor %}
Expand All @@ -509,6 +511,7 @@
}
}
{% endcapture %}

{% assign mutations[mutations.size] = mutation %}
{% endif %}

Expand All @@ -524,6 +527,7 @@
}
}
{% endcapture %}

{% assign mutations[mutations.size] = mutation %}
{% endif %}

Expand Down
Loading

0 comments on commit 43afc20

Please sign in to comment.