Auto-tag products that meet a sales threshold with Mechanic.

Mechanic is the one-tool-does-it-all automation app for Shopify. :)

Auto-tag products that meet a sales threshold

by Isaac Bowen (team@usemechanic.com)

Out of the box, this task scans the last week's worth of paid orders, and auto-tags products that meet the sales threshold of your choice, removing the tag from products that no longer qualify. This scan can be run on demand (with the "Run task" button), and will otherwise run nightly. You can choose between a threshold defined by line item quantity (for the total number of units sold), by line item subtotals (for the total amount of money each product earned), or by number of orders (for the total number of times a customer placed an order including that product). You can also reconfigure the query used to fetch orders, to get specific about what orders you want to consider.

Runs when a user triggers the task and every day at midnight. Configuration includes tally by line item quantity, tally by line item subtotals, tally by number of orders, minimum tally threshold per product, product tag to apply, and order query.

15-day free trial – unlimited tasks

Documentation

Out of the box, this task scans the last week's worth of paid orders, and auto-tags products that meet the sales threshold of your choice, removing the tag from products that no longer qualify. This scan can be run on demand (with the "Run task" button), and will otherwise run nightly.

You can choose between a threshold defined by line item quantity (for the total number of units sold), by line item subtotals (for the total amount of money each product earned), or by number of orders (for the total number of times a customer placed an order including that product).

Configure the "Order query" option to get specific about which orders you want to consider. When altering the "processed_at" query parameter, you may use the following values:

This task can take some time to run, depending on the number of orders that need to be scanned. :)

For the interested, see the very end of the "Task result" portion of each task run's event, for a logged dump of product IDs mapped to the tallies Mechanic has arrived at for each one. You can use this to make sure you understand why Mechanic is making the tagging decisions it's making.

Developer details

Mechanic is designed to benefit everybody: merchants, customers, developers, agencies, Gurus, everybody.

That’s why we make it easy to configure automation without code, why we make it easy to tweak the underlying code once tasks are installed, and why we publish it all here for everyone to learn from.

Events
when a user triggers the task (mechanic/user/trigger)
every day at midnight (mechanic/scheduler/daily)
Options
tally by line item quantity (boolean), tally by line item subtotals (boolean), tally by number of orders (boolean), minimum tally threshold per product (required, number), product tag to apply (required), order query (required)
Script
{% comment %}
  Preferred option order:

  {{ options.tally_by_line_item_quantity__boolean }}
  {{ options.tally_by_line_item_subtotals__boolean }}
  {{ options.tally_by_number_of_orders__boolean }}
  {{ options.minimum_tally_threshold_per_product__required_number }}
  {{ options.product_tag_to_apply__required }}
  {{ options.order_query__required }}
{% endcomment %}

{% assign product_tallies = hash %}
{% assign products_already_tagged = array %}
{% assign products_deserving_tag = array %}

{% assign mode_selections = 0 %}
{% if options.tally_by_line_item_quantity__boolean %}
  {% assign mode = "line_item_quantity" %}
  {% assign mode_selections = mode_selections | plus: 1 %}
{% endif %}
{% if options.tally_by_line_item_subtotals__boolean %}
  {% assign mode = "line_item_subtotal" %}
  {% assign mode_selections = mode_selections | plus: 1 %}
{% endif %}
{% if options.tally_by_number_of_orders__boolean %}
  {% assign mode = "order_count" %}
  {% assign mode_selections = mode_selections | plus: 1 %}
{% endif %}

{% if mode_selections == 0 %}
  {"error": "Choose one tally mode."}
{% elsif mode_selections > 1 %}
  {"error": "Choose exactly one tally mode - more than one is not supported."}
{% endif %}

{% assign cursor = nil %}
{% for n in (0..10000) %}
  {% capture query %}
    query {
      products(
        first: 250
        after: {{ cursor | json }}
        query: {{ options.product_tag_to_apply__required | json | prepend: "tag:" | json }}
      ) {
        pageInfo {
          hasNextPage
        }
        edges {
          cursor
          node {
            id
          }
        }
      }
    }
  {% endcapture %}

  {% assign result = query | shopify %}

  {% assign products_already_tagged = result.data.products.edges | map: "node" | map: "id" | concat: products_already_tagged %}

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

{% assign cursor = nil %}
{% for n in (0..10000) %}
  {% capture query %}
    query {
      orders(
        first: 10
        after: {{ cursor | json }}
        query: {{ options.order_query__required | json }}
      ) {
        pageInfo {
          hasNextPage
        }
        edges {
          cursor
          node {
            id
            createdAt
            lineItems(
              first: 30
            ) {
              edges {
                node {
                  quantity
                  originalTotalSet {
                    shopMoney {
                      amount
                    }
                  }
                  product {
                    id
                  }
                }
              }
            }
          }
        }
      }
    }
  {% endcapture %}

  {% assign result = query | shopify %}

  {% for order_edge in result.data.orders.edges %}
    {% assign order_node = order_edge.node %}
    {% assign lineItem_nodes = order_node.lineItems.edges | map: "node" %}

    {% case mode %}
    {% when "line_item_quantity" %}
      {% for lineItem_node in lineItem_nodes %}
        {% if lineItem_node.product %}
          {% assign product_tallies[lineItem_node.product.id] = product_tallies[lineItem_node.product.id] | default: 0 | plus: lineItem_node.quantity %}
        {% endif %}
      {% endfor %}
    {% when "line_item_subtotal" %}
      {% for lineItem_node in lineItem_nodes %}
        {% if lineItem_node.product %}
          {% assign product_tallies[lineItem_node.product.id] = product_tallies[lineItem_node.product.id] | default: 0 | plus: lineItem_node.originalTotalSet.shopMoney.amount %}
        {% endif %}
      {% endfor %}
    {% when "order_count" %}
      {% assign product_ids = lineItem_nodes | map: "product" | map: "id" | compact %}
      {% for product_id in product_ids %}
        {% assign product_tallies[product_id] = product_tallies[product_id] | default: 0 | plus: 1 %}
      {% endfor %}
    {% endcase %}
  {% endfor %}

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

{% if event.preview %}
  {% assign product_tallies["gid://shopify/Product/1234567890"] = options.minimum_tally_threshold_per_product__required_number %}
{% endif %}

{% for keyval in product_tallies %}
  {% assign product_id = keyval[0] %}
  {% assign tally = keyval[1] %}

  {% if tally >= options.minimum_tally_threshold_per_product__required_number %}
    {% assign products_deserving_tag[products_deserving_tag.size] = product_id %}

    {% unless products_already_tagged contains product_id %}
      {% action "shopify" %}
        mutation {
          tagsAdd(
            id: {{ product_id | json }}
            tags: {{ options.product_tag_to_apply__required | json }}
          ) {
            userErrors {
              field
              message
            }
          }
        }
      {% endaction %}
    {% endunless %}
  {% endif %}
{% endfor %}

{% for product_id in products_already_tagged %}
  {% unless products_deserving_tag contains product_id %}
    {% action "shopify" %}
      mutation {
        tagsRemove(
          id: {{ product_id | json }}
          tags: {{ options.product_tag_to_apply__required | json }}
        ) {
          userErrors {
            field
            message
          }
        }
      }
    {% endaction %}
  {% endunless %}
{% endfor %}

{"log": {{ product_tallies | json }}}
Mechanic tasks are written in Liquid, which makes them easy to write and easy to modify. Learn more about our platform.
Defaults
Tally by line item quantity
true
Order query
financial_status:paid processed_at:past_week