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()