Example actions - ION Manual

Documentation Index

Fetch the complete documentation index at: /llms.txt

Use this file to discover all available pages before exploring further.

Read Create an action before working through these examples. Each one below gives the field values for one ION Action: its title, target, and event, followed by the context query it reads and the code it runs.

Block a status transition based on a related collection

Stops an issue from moving to In Review while any of its redlines sit on a step that isn’t complete or canceled. The technique: read a related collection from the context and require every record in it to be in an acceptable state.

Field Value
Title Open redlines prevent moving issue into In Review
Target ISSUE
Event UPDATE

Context

{
  issue(id: $id) {
    title
    status
    redlines { id step { status } }
  }
}

Code

if (
    context.get('issue', {}).get('status') == 'IN_REVIEW'
    and context.get('issue', {}).get('redlines') is not None
    and not all(
        redline.get('step', {}).get('status') in ['COMPLETE', 'CANCELED']
        for redline in context.get('issue', {}).get('redlines')
    )
):
    raise ValidationError()

Restrict an action to specific roles

Blocks moving an issue back to In Review from a resolved state unless the acting user holds a quality role. The technique: branch on the acting user’s roles from currentUser, and read the prior value from changes to detect the reopen.

Field Value
Title No permission to reopen a closed issue
Target ISSUE
Event UPDATE

Context

{
  issue(id: $id) {
    title
    status
  }
}

Code

allowed_roles = {'Quality Engineer', 'Quality Inspector', 'Reliability Engineer', 'admin'}
if (
    context.get('issue', {}).get('status') == 'IN_REVIEW'
    and context.get('changes', {}).get('issues', {}).get('status', {}).get('old') == 'resolved'
    and not set(context.get('currentUser', {}).get('roles', [])).intersection(allowed_roles)
):
    raise ValidationError()

Check a custom attribute

Stops a part from being added to a kit when its On CAPA? attribute is set. The technique: scan a list of { key, value } attributes on a related object for a specific key.

Field Value
Title Cannot add CAPA part to kit
Target PARTKITITEM
Event CREATE

Context

{
  partKitItem(id: $id) {
    part {
      attributes { key value }
    }
  }
}

Code

if any(
    attr.get('key') == 'On CAPA?' and attr.get('value') is not None
    for attr in context.get('partKitItem', {}).get('part', {}).get('attributes', [])
):
    raise ValidationError()

Require a field on every item in a collection

Blocks moving a procedure to In Review when any step has no upstream or downstream dependency. The technique: gate on a status change read from changes, then iterate the object’s child collection and require a field on each.

Field Value
Title Check if step has dependencies
Target PROCEDURE
Event UPDATE

Context

{
  procedure(id: $id) {
    id
    steps {
      upstreamStepIds
      downstreamStepIds
    }
  }
}

Code

status_change = context.get('changes', {}).get('procedures', {}).get('status', {})
if status_change.get('new') == 'in_review' and any(
    not (step.get('upstreamStepIds') or step.get('downstreamStepIds'))
    for step in context.get('procedure', {}).get('steps', [])
):
    raise ValidationError()

Match records across a nested collection

For a Build procedure, blocks the move to In Review until reviewers from the Responsible Engineer, Production, and Mission Assurance teams have been added. The technique: flatten a nested collection (reviewers, then their teams) and confirm every required value is present.

Field Value
Title Procedure requires the correct teams to review
Target PROCEDURE
Event UPDATE

Context

{
  procedure(id: $id) {
    attributes { key value }
    reviewRequests { id status reviewer { id teams { name } roles { name } } }
  }
}

Code

status_change = context.get('changes', {}).get('procedures', {}).get('status', {})
procedure = context.get('procedure', {})
is_build = any(
    attr.get('key') == 'Procedure Type' and attr.get('value') == 'Build'
    for attr in procedure.get('attributes', [])
)
required_teams = ['Responsible Engineer', 'Production', 'Mission Assurance']
reviewer_team_names = [\
    team.get('name', '')\\
    for review in procedure.get('reviewRequests', [])\
    for team in review.get('reviewer', {}).get('teams', [])\
]
has_required = all(
    any(required in name for name in reviewer_team_names)
    for required in required_teams
)
if status_change.get('new') == 'in_review' and is_build and not has_required:
    raise ValidationError()

Require a field before a status change

Blocks a purchase order line from moving to Requested when it has no need date. The technique: gate on a status change read from changes, then require a scalar field on the object.

Field Value
Title Purchase line items must have a need date
Target PURCHASEORDERLINE
Event UPDATE

Context

{
  purchaseOrderLine(id: $id) {
    id
    needDate
  }
}

Code

status_change = context.get('changes', {}).get('purchaseOrderLines', {}).get('status', {})
if (
    status_change.get('new') == 'requested'
    and context.get('purchaseOrderLine', {}).get('needDate') is None
):
    raise ValidationError()

To enforce purchase order approval levels, use the built-in purchase order approvals instead of an ION Action. It’s easier to configure and maintain.

Require multiple fields on creation

Blocks creating a part of type Part until the required fields are filled in. The technique: run on the CREATE event, guard on the object’s type, then check a list of required fields at once.

Field Value
Title Required fields for part creation
Target PART
Event CREATE

Context

{
  part(id: $id) {
    id
    revision
    description
    trackingType
    sourcingStrategy
    partType
    unitOfMeasure { id }
  }
}

Code

part = context.get('part', {})
required_fields = ['revision', 'description', 'trackingType', 'sourcingStrategy', 'unitOfMeasure']
if part.get('partType') == 'PART' and any(not part.get(field) for field in required_fields):
    raise ValidationError()