Custom scripts
Overview#
A trigger may have a custom script associated with it which allows for extra control over how the Notification Service works. If the trigger has an associated script then when event matching succeeds for the trigger, the script is executed and passed the same information as was used in event matching.
The script can be used to either continue or even skip the processing of the notification. The script might also change the information that is made available to templates.
Custom scripts processing multiple events#
For performance reasons, the script may be executed with multiple similar events in parallel. The script may return the passed events unchanged to continue processing. Alternatively, the script may remove some of the events to skip further processing of those events. The script might also alter and return the events to continue processing and change the data available in templates.
Custom script parameters#
The script must be a standard NamedUserItem script which has a function with a single parameter: input.
The input parameter contains the following:
trigger(Object): A representation of the trigger that invoked this script.id(String): The trigger ID.type(String): The trigger type (currently onlyEVENTtriggers are supported).
requests(Array): An array of matched notification event objects. Each entry is arequestobject. Therequestobject contains the current request data, which in the case of anEVENTtrigger, will be the event data.
The function should return an array of request objects. To alter the body or subject of a template, modify the relevant entry in the array. To skip a notification, remove the relevant entry from the array.
The function may be called with multiple notification requests. Note that only the returned entries will be sent, and the others will be skipped.
Example custom scripts#
Trigger based on file names#
Refer to the script below as an example of a custom script. This script will check to see if files in the event have a file name that starts with the string trigger_file_. If the file name does not start with that string, then no notifications will be sent. If the file does start with that string, then the downloadUrl value for the file will be added to the request data and will be available in templates.
const scriptContent = `async function processNotifications(input, libraries, context) {
const { PlatformApi } = libraries; const { IafFileSvc } = PlatformApi;
const results = []; for(const request of input.requests) {
const event = request.request;
// Get file name from the event const fileName = event.resource?.after?.name || event.resource?.name; const fileId = event.resource?.id;
if(fileName.startsWith('trigger_file_')) { const fileData = await IafFileSvc.getFileUrl(fileId, ctx); const downloadUrl = fileData?._url || fileData?.url; event.downloadUrl = downloadUrl; results.push(request); } }
return results;}`;
const result = await IafItemSvc.createNamedUserItems([{ _name: "notification-script-1", _shortName: "notification-script-1", _userType: "notification-script-1", _itemClass: "script", _namespaces: project._namespaces, _version: { _userData: scriptContent }}],"script", ctx);console.log("Created script:", result);Script that performs no operations#
The example script below performs no operations. It will just process notifications as if no script is present.
This example is shown because if you do not return the set of candidate notifications that are passed to the script then all of them are suppressed. The script is passed a set of notification candidates where each can either be altered, or removed, or left as is. For a script to indicate that it wants to continue processing all of these without changes, then you need to return everything that is passed in.
function passAllNotifications(input, libraries, context) { return input.requests;}Script that suppresses all script content#
This example script will suppress all script content and prevent all further processing for the trigger.
function suppressAllNotifications(input, libraries, context) { return [];}Notes on scripts#
When working with scripts, consider the following:
Structure of request object#
The input.requests[n].request object can be modified and those changes will be available during template processing.
For example, if a script adds the property input.requests[n].request.newProperty, then when the notification pipeline resumes with input.requests[n], the value ${request.newProperty} will be available in the matched template.
No other properties of the event objects should be modified. Any changes to other properties will be ignored. Removing an entry from the returned array will skip sending that notification.
The structure of the input.requests[n].request object depends on the way the trigger is invoked. For an EVENT type trigger, this will be the event that activated the trigger.
If a trigger is invoked by this event, the request will have a structure like the example below.
{ "event": "resource.ResourceUpdated", "resource": { "type": "datasources.OrchestratorRun", "namespaces": ["X"], "orchestratorId": "68a15d18-620c-4672-9e39-34106ed50b08" "before": { "status": "RUNNING", }, "after": { "status": "COMPLETED", } } }The input to the script will have the properties listed below.
input.requests[n].request.eventinput.requests[n].request.resource.typeinput.requests[n].request.resource.namespacesinput.requests[n].request.resource.orchestratorIdinput.requests[n].request.resource.before.statusinput.requests[n].request.resource.after.statusAvoiding scripts if event filters are sufficient#
For simple pattern-based event matching, a script is not actually required. This is because event filters support wildcard notation for pattern matching.
For example, in the earlier script, we checked to see if the file name starts with trigger_file_. Recall the code fragment below.
if(fileName.startsWith('trigger_file_')) { const fileData = await IafFileSvc.getFileUrl(fileId, ctx); const downloadUrl = fileData?._url || fileData?.url; event.downloadUrl = downloadUrl; results.push(request);
However trigger event filters are sufficient to do this kind of matching (although the script is still required to obtain the download URL).
The number of unnecessary invocations of the script can be reduced by writing an event filter. Refer to the example below. In this example, the resource.name filter is set to trigger_file_*. This will perform the same simple filtering as we did in the script.
{ "event": "resource.ResourceCreated", "resource.type": "filesvc.File", "resource.namespaces": project._namespaces[0], "resource.name": "trigger_file_*"}Associating a script with a trigger#
To associate the script with a trigger the _script property of trigger should be used.
The following properties are required when the _script property is set:
_script._userType: The user type of the script in the Item Service._script._name: The name of the function to call.
Assuming that the above script exists in TriggerService with a userType of notification-script-1, refer to the sample code below.
This script ensures that when a file is added that does not start with the string trigger_file_ then nothing happens. Alternatively, if a file is added that does start with this string, then the downloadUrl value is sent in the email notification.
const bodyContent = `<html><body>#if(\$request.resource && \$request.resource.after && \$request.resource.after.name)<h2>A new file was created: \${request.resource.after.name}</h2>#else<p>A new file was created</p>#end#if(\$request.downloadUrl)<p>To download the file, please click here: \${request.downloadUrl}</p>#else<p>The download link is missing</p>#end<p> <a href="\$cancelLink">Cancel Subscription</a> | <a href="\$cancelAllLink">Block Device</a></p><p>Request details: $request</p>#if($group)<p>Group: $group.name</p>#end</body></html>`;
const template = await IafNotification.createTemplate({ _namespaces: project._namespaces, _name: "template-1", _transport: "EMAIL", _format: "VELOCITY", _email: { _subject: "A new File was created, _body: bodyContent }, _locale: "en-GB"}, ctx);
console.log("Created template:", template);
const trigger = await IafNotification.createTrigger({ _namespaces: project._namespaces, _type: "EVENT", _name: "trigger-1", _event: { _filters: { "event": "resource.ResourceCreated", "resource.type": "filesvc.File", "resource.namespaces": project._namespaces[0] } }, _templates: [template], _groups: [group], _senders: [sender], _script: { _userType: "notification-script-1", _name: "processNotifications" }}, ctx);
console.log("Created trigger:", trigger);Trigger script authentication#
When creating a trigger, the calling user's accessToken is converted to a long-lived token and associated with the script. Therefore, the user creating the trigger must have sufficient access to perform all actions in the script.
When updating a trigger, the calling script accessToken is not overwritten by default. The trigger remains associated with the accessToken of the user who originally created it.
To update the trigger script access token, the _script._accessToken parameter is supported when updating the trigger.
_script._accessToken is a string which should be a valid access token, without the Bearer prefix.
Refer to sample code below.
Note: The
_script._accessTokenproperty is write-only, and only supported in updates or create requests. The value is never returned in API responses.
const trigger = await IafNotification.updateTrigger(trigger.id, { _script: { _userType: "test_notification_script" _name: "processNotifications" , _accessToken: ctx.accessToken }}, ctx);