Creating an Active Action Script
Conventions
OSD_HOME- the Search Anywhere Web home directory, usually/app/opensearch-dashboards/
General Rules
An active action script defines custom logic for processing incidents and aggregations in Incident Manager.
Active action scripts can be used for both incidents and aggregations. Scripts run manually or as Primary Actions or Other Actions when an incident transitions to a new workflow state.
The differences between Primary Actions and Other Actions are described in Active Actions.
Active action scripts can be written in JavaScript (Node.js) or Python. Place the script file in $OSD_HOME/plugins/smartMonitorIncidentManager/common/actions or $OSD_HOME/config/actions.
Scripts in $OSD_HOME/config/actions are preserved when plugins are updated. Scripts in $OSD_HOME/plugins/smartMonitorIncidentManager/common/actions are deleted during an update.
Each script must have a unique name. Otherwise, the required script may be identified incorrectly.
After adding a script file, restart Search Anywhere Web to update the active action catalog.
The following sections describe the data passed to a Python script. A JavaScript script receives the same data as function arguments. For details, see Using JavaScript.
Incident Data
When a script runs for an incident, the following data is passed to the Python script. If the action is started manually, sideEffects is passed as an empty object ("sideEffects": {}). The following example shows a populated sideEffects object.
{
"doc": {}, // fields to modify in the incident or aggregation
"user": {
"name": "admin", // user name
"backend_roles": [
// user backend roles
],
"roles": [
// user roles
]
},
"incidentId": "zGQt4o8BqXcWBDa4I1DFGDH5", // incident ID
"index": ".incident-index-2023.21", // index where the incident is stored
"log": "", // incident logs (change history)
"metadata": {
"incidentId": "zGQt4o8BqXcWBDa4I1DFGDH5", // incident ID
"incidentTitle": "Exchange: anti-spam missed an email", // incident title
"incidentDescription": "Anti-spam missed an email", // incident description
"index": ".smos_incident-2024.23", // index where the incident is stored
"urlToIncident": "https://smarthost.ru/incident-manager/...", // incident URL
"baseFields": {}, // incident card fields specified in the active action settings
"additionalFields": {}, // values from Additional Fields in the active action settings
"fields": {
// additional incident fields set in the active action
},
"reviewer": {
// person responsible for the incident
}
},
"sideEffects": {
"status": {
"from": "open",
"to": "in_progress"
},
"assignedTo": {
"id": "sm_user",
"name": "John Doe"
},
"fields": {
"severity": "high",
"category": "email_security"
},
"comment": "Incident accepted for processing",
"actions": [
{
"type": "notification",
"channel": "email",
"recipients": ["admin@company.ru"],
"message": "Incident moved to in_progress"
}
]
},
"target": "manual", // manual indicates an active action or a new workflow status
"workflowId": "my-workflow", // incident workflow ID
"_source": {
// incident body
}
}
Aggregation Data
When an active action script is run manually for an aggregation, sideEffects is passed to the Python script as an empty object:
{
"sideEffects": {}
}
The complete set of data passed to the script is as follows:
{
"doc": {}, // fields to modify in the incident or aggregation
"user": {
"name": "admin", // user name
"backend_roles": [
// user backend roles
],
"roles": [
// user roles
]
},
"aggregationId": "zGQt4o8BqXcWBDa4I1DFGDH5", // aggregation ID
"index": ".incident_of_aggregation_results", // index where the aggregation is stored
"log": "", // aggregation logs (change history)
"metadata": {
"aggregationId": "zGQt4o8BqXcWBDa4I1DFGDH5", // aggregation ID
"aggregationTitle": "Exchange: anti-spam missed emails", // aggregation title
"aggregationDescription": "Exchange: anti-spam missed emails with an archive", // aggregation description
"index": ".incident_of_aggregation_results", // index where the aggregation is stored
"urlToAggregation": "https://smarthost.ru//incident-manager/aggregations-result...", // aggregation URL
"baseFields": {
// values of the incident-group comparison fields specified in the active action settings
},
"additionalFields": {
// values from Additional Fields in the active action settings
},
"comparation_fields": {
// comparison field values from the aggregation
},
"functional_fields": {
// functional field values from the aggregation
}
},
"sideEffects": {}, // an empty object is passed when an active action is run manually
"target": "manual", // manual indicates an active action or a new workflow status
"workflowId": "my-workflow", // aggregation workflow ID
"_source": {
// incident aggregation body
}
}
The sideEffects Field
When an active action is run manually, sideEffects is passed as an empty object ("sideEffects": {}), as shown in the aggregation example above.
When an active action runs as part of a workflow transition, target contains the new status and sideEffects contains script lists:
{
"sideEffects": {
"pre": [
// names of scripts that run before the status changes
],
"post": [
// names of scripts that run after the status changes
],
"groupingPost": false // for a bulk transition, run the post-action once for the incident group
},
"target": "in_progress"
}
Scripts in sideEffects.pre run before the incident or aggregation is updated. Incident Manager then updates the object status and runs the scripts in sideEffects.post.
Scripts run manually (target: "manual") cannot modify incident or aggregation data directly.
A script that modifies the state or fields of an incident or aggregation must be used as a primary action (pre-action) when transitioning to a new workflow state.
Using JavaScript
Use the following template to create an active action in JavaScript:
/** Function for executing active actions
* @param doc - fields to modify in the incident or aggregation
* @param metadata - metadata of the passed entity (aggregation or incident)
* @param entities - incident list for a bulk workflow transition post-action; an empty array is passed when an active action is run manually
* @param _source - complete, unmodified entity document (aggregation or incident)
* @param currentUser - current user: { name, backend_roles, roles }
* @param index - index where the entity is stored (aggregation or incident)
* @param log - entity logs (aggregation or incident change history)
* @param sideEffects - actions performed during a status transition
* @param target - indicates an active action or a new workflow status
* @param workflowId - entity workflow ID (aggregation or incident)
* @param incidentId - incident ID (passed for an incident)
* @param aggregationId - aggregation ID (passed for an aggregation)
* @param logger - logging function
*/
export default async (
doc,
metadata,
entities,
_source,
currentUser,
index,
log,
sideEffects,
target,
workflowId,
incidentId,
aggregationId,
logger
) => {
// function body
};
Parameters are passed directly when the function is called. The function must be asynchronous to work correctly and prevent premature termination.
The same JavaScript file can be used for active actions and workflow transitions. If the script is used as a post-action and requires currentUser or grouping, use the following argument order: (..., aggregationId, currentUser, logger, { grouping }).
Using Python
Use the following template to create an active action in Python:
import json
class Object:
# helper function for converting an object to JSON
def toJSON(self):
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=1)
""" Function for executing active actions
@param: doc - fields to modify in the incident or aggregation
@param: metadata - metadata of the passed entity (aggregation or incident)
@param: incidents - incident list for a bulk workflow transition post-action
@param: user - current user: { name, backend_roles, roles }
@param: index - index where the entity is stored (aggregation or incident)
@param: log - entity logs (aggregation or incident change history)
@param: sideEffects - actions performed during a status transition
@param: workflowId - entity workflow ID (aggregation or incident)
@param: target - indicates an active action or a new workflow status
@param: incidentId - incident ID (passed for an incident)
@param: aggregationId - aggregation ID (passed for an aggregation)
@param: _source - complete, unmodified entity document (aggregation or incident)
"""
def my_func(doc, metadata, incidents, user, index, log, sideEffects, workflowId, target, incidentId, aggregationId, _source, grouping):
# function code
print(json.dumps({"doc": doc})) # return doc with the fields to modify
if __name__ == "__main__":
_json = input() # read data from the stream through stdin
res = json.loads(_json)
my_func(
doc=res.get("doc"),
metadata=res.get("metadata"),
incidents=res.get("incidents", []),
user=res.get("user"),
index=res.get("index"),
log=res.get("log"),
sideEffects=res.get("sideEffects"),
workflowId=res.get("workflowId"),
target=res.get("target"),
incidentId=res.get("incidentId"),
aggregationId=res.get("aggregationId"),
_source=res.get("_source"),
grouping=res.get("grouping"),
)
This is one possible implementation. Other methods and functions can also be used.