ion actions best practices.md

ION Actions Best Practices

Overview

The Ion Actions engine enables powerful automation and business logic within ION by allowing users to define rules that respond to object events such as Work Order updates, Issue creation, or Part changes. Rules are written in Python and executed dynamically based on event triggers.

This document outlines best practices for writing, testing, and managing Ion Action rules. Following these guidelines will help ensure reliable behavior, maintainability, and consistency across your automation logic.


1. Rule Execution and Behavior

Chained Execution

When multiple rules target the same object and event type (for example, Issues Update), they are chained together and executed sequentially. The execution order is determined by internal rule IDs, which are not user-controlled and may vary. Because of this, rule order should not be relied upon.

Avoid Top-Level return Statements

If a return statement is used at the top level of a rule, the Ion Actions engine will stop executing that rule and all subsequent rules in the chain. This can lead to unintended skipping of logic defined in other rules that share the same event target.

Example (Problematic):

if issue.status == "Closed":
    return  # ❌ This stops execution of all following rules for this event

Use Nested Returns Instead

To safely control flow within your rule without affecting other chained rules, scope your return statements within functions or conditionals. This allows the Ion engine to continue executing subsequent rules.

Example (Recommended):

if issue.status == "Closed":
    def handle_closed_issue():
        # Logic specific to closed issues
        return "Handled closed issue"  # ✅ Nested return affects only this function

handle_closed_issue()

Safely Accessing Context Values

When writing rules, you’ll often chain lookups like:

context.get('part', {}).get('attributes', {}).get('serialNumber')

This can easily break if any key in the chain exists but has a value of None. For example, if context['part'] is None, the call above will return None from the first .get() — causing the next .get('attributes', {}) to throw a TypeError.

The example below shows this:

## Simplified example context dictionary
context = {"part": None}

# ------------------------ #

## Example Bad ION Action Context lookup
serial_number = context.get('part', {}).get('attributes', {}).get('serialNumber') ## 
# 🛑 Raises: TypeError: 'NoneType' object has no attribute 'get'

Recommended Pattern

Instead, use the following safer pattern:

part = context.get('part') or {}
attributes = part.get('attributes') or {}
serial = attributes.get('serialNumber')

This approach safely handles allows you to chain context lookups when the key is missing or the key exists but is None. This pattern ensures your ION Action is more resilient when chaining context lookups.


Additional Recommendations


2. Rule Development and Deployment

Version Control and Collaboration

Deployment Process

Governance and Documentation


3. Action Context Filters

Filters are allowed in context queries as long as the object supports filtering. Here is an example of a context query that filters for the Department attribute on a purchase order:

query ($id: ID!) {
  purchaseOrder(id: $id) {
    id
    status
    Attributes(filters: {key: {eq: "Department"}}) {
      value
    }
  }
}

This would work fine as long as there are no other actions on the same resource/action (purchase order/update) that also has a different filter on Attributes for a purchase order. If you create a new action with this context query:

query ($id: ID!) {
  purchaseOrder(id: $id) {
    id
    status
    Attributes(filters: {key: {eq: "Quality Level"}}) {
      value
    }
  }
}

An error will be thrown and the actions would not be able to function together. ION Actions merges the context queries for all actions that have the same resource/action so that a single query can be executed.

Solution

The solution is to use an alias on any object where there is a specific filter specified. The above two context queries should be written as:

query ($id: ID!) {
  purchaseOrder(id: $id) {
    id
    status
    deptAttr:Attributes(filters: {key: {eq: "Department"}}) {
      value
    }
  }
}
query ($id: ID!) {
  purchaseOrder(id: $id) {
    id
    status
    qlAttr:Attributes(filters: {key: {eq: "Quality Level"}}) {
      value
    }
  }
}

In the code for the action be sure to reference deptAttr and qlAttr respectively instead of Attributes.