Automation scripts
An automation script (further referred to as script) is a piece of code that allows you to implement custom business logic. Automation scripts are written in plain JavaScript with the support for node packages.
Take a look at our samples (the sample project to get started.
) and
See whether you can implement your logic using Workflows. |
There are two categories of automation scripts; server scripts and client scripts.
These are executed in the Corteza Corredor server. Use server scripts when:
Examples of usage:
|
|||
These are executed in the client’s browser (user agent). Use client scripts when:
Examples of usage:
|
Which one should you use? If you need to interact with the user (show notification, request confirmations), use client scripts, elsewhere use server scripts.
File structure
To start writing automation scripts, you must first define the appropriate file structure.
-
package.json
defines metadata as well as your dependencies. -
/server-scripts
contains a set of automation scripts that are executed on the Corredor server. -
/client-scripts
contains a set of automation scripts that will be executed inside the web application.-
Each sub-directory inside
/client-scripts
defines a bundle. When a web application loads, it fetches the bundle that is assigned to it (this is done automtically).
-
Both /server-scripts
and /client-scripts
assume that all underlying files are automation scripts with valid signatures.
When defining a file with utility functions, move it under /shared
or define a /util
(or similar) at the root of the project.
package.json
...
/server-scripts
...
/client-scripts
/auth
...
/admin
...
/compose
...
/messaging
...
/shared
...
…
indicates that you are free to structure your files as you see fit.
We do recommend you group your automation scripts based on their content, for example, scripts working with leads should go under the /Lead
directory.
/auth
, /admin
, /compose
, and /messaging
contain scripts specific to each web application (as discussed earlier).
/shared
contains code that client scripts can reuse.
Script signature
It is only possible to define one automation script per file. |
{
// A short label describing this script
label: '...',
// A longer description of what it does. Don\'t go over board.
description: '...',
// This controls script-level security, such as the invoking user.
// We cover some details a bit later.
security: {...},
// This function returns a list of triggers that specify when the script should be ran.
// We cover some details a bit later.
triggers: (t) {...},
// This is the code that is ran when the script is executed.
// We cover some details a bit later.
exec: (args, ctx) {...};
}
export default {
label: "label goes here",
description: "description goes here",
// Use the ones you need, delete the rest
triggers ({ before, after, on, at }) {
return before('event goes here')
.where('constraint goes here')
// Add/remove constraints here
},
// remove async if you aren't doing any async operations
// use object destructuring for args and ctx
async exec(args, ctx) {
// Code goes here
},
}
See server script samples, and client script samples for more. |
Automation triggers
Automation triggers (further referred to as triggers) control the timing of the execution of a specific automation script.
Automation triggers are evaluated in an isolated context that doesn’t allow any external data (variables or imports). This will not work:
|
-
an event that specifies what system events the trigger reacts to,
-
a resource that specifies what system resource the trigger reacts for,
-
a constraint that specifies how the event needs be presented as in order for the trigger to react.
These are explicitly triggered by pressing a button. Use explicit triggers when you wish to manually initiate an action, for example an OAuth authentication flow, redirection to an external resource, or data export. |
|||
These are implicitly triggered and based on system events. Use implicit triggers when you wish for an action to be automatically performed when triggered by another action or process; such as sending an email when you register a new user or adding a changelog entry when the content changes. Refer to resources and events for a complete list of events you can listen for. |
|||
Deferred Deferred triggers can only be used in server scripts and require explicit security context. |
The system triggers these at a certain point in the future; either periodically (define with cron expressions), or at a timestamp (use ISO 8601, Use deferred triggers when you want an action to periodically repeat or be performed at a certain point in the future. Examples of such use are recurring payments or sending holiday newsletters to your subscribers.
|
||
Sink Sink triggers can only be used in server scripts and require explicit security context. |
These are triggered by the system when it receives a request; either HTTP or email. Use sink triggers when you want to respond to requests; such as webhooks for external services or custom API endpoints, f1or example capturing data from external forms, tracking external document changes, and capturing payments.
|
Defining a resource
To define what resource the trigger should react for (such as module, user, role), we use the conveniently named .for('resource:goes:here')
method.
triggers ({ before }) {
return before('create', 'update')
// This will trigger for a compose record resource
.for('compose:record')
},
Refer to resources and events for a complete list of available resources and supported events.
Defining a constraint
Refer to resource constraints for a list of available constraint properties for each resource. |
To define how an event should be presented as (such as the module name, user email, role handle), we use the .where(property, operator, value)
method or a variation of it.
-
When applying two arguments to the method, the first one specifies the property and the second one specifies the value. The default equality operator is used.
-
When specifying three arguments to the method, the first one specifies the property, the second one specifies the comparison operator and the third one specifies the value.
triggers ({ before }) {
return before('create', 'update')
.for('compose:record')
.where('module', 'Lead')
.where('namespace', 'crm')
},
|
|||
|
|||
|
|||
|
|||
|
|||
|
Conventions
Object destructuring helps you shorten the code. Example:
|
|
Loose constraints may lead to unwanted side effects such as running the script when a record in a different namespace is created. Example:
|
Security context
Automation scripts are no exception to the access control system. |
The security context allows you to control who can invoke the automation script as well as what access the script should have.
The invoking user
Deferred and sink scripts require you to specify the security context as the invoker is not known. |
The invoking user is a person who performes an action that triggers the script execution. To examplify; you press a button, therefore you are the invoking user.
By specifying the invoking user, the automation script may access some resources that the actual invoking user may not have access to, such as personal client information.
This is only available for server-scripts. |
export default {
trigger (t) {...}
security: 'some-user-identifier-here',
exec (args, ctx) {...}
}
You can use the user’s handle, email or ID as the user identifier. We suggest you use an email or a handle. |
It is a good idea to create a new system user the purpose of which is that of script execution wherever that is needed. |
Allowing and denying script execution
Security context lets you prevent specific users from performing specific operations. Each user is attributed a role that specifies the degree of control they can operate with. To examplify; you can prevent regular users from signing documents or sending quotes.
-
allow
specifies which roles are permitted to access the automation script, -
deny
specifies which roles are not permitted to access the automation script,
This is only available for explicit scripts. It is ignored for any other script types |
export default {
trigger (t) {...}
security: {
allow: ['administrator', 'superuser'],
},
exec (args, ctx) {...}
}
export default {
trigger (t) {...}
security: {
deny: ['client', 'lead'],
},
exec (args, ctx) {...}
}
An example setup
/ .gitignore
/ .eslintrc.js
/ .mocharc.js
/ package.json
/ server-scripts
/ Sample.js
/ Sample.test.js
/ ...
/ client-scripts
/ ....
.vscode
node_modules
.nyc_output
coverage
yarn-error.log
module.exports = {
root: false,
env: {
node: true,
es6: true,
},
extends: [
'standard',
],
}
module.exports = {
require: [
'esm',
],
'full-trace': true,
bail: true,
recursive: true,
extension: ['.test.js'],
spec: [
'client-scripts/**/*.test.js',
'server-scripts/**/*.test.js',
],
'watch-files': [ 'src/**' ],
}
{
"scripts": {
"lint": "eslint {server-scripts,client-scripts}/**/* --ignore-pattern *.test.js",
"test:unit": "mocha",
"test:unit:cc": "nyc mocha"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^6.8.0",
"eslint-config-standard": "^14.1.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"esm": "^3.2.25",
"mocha": "^7.0.1",
"nyc": "^14.1.1",
"sinon": "^8.1.1"
},
"nyc": {
"all": true,
"reporter": [
"lcov",
"text"
],
"include": [
"client-scripts/**/*.js",
"server-scripts/**/*.js"
],
"exclude": [
"**/*.test.js"
],
"check-coverage": true,
"per-file": true,
"branches": 0,
"lines": 0,
"functions": 0,
"statements": 0
}
}
export default {
/* istanbul ignore next */
trigger ({ before }) {
return before('create')
},
exec () {
return 'Hello World!'
}
}
Pay attention to the following part:
|
import { expect } from 'chai'
import Sample from './Sample'
describe(__filename, () => {
describe('Sample exec result', () => {
it('should return a string', () => {
expect(Sample.exec()).to.eq("HelloWorld")
})
})
})
package.json
defines three scripts:-
lint
lints the code using the default ES6 standard (can be configured; see here), -
test:unit
runs unit tests defined inside.test.js
files (can be configured in the.mocharc.js
file), -
test:unit:cc
runs unit tests with code coverage.
The code coverage report gets generated into the Usually the http-server package is used to help with this, but a simple "Open in <browser name here>" suffices.
|