Skip to main content
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. 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.
FieldValue
TitleOpen redlines prevent moving issue into In Review
TargetISSUE
EventUPDATE
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.
FieldValue
TitleNo permission to reopen a closed issue
TargetISSUE
EventUPDATE
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.
FieldValue
TitleCannot add CAPA part to kit
TargetPARTKITITEM
EventCREATE
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.
FieldValue
TitleCheck if step has dependencies
TargetPROCEDURE
EventUPDATE
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.
FieldValue
TitleProcedure requires the correct teams to review
TargetPROCEDURE
EventUPDATE
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.
FieldValue
TitlePurchase line items must have a need date
TargetPURCHASEORDERLINE
EventUPDATE
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.
FieldValue
TitleRequired fields for part creation
TargetPART
EventCREATE
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()