Reading and writing data

First thing to be aware of is that all data available through global is read only – meaning you cannot update Global Data Tables through the script the same way you want to update Person Data or One To Many tables. Since you may need to use a Global Data as a counter or similar there is a different way to update Global Data values in the script. Naturally settings are also read only.

Below we will describe how it works for each object. Note how the logic for recipient, global, and settings is identical.

Reading Data

Person Data, Global Data, and Settings

The recipient, global, and settings objects act like maps, so you can get these data fields with the standard js notations.

var firstname  = recipient.FIRSTNAME;
var dayOfWeek  = global["DAY_OF_WEEK"];
var inputSetting  = settings.MY_INPUT;

If no value is present, the result will be undefined. The values are also typed, so if the data field is a number, then the value returned is a number, etc. Currently, for the settings object, all values are typed as strings.

One To Many & Global Data Tables

For One To Many and Global tables you can again similarly access the data like recipient.table.PURCHASE or global.table["PRODUCTS"] – what you are getting back can be seen as a javascript array, i.e. recipient.table.PURCHASE[0] will return the first row in the list, Agillic provide no guarantee on the ordering of the rows returned. Each row is then itself a map and can be read that way, as before we use the same types as are defined through the UI.

Reading Global Data Tables is only possible on tables that consist of under 5.000 rows.

We to support most of the methods available on arrays in Javascript:

See Mozilla Documentation here

Currently we do not have support for all the methods (coverage is currently about 80%), we have focused on the ones that makes most sense in the context or reading and writing One To Many tables, so forEach(), filter() and find() are supported. Also some of the methods are not available directly on the table but only after another method has been applied, this will be changed before we go live officially, and at that point full documentation with examples will be provided.

Here are relevant Agillic array function examples

We are using the javax.scripting library in Java to provide the bridge between the JavaScript file and Agillic. This means that the JavaScript file is executed inside our Java application. Note that features available in ES6 or later are not supported.

Target Groups

For Target Groups you can check if a recipient is a member of a specified Target Group with the standard js notations:

var isValid  = recipient.groups["All Valid Recipients"];
var isLead  = recipient.groups.LEAD;

You can think of groups as a map where all values are Booleans, reflecting a Recipient’s status of membership preloaded Target Groups. If you query the groups map for a Target Group not listed in the required target group method, your script will fail.

Writing Data

Where allowed, the actual writing of recipient data to the database is done after the script successfully completes, in a particular order:

  1. Person Data
  2. One To Many
  3. Events

Global Data values are written immediately

This ordering is done, so that any Event you achieve has access to all the previously set data in the extension, and any Side effects are also executed after this, so all modification made in the extension are accessible.

Before the script successfully completes, you are able to read the updated values in that context. For example if my Recipient has FIRSTNAME = “Foo”, and the following code is run.

function execute(recipient,global,settings) {
    logger.info("Start: "+recipient.FIRSTNAME);
    recipient.FIRSTNAME = "Bar";
    logger.info("End: "+recipient.FIRSTNAME);
    return false;
}

The logs would print:

Start: Foo
End: Bar

However, the Recipient’s FIRSTNAME is still “Foo”, due to the script returning false, stopping the database write from completing. Http extensions are the only other extension where data can be written to recipients, but has a more complex return type(although the ordering mentioned above still holds). Therefore please see the documentation about Http Extension’s execution method for more details.

Monitor and Condition Extensions cannot write data to, or apply side-effects on recipients.

Person Data

For Person Data simply assign the value:

recipient.FIRSTNAME = "Foo";    //Firstname is be "Foo"
recipient.FIRSTNAME = null;     //Firstname is now empty
recipient["FIRSTNAME"] = "Bar"; //Firstname is now "Bar"
recipient.FIRSTNAME = "";       //Firstname is now empty

The values are typed, but we attempt type conversion in the following way:

String

Anything not “like” a map, array, or list, will be simply converted to the string type – so providing the boolean will result in either the string “true” or “false”.

Number

  • All numeric types – if we get NaN or if infinite is provided, the assignment will cause the script to fail.
  • Strings are attempted to be parsed to a number (“.” as decimal separator) – if converting to a valid number fails the assignment will cause the script to fail.
  • Everything else – the assignment will cause the script to fail.

Boolean

  • Boolean values (true / false)
  • Strings – the string “true” (any casing) will be handled as true everything else false.
  • Number – the value 0 will be seen as false – everything else true.
  • Everything else – the assignment will cause the script to fail.

Date and Timestamp

  • Javascript date objects.
  • Numbers – treated as epoch timestamps in milliseconds.
  • Strings, we attempt to parse strings as dd.MM.yyyy or dd.MM.yyyy HH:mm:ss if successful the parsed date is assigned – the parsing is lenient meaning that the parsing will allow for minor variations from the format, and will also allow for providing a full string with time for the date (but not the other way).
  • Everything else – the assignment will cause the script to fail.

As stated above the script will fail as soon as you attempt to assign the value.

Please be aware of the following:

  • We don’t check if the Person Data you assign to exists.
  • Checks for uniqueness or restriction is done after the script executes, not at assignment.
  • Data is not persisted in the database until after the script has run.

One To Many

For One To Many, assigning a value to a field in a row is exactly the same as for Person Data, including the type conversion.

Adding a record to a table is done like this:

recipient.table.COUPONS.push({
"ID":"123",
"DESCRIPTION":"This is a coupon",
"VALUE":100
});

When adding you must provide a non-empty value for the field defined as row key in the table. Any other fields are optional, and can be added later, same type rules as above apply.

You can remove a record in two ways:

recipient.table.COUPONS.delete({"ID":"123"});
recipient.table.COUPONS[0].delete();

So either call delete on the array with an object which must at least contain the value of the primary field you want to delete, or call delete on a particular item in the array.

Please be aware that if you use any of the array functions like filter, forEach(), … then the push and delete options directly on the table object will not be available.

Event

To achieve an Event on a recipient, use:

recipient.achieveEvent("My event");

Please note that Event achieving is a list. So if you call the above code twice you will also achieve the Event twice. Events are achieved in the order they were called in the code.

Function on Global Data

There can be many reasons why you would want to update the value of a Global Data during execution, the most basic being that you have a counter which defines the max number of recipients allowed through. Since both Steps and Conditions can be evaluated multiple times in parallel it is easy to run into concurrency issues. Therefore we do not allow direct writes to Global Data, but only allow access through a function which is based on best practices for access to shared resources, and will guarantee that access is secured.

Hopefully the description and example below will make it clear how to use it, but it is slightly more advanced than the rest of the functions made available, so please reach out if you want us to review the logic.

The function is defined on the global object and is called “merge” and takes three parameters:

  • The name of the Global Data to update
  • The default value to give the Global Data if no value was set on it
  • A function that will be applied in order to update the value in case a value is already there and returns the new value. We guarantee that there cannot be several calls to merge on the same Global Data happening in parallel, this also means that two scripts each updating the same Global Data in different way will not be able to update it in parallel.

A simple example that decreases a Global Data called AVAILABLE_TICKETS – and defaults to 1000 tickets, and then does a post check to see if the recipient got a ticket. Of course since any number less than 0 means no more tickets we just stop the counter at -1, this code would be running inside the main method of either a step or a condition.

Flow Context

The flow context argument is a simple map that gives access to data regarding the current flow execution. It has the following fields:

  • flowExecutionId: the id of the current flow execution. This will be the same for all recipients that are part of the same execution.
  • flowName: The name of the flow that is executing this extension.
  • stepName: The name of the extension step that is executing this extension.

Here is a basic example of usage and local test: See Example