Automation

Create api request instruction configurations that can be either triggered based on events or timed triggers. Automation instructions are json structures where we use the same templating functionalities that we use to generate pages to generate json instead.

Automation configuration is on the ‘Automation’ tab on the right side menu, and it is only available for ‘page’ or ‘automation’ type templates. Page types are deprecated for automations, they will still work but syntax corrections and new features will not be supported on that type.

Warning

As of 1.228+ automation no longer use jwt’s to operate. Sessions are used instead, this also means that regular user permissions are being used. Automations use a dedicated user group called ‘automation-group’, if some of the api’s are giving access errors (example: 1060 on erply api) then the rights on this group need to be adjusted according to the api calls used.

The user group will be generated automatically once a template is either saved or installed.

Workflow

Automation templates are processed twice, first when forming the json and second when the post operations are being triggered.

  1. Template formation step, on this step the json instruction is formed. All preset calls are processed and can be used in the json instruction generation.
    1. Calls defined in the url configuration presets are triggered on this step
    2. All template functions will run
    3. Result needs to be clean and valid json
  2. Post operations trigger step, on this step the api calls defined in the post operations are triggered.
    1. Calls made in the url configuration cannot be chained to the post operation calls, but the values can just be printed to the values as they are triggered in different steps
  3. Stage call step. Read staged automation at the end of this page for more information.

Automation Automation

Automation triggers

Automation triggering uses hmac (sha256) keys for authentication. Each automation page has a separate unique key that is occasionally refreshed (refresh will also re-register all webhook triggers).

By default, the key is not visible. Installation, store update or manual triggering will generate one in the ui.

Automation trigger Automation trigger

Structure

Basic structure consists of ‘postOperations’ and ’enabled’ keys. Post operations is a key/value set of parameters similar to what is being used on templates. Enabled flag can be used to toggle the automation feature on and off.

{
    "postOperations": [
        {
          "call": "value"
        }
    ],
    "enabled": true
}

Post operations

Post operations are input parameters on what should be done. These are the same instructions that are being used on regular html templates. Dynamic api instructions are also supported.

{
    "postOperations": [
        {
            "ErplyApi.Api.Post.createProduct": "saveProduct",
            "ErplyApi.Api.PostParam.createProduct.groupID": "1",
            "ErplyApi.Api.PostParam.createProduct.code": "Code",
            "ErplyApi.Api.PostParam.createProduct.nameENG": "Name"
        }
    ],
    "enabled": true
}

Sets of instructions can be separated by bucketing the instructions, to make the run on separate times. Dynamic api chaining is also supported here.

Warning

Separate sets here cannot access data between themselves. For example dynamic api chains cannot chain data from an api call of a separate instruction set. See staging instead for more complex automations.

{
    "postOperations": [
        {
            "ErplyApi.Api.Post.createProduct1": "saveProduct",
            "ErplyApi.Api.PostParam.createProduct1.groupID": "1",
            "ErplyApi.Api.PostParam.createProduct1.code": "Code 1",
            "ErplyApi.Api.PostParam.createProduct1.nameENG": "Name 1"
        },
        {
            "ErplyApi.Api.Post.createProduct2": "saveProduct",
            "ErplyApi.Api.PostParam.createProduct2.groupID": "1",
            "ErplyApi.Api.PostParam.createProduct2.code": "Code 2",
            "ErplyApi.Api.PostParam.createProduct2.nameENG": "Name 2"
        }
    ],
    "enabled": true
}

Url configuration

Url configuration’s static presets work the same way on automations as they do on regular templates. These calls are processed when the instruction is generated and cannot be chained into the post operation calls. To use these values in post operations, we can safely just print the values of the calls.

All calls both preset and post operations however are available for stages to be read under the ParentRequests map.

Reading webhook data

Webhook data can be read the limited model based support:

  • wm_sales_document -> .Data.Automation.SalesDocumentList
  • wm_customer -> .Data.Automation.PaymentList
  • wm_payment -> .Data.Automation.PaymentList

In this data the model data matches that of the data source naming for Erply Api

Supports limited data.

{
    "postOperations": [
        {{ range $index, $customer := .Data.Automation.CustomerList }}
            {
                "EMSApi.SendEmailInput.Subject": "Customer {{ $customer.CustomerID }} created",
                "EMSApi.SendEmailInput.To": "{{ $customer.Email }}",
                "EMSApi.SendEmailInput.Content": "<encode><h1>Hello {{ $customer.FullName }}</h1></encode>",
                "EMSApi.SendEmailInput.ContentType": "html"
            }
        {{ end }}
    ],
    "enabled": true
}

Or all possible values using the json parameter reading (supports all the same functionality as dynamic api parameter reading).

Supports all data.

{
    "postOperations": [
        {{ range $customer := (.Data.Automation.Request.Get "items").Array }}
            {
                "EMSApi.SendEmailInput.Subject": "Customer {{ $customer.Get "CustomerID" }} created",
                "EMSApi.SendEmailInput.To": "{{ $customer.Get "Email" }}",
                "EMSApi.SendEmailInput.Content": "<encode><h1>Hello {{ $customer.Get "FullName" }}</h1></encode>",
                "EMSApi.SendEmailInput.ContentType": "html"
            }
        {{ end }}
    ],
    "enabled": true
}

Testing & custom webhooks

Warning

As of 1.288+ the new automation types have the ‘TEST’ button in the editor instead of the preview. This test allows us to define the hook data (not used for timed triggers) and test the execution of the automation without the need to construct the hmac ourselves. Output will contain details if it was successful.

In order to test the functionality without using an actual webhook we can construct the webhook data and send it against the automation endpoint using a generated hmac key.

Warning

To use the following features please make sure that a key has been generated for the page (use the refresh button if not). Automation will still work if a key is not present (as one will be automatically generated for it) but in order to test that everything works it’s always best to generate it manually first.

Custom webhook

For custom webhooks use the page trigger key and generate a hmac for the entire json body. Add the key as a header ’trigger-key’ to the request and send it to the appropriate cluster automation endpoint.

/api/automation/{YOUR_CLIENTCODE}/automation-demo-page

Generate the hmac key with the trigger key from automation tab as a secret using sha256 method. Use the entire json body as content.

The same method can be used for any custom webhook triggers.

Erply webhook

Erply webhooks use a specific structure.

```json
{
    "id": "{HOOK_ID}",
    "hmac": "{HMAC}",
    "clientCode": "{CLIENT_CODE}",
    "table": "wm_customer",
    "action": "update",
    "eventCount": 1,
    "items": [
        {
            "rowId": 18651,
            "timestamp": "2022-05-18 15:10:46",
            "data": {
                "id": 18651,
                "code": "Auto-Prod-Code-3",
                "name": "Generated by automation 3"
            }
        }
    ]
}

For these use the trigger key from the automation tab as secret using sha256 method. Use the id of the webhook as content.

Chaining automation data into api calls

We can also chain automation webhook data into api requests, using the following url configuration as a sample

ServiceApi.Api.Get.getWorkorders: api/v1/workorder
ServiceApi.Api.Query.getWorkorders.<-id: Automation.items.0.id

We can also use a special helper modifier function to get the oldest date out of the webhook items. The argument ‘2006-01-02T15:04:05Z07:00’ is the format that the date should be converted to (depends on the api used).

The function will return the oldest date in the items and then the used api should filter items based on that.

ServiceApi.Api.Get.getWorkorders: api/v1/workorder
ServiceApi.Api.Query.getWorkorders.<-createdAtStart: Automation.items|@getOldestItemDate:2006-01-02T15:04:05Z07:00

Automation page could look something like this then

{
    "postOperations": [
        {{ range .Data.ServiceApi.Api.Requests.getWorkorders.Response.Array }}
        {
            "test": "{{ .Get "id" }}"
        },{{ end }}],
    "enabled": true
}

Logs

From 1.228+ the automations that have been linked to an application will log the failures in the application logs. Similarly, the debug flag for automation logs works with automations as well.

Staging

Only available for the ‘automation’ file types.

This allows us to make separate stages to automations (up to 5). Useful for cases where we need to perhaps construct data for the final operations, and we need access data from the original postOperations api calls.

For example api calls defined in postOperations the sample below are executed at the end of the automation, so logic to read the data cannot be added.

{
    "postOperations": [
        {
            "ErplyApi.Api.Post.createProduct1": "saveProduct",
            "ErplyApi.Api.PostParam.createProduct1.groupID": "1",
            "ErplyApi.Api.PostParam.createProduct1.code": "Code 1",
            "ErplyApi.Api.PostParam.createProduct1.nameENG": "Name 1"
        }
    ],
    "enabled": true
}

Define a stage

To define a second stage we use the stageTo instruction, name here is the name of the automation to use and data is representation of the data that we access from .Data.Automation.Request. The data here needs to be a json string.

We can use the jsonSet, jsonSetObj and jsonDel helpers to manipulate possible json for this input.

The entire automation will complete and then when successful it will execute the second stage when defined. Note that the second stages can access all the api requests made in the first automation without the need to pass that data specifically to the next.

{
    "postOperations": [
        {
            "ErplyApi.Api.Post.createProduct1": "saveProduct",
            "ErplyApi.Api.PostParam.createProduct1.groupID": "1",
            "ErplyApi.Api.PostParam.createProduct1.code": "Code 1",
            "ErplyApi.Api.PostParam.createProduct1.nameENG": "Name 1"
        }
    ],
    "stageTo": {
        "name": "da-at2-automation",
        "data": "{}"
    },
    "enabled": true
}

Optionally we can also define an execution behaviour (runBehaviour) for the stage, available options are:

  1. skip - stage will not be triggered if any of the postOperations on the parent automation fail (default value)
  2. always - stage will always be triggered, and it does not matter if the parent stage is successful or fails
  3. fail - stage will only be triggered if the parent automation fails

The field is optional and will use ‘skip’ by default.

    ...,
    "stageTo": {
        "name": "da-at2-automation",
        "data": "{}",
        "runBehaviour": "always"
    },
    ...

If the parent does indeed fail then we can access the failure message in the stages request data object,

.Data.Automation.Request.Get "parentError"

Define multiple stages

There is option to define multiple stages in array, but only one will be executed based on the runBehaviour of the stage and the result of the parent.

{
   "stageToOneOf": [
      {
         "name": "pg-aut2-automation"
      },
      {
         "name": "pg-autFail-automation",
         "runBehaviour": "fail"
      }
   ]
}

Construct and read data for second stage

Warning

Maximum amount of stages is 10

Adding items the data object

{{ $myJson := `{}` }}

<!-- Add to array -->
{{ $myJson = jsonSet $myJson "myArr." "test-1" }}
{{ $myJson = jsonSet $myJson "myArr. "test-2" }}

<!-- Add entire json strings (arrays or objects) -->
{{ $myJson = jsonSetObj $myJson "myData2" `[5,6]` }}

{
    "postOperations": [
        {
            "CaFaApi.Api.Get.getConfig": "configuration",
            "CaFaApi.Api.Query.getConfig.application": "GOERP"
        }
    ],
    "stageTo": {
        "name": "da-at2-automation",
        "data": {{ $myJson }}
    },
    "enabled": true
}

To read the api calls from the parent stage we can use the parentRequests data element. Note that all calls from the parent are accessible like this both calls made with presets and post operations.

If the parent defines multiple calls with the same name then only the last one called is accessible (for example in a loop). Loop index can be used to make unique names for the requests.

{
    "postOperations": [
        {{ range $index, $key := (.Data.Automation.Request.Get "myArr").Array }}
        {
            "Test": "{{ $key }}",
            "Test2": "{{ $.Data.Automation.ParentRequests.CaFaApi.getConfig.Response.Get "0.name" }}"
        },
        {{ end }}
    ],
    "enabled": true
}

Subsections of Automation

Custom event triggers

It’s possible to trigger automations from the template code by defined conditions, existence of specific parameters or by submitting a form.

Trigger automations from forms

To trigger an automation we need to add the form parameter with name AutomatApi.AutomationEvent.Name the value of it should be the automation we want to trigger.

<!DOCTYPE html>
<html>

    <head></head>

    <body>

        <form method="post">
            <input type="hidden" name="AutomatApi.AutomationEvent.Name" value="da-at1-automation">

            <button type="submit">Save</button>
        </form>
    </body>

</html>

Trigger automations on page load

To trigger automation immediately when the page is rendered we can use the tools’ helper. Note that this means that the automation will always trigger when the page is opened.

{{ .Tools.AutomationEvent "da-at1-automation" }}

Trigger automations conditionally on page load

Sometimes we might want to trigger the automations conditionally on page load, for this the regular if conditions apply. Wrapping these into the conditions will only make the run when the condition is truthful.

<!-- The automation will only be triggered if there is a custom parameter called triggerMyAutomation in the request -->
{{ if .Data.Parameters.triggerMyAutomation }}
    {{ .Tools.AutomationEvent "da-at1-automation" }}
{{ end }}

Passing custom data to automation triggers

We can also optionally pass custom data to the automations in json format. This can be some specific id’s, codes etc. You can pass as many parameters as you want.

Also, there is option to define id of the automation event, which is useful when we need to ensure that automation process were executed only once during processing time. See example below for each option.

Forms

Note that the value should be in valid json format. The json helpers might be useful here.

<!DOCTYPE html>
<html>

    <head></head>

    <body>

        <form method="post">
            <input type="hidden" name="AutomatApi.AutomationEvent.Name" value="da-at1-automation">
            <input type="hidden" name="AutomatApi.AutomationEvent.ID" value="uuid-or-something">
            <input type="hidden" name="AutomatApi.AutomationEvent.Data" value='{"browser": "{{ .Browser.Name }}"}'>

            <button type="submit">Save</button>
        </form>
    </body>

</html>

On automatic load

Using the jsonSet helper will make this a bit simpler.

{{ .Tools.AutomationEvent "da-at1-automation" (jsonSet `{}` "browser" .Browser.Name) "uuid-or-something" }}

Reading the parameters on the automation

To access the parameters we use the regular automation request syntax (.Data.Automation.Request). Structure after the Request.Get is based on the json we sent as input.

{
    "postOperations": [
        {
            "IntLogApi.Api.Post.myRequest1": "v1/log",
            "IntLogApi.Api.Json.myRequest1.application": "da-custom-events",
            "IntLogApi.Api.Json.myRequest1.string.value.visited": "{{ dtCurrent.Unix }}", 
            "IntLogApi.Api.Json.myRequest1.string.value.browser": "{{ .Data.Automation.Request.Get "browser" }}"
        }
    ],
    "enabled": true
}

Installer automations

Version 1.254+

It’s possible to trigger automations when an application is installed or updated. This allows the automations to set up some application specific configurations (example: cafa configurations or base kvs data).

For this create an automation and set its name in the application edit screen.

Install automation Install automation

Note

Installation will not fail if the automations fail or do not exist, so make sure the given automation exists and is linked to the application.

Install automation special data

There are 2 special fields available when the automation is triggered from the installation.

  • fromVersion - the version number the user had before, value will be 0 if this is a fresh install
  • toVersion - the version that is being installed

These values can be used to produce certain conditions for the automations.

{{ $fromVersion := .Data.Automation.Request.Get "fromVersion" }}
{{ $toVersion := .Data.Automation.Request.Get "toVersion" }}

{
    "postOperations": [
        {
            "CustomApi.Api.Post.myRequest1": "some-api-call"
        }
    ],
    "enabled": true
}