Actions

Each action has this basic model. An action receives an input message which depend on the action hook and the previous action in case of an action pipeline. After execution of the action it will output a message which could be altered depending on the action type.

{
    "type": "",        // Type of the action.
    "message": {       // (Optional) To be merged with the input message.
        "topic": "",   // (Optional) Can be a string or null.
        "payload": {}  // (Optional) Can be any number, string, object or array.
    }
}

Pipeline can consist of actions with type:

  • action: Refers to another action to be executed.

  • collect: Collect data from a widget.

  • consoleLog: Write to the browser’s console log.

  • convert: Converts data to and from JSON, Base64.

  • copy: Copy to clipboard.

  • delegate: delegate the execution context of an action pipeline.

  • dismiss: Dismiss an active floating tab or the last shown prompt

  • gettime: Converts relative, ISO UTC and milliseconds since Epoch timestamps.

  • function: Advanced Endpoint call to the system.

  • modify: Change the model of a widget.

  • notify: Display a notification.

  • openLink: Opens a URL in the browser.

  • passthrough: Passes the input message to the next action with the option to merge a message and/or payload.

  • prompt: Show a dialog.

  • read: Reads a value of an object.

  • read-write: Used for data sources, supports read and write.

  • refresh: Refresh a widget.

  • send: Send data to another widget.

  • subscribe: Subscribe to data changes in the system.

  • switch: Execute different actions based on conditions.

  • tpm-oee: Read configuration table models from the backend relating to OEE monitoring.

  • transform: Transform the data using MongoDB’s Aggregation Pipeline logic.

  • wait: Adds a delay before executing the next action.

  • write: Writes a value to an object.

Note: Features marked with (*) are not supported yet.

Action

Invoke a named action defined in the widget’s own actions collection or in the actions collection at compilation level. Actions defined at the widget level take precedence over those at compilation level. If a widget refers to a named action which exists in his own model and at compilation level, the one in the widget collection will be executed.

{
    "type": "action",
    "name": "NAME OF THE ACTION"
}

Refer to the write-example-01 compilation to see how named actions are defined and used.

Collect

Collect data from a widget. The specific data retrieved dependents on the source widget. In general, collect returns the same payload data provided to action pipelines defined directly on the referenced widget. Refer to the widget specific documentation for more details. The collected information is assigned to the provided key name of the message payload. If a key is not specified the whole payload will be overwritten with the collected data.

{
    "type": "collect",
    "from": "Place the ID of the widget here",
    "key": "collectedData"
}

The from field can be set to one of the following:

  • Widget ID: ID of a widget at the same level in the compilations. Note: Dot-notation cannot be used to collect data from widgets nested inside tabs.

  • Route: The route notation is typically used to access widgets contained in nested tab compilations.

  • "self": This allows the pipeline to fetch data from the widget that initiated the actions. This might seem like an odd thing to do since the message at the beginning of a pipeline will be initialized by the source widget, but as the execution progresses this information may be overwritten. Using the collect action on "self" provides a way to get back to the original content.

Note: Collect is mainly intended to grab the "data" content of a widget. If you need to get access to any other fields of a widget during pipeline execution, the modify action can be used together with set

Console Log

Writes the input message payload to the browser’s console log.

{
    "type": "consoleLog"
}

Add context by providing a tag.

{
    "type": "consoleLog",
    "tag": "fetch response"
}

Example to set a fixed text message on the payload:

{
    "type": "consoleLog",
    "tag": "fetch response",
    "message": {
        "payload": "This is just a message"
    }
}

Example to merge the input message payload with other key-value data.

{
    "type": "consoleLog",
    "tag": "fetch response",
    "message": {
        "payload": {
            "otherText": "This is just some other text message"
        }
    }
}

Convert

Converts the payload to a specified format. Supported formats are:

  • json

  • base64

Encode the payload:

{
    "type": "convert",
    "encode": "json"
}

Decode the payload:

{
    "type": "convert",
    "decode": "json"
}

Copy

Copies the payload to the clipboard.

{
    "type": "copy"
}

Delegate

Using tabs widgets, compilations can be created that contain sub-compilations in each tab. Widgets defined within these cannot directly interact with other widgets in peer level compilations.

The delegate action provides a mechanism to address this constraint.

{
    "type": "delegate",
    "action":  []
}

This action is probably easiest to understand by considering an example.

Suppose we have a tabs widget with two tab instances, each containing their own text widget, text01 and text02. We want to change the text of text02 when clicking on text01. As a first attempt, we might try something like this:

{
    "type": "text",
    "text": "Click Me",
    "id": "text01",
    "actions": {
        "onClick": {
            "type": "send",
            "to": {
                "route": [
                    "tabs01",
                    "tab02",
                    "text02"
                ]
            },
            "message": {
                "payload": "Text on tab 1 was clicked"
            }
        }
    }
}

This fails to update text02 since, from within the tab01 compilation, there is no widget that resolves to the provided route. The route only makes sense when it is traversed starting at the root compilation.

Root (compilation)
|
+- tabs01 (widget)
   |
   +- tab01 (compilation)
   |  |
   |  +- text01 (widget)  <-- Action starts here
   |
   +- tab02 (compilation)
      |
      +- text02 (widget)

To get the correct execution context we need to define the action at either the root compilation level, or in the actions section of the tabs01 widget.

Note: The execution context refers to the compilation model from which routes and widget ids are resolved.

A named action can be be defined in the root compilation and invoked from text01. This works since named actions are resolved by searching upwards in the containment hierarchy. In this case the onClick could be defined like so:

{
    "type": "text",
    "text": "Click Me",
    "id": "text01",
    "actions": {
        "onClick": {
            "type": "action",
            "name": "modifyWidgetOnSecondTab"
        }
    }
}

with the named action modifyWidgetOnSecondTab in the root compilation:

{
    "actions": {
        "modifyWidgetOnSecondTab": {
            "type": "send",
            "to": {
                "route": [
                    "tabs01",
                    "tab02",
                    "text02"
                ]
            },
            "message": {
                "payload": "Text on tab 1 was clicked"
            }
        }
    }
}

Unfortunately, just declaring the named action in the root compilation is not enough. The execution context is not affected by where the action is declared, unless delegate is used.

{
    "actions": {
        "modifyWidgetOnSecondTab": {
            "type": "delegate",
            "action": [
                {
                    "type": "send",
                    "to": {
                        "route": [
                            "tabs01",
                            "tab02",
                            "text02"
                        ]
                    },
                    "message": {
                        "payload": "Text on tab 1 was clicked"
                    }
                }
            ]
        }
    }
}

In other words, delegate changes the context of the execution pipeline to be at the level where it is defined in the compilation. Using the new context, the pipeline defined in the action property is executed.

When the delegate action returns, the context is restored and any subsequent actions will be executed in the context that was there before.

Note: The context also includes the widget that initiated the pipeline, and is referred to as "self". If the delegate action is declared at compilation level, the self widget will not be set.

Dismiss

This action is used to close a modal dialog shown using the prompt action or to close a floating tab from within the tab compilation.

{
    "type" : "dismiss"
}

Note: The dismiss action closes the last shown prompt. Prompts can be "stacked" by invoking a prompt action from within an active popup dialog. When dismiss is called, only the top most instance is closed.

The example below shows the use of the dismiss action to close a tab.

{
    "type": "tabs",
    "appearance": {
        "type": "floating"
    },
    "tabs": [
        {
            "id": "tab01",
            "name": "Tab01",
            "indicator": {
                "title": "Tab 01"
            },
            "compilation": {
                "version": "1",
                "widgets": [
                    {
                        "type": "text",
                        "text": "Dismiss",
                        "captionBar": false,
                        "actions": {
                            "onClick": {
                                "type": "dismiss"
                            }
                        },
                        "layout": { "x": 0, "y": 0, "w": 32, "h": 32,
                            "static": false
                        },
                        "id": "txt"
                    }
                ],
                "options": {
                    "stacking": "vertical",
                    "numberOfColumns": 32,
                    "width": 200,
                    "height": 150,
                    "numberOfRows": {
                        "type": "count",
                        "value": 32
                    }
                }
            }
        }
    ],
    "options": {
        "tabAlignment": "top"
    },
    "layout": { "x": 31, "y": 0, "w": 29, "h": 5,
        "static": false
    },
    "id": "tabs"
}

GetTime

The gettime action can perform two types of functions:

  • Convert a relative time expression to the equivalent ISO UTC time string or an Epoch timestamp. By default the output is an ISO string. If the optional asEpoch property is set to true, the output is returned as a number.

  • Convert between ISO UTC string and epoch integer

A time relative expression consists of a , indicating *now, optionally followed by an offset expression subtracted from, or if required, added to the current time.

The offset is stated as an integer number followed by a time scale unit. The supported time scale units are:

  • ms - millisecond

  • s - second

  • m - minute

  • h - hour

  • d - day

  • w - week

Here are some examples of relative time expressions:

Expression Description

*

Now

*-5d

5 days ago

*+30m

30 minutes from now

The table below illustrates the conversions performed by the gettime action.

type value type value example results

Relative

string

*-1d

Now minus one day as an ISO UTC string. Unless: asEpoch : true Then the output is the number of milliseconds since January first 1970

ISO UTC

string

2021-04-28T09:44:35.668Z

1619603075668

Milliseconds since Epoch

number

1619603075668

2021-04-28T09:44:35.668Z

The JSON below shows an example of gettime used to convert relative times to absolute ISO UTC strings:

{
    "type": "gettime",
    "set": [
        {
            "name": "starttime",
            "value": "*-5d"
        },
        {
            "name": "endtime",
            "value": "*-1d"
        }
    ]
}

Resulting message:

{
    "payload": {
        "starttime": "2021-04-24T10:31:04.091Z",
        "endtime": "2021-04-28T10:31:04.092Z"
    }
}

This example shows how to convert a relative time to its Epoch equivalent:

{
    "type": "gettime",
    "set": [
        {
            "name": "starttime",
            "value": "*-5d",
            "asEpoch" : true
        }
    ]
}

yields this output message:

{
    "payload": {
        "starttime": 1619260264103
    }
}

The action can also take the set instruction from the message payload to do dynamic conversions.

Action message input:

{
    "payload": {
        "set": [
            {
                "name": "myTimestamp",
                "value": "*-2d"
            }
        ]
    }
}

Action:

{
    "type": "gettime"       // Notice it does not contain a set field.
}

Result is the message output:

{
    "set": [],                      // Since the action result is merged with the message input the set is still present.
    "myTimestamp": 1630497600000
}

Function

Invoke an Advanced Endpoint.

{
    "type": "function",
    "lib": "LIBRARY NAME",
    "func": "FUNCTION NAME", // (Optional) Function name in case library is Lua table.
    "farg": {},              // (Optional) Function argument.
    "ctx": ""                // (Optional) System object path.
}

Modify

With modify the model of a widget can be changed. Rather than directly altering the underlying widget, this action applies changes by performing the following steps:

  1. Copy the work model of the widget being modified into the model field of the message received by the action.

  2. Merge operator fields from the message.payload into the action definition. (Have a look at this example to see the process in action).

  3. Apply one or more modification operator. These operators typically change values in the message.model.

  4. Update the the work-model of the widget with the content of message.model. This triggers update and possibly refresh lifecycle hooks depending on what was changed and the action settings. (Also see Using modify to collect model information)

If no modification operator is specified a mergeObjects will be performed. This will merge / overwrite the properties from the message.payload into the model rather than applying the message.model. (See example below)

The id field is used to point to the widget which needs to be modified. The value is normally a string with the ID of the relevant widget.

{
    "type": "modify",
    "id": "TextWidget"      // Pointing to a Text widget which has an ID of 'TextWidget`.
}

Should you need to reference a nested widget, which is to say a tab instance of a Tabs widget or a widget which resides within a tab, you need to use a route expression.

{
    "type": "modify",
    "id": {
        "route" : [
            "MyTabs",       // ID of the Tabs widget.
            "Tab01",        // ID of the single tab.
            "TextWidget"    // Pointing to a Text widget which has an ID of 'TextWidget` which.
        ]
    }
}
The id field may be set to "self" resulting in the action being applied to the currently scoped widget.

Modification Operators

The following modification operators are supported, multiples of which can be specified in the same action. They are evaluated in the order shown. In other words, if multiple modification operators are used, the set modification is done first, followed by unset and so on.

  • set: Add field to model or update field.

  • unset: Remove field from model.

  • addToArray: Adds an item to an array field.

  • removeFromArray: Removes one or more items from an array that matches the provided fields.

  • filter: Removes items from an array field based on a condition.

A transform action will be performed under the hood with completeMsgObject set to true. As stated earlier, the model field is added to the input message by the modify action before the transform actions starts. It is read from the model of the widget being modified (designated by the id field).

The message payload, which is passed down from the originating widget or previous action in the pipeline is typically used as the source for setting model fields.

Main signature of the modify action is:

{
    "type": "modify",
    "id": "TextWidget",
    "refresh": true,    // Default is true. Set it to false to prevent a widget refresh.
    "debug": false      // If true, writes the model to the console log, after the modification.
}

Examples

A number of examples are presented to illustrate how modify actions are used and what the underlying transform logic looks like

Merge the widget model with the message payload

{
    "type": "modify",
    "id": "TextWidget"
}

Will result in a transform action:

{
    "type": "transform",
    "completeMsgObject": true,
    "aggregateOne": [
        {
            "$project": {
                "model": {
                    "$mergeObjects": [
                        "$model",
                        "$payload"
                    ]
                },
                "payload": "$payload"
            }
        }
    ]
}

To modify a specific sub document of the model, make use of the supported modification operators.

Example to update the font size:

{
    "type": "modify",
    "id": "TextWidget",
    "set": [
        {
            "name": "model.options.style.fontSize",
            "value": "50px"
        },
        {
            "name": "model.options.style.fontFamily",
            "value": "Courier New"
        }
    ]
}

Will result in a transform action:

{
    "type": "transform",
    "aggregateOne": [
        {
            "$set": {
                "model.options.style.fontSize": "50px",
                "model.options.style.fontFamily": "Courier New"
            }
        }
    ]
}

Example to update the style:

With this example the custom style will only include fontSize.

{
    "type": "modify",
    "id": "TextWidget",
    "set": [
        {
            "name": "model.options.style",
            "value": {
                "fontSize": "50px"
            }
        }
    ]
}

Will result in a transform action:

{
    "type": "transform",
    "aggregateOne": [
        {
            "$set": {
                "model.options.style": {
                    "fontSize": "50px"
                }
            }
        }
    ]
}

Example to remove the style example:

This way the widget uses the default style.

{
    "type": "modify",
    "id": "TextWidget",
    "unset": ["model.options.style"]
}

Will result in a transform action:

{
    "type": "transform",
    "aggregateOne": [
        {
            "$project": {
                "model.options.style": 0
            }
        }
    ]
}

Example to update an item by index:

This example changes the aggregate of the first pen in a chart. Since the field notation is one-on-one used in the Aggregation Pipeline, the index is zero based.

{
    "type": "modify",
    "id": "chartWidget",
    "set": [
        {
            "name": "model.chart.pens.0.aggregate",
            "value": "AGG_TYPE_AVERAGE"
        }
    ]
}

Will result in a transform action:

{
    "type": "transform",
    "completeMsgObject": true,
    "aggregateOne": [
        {
            "$set": {
                "model.chart.pens.0.aggregate": "AGG_TYPE_AVERAGE"
            }
        }
    ]
}

Example to add an item to the data array of a table widget:

{
    "type": "modify",
    "id": "fixedTableWidget",
    "addToArray": [
        {
            "name": "model.data",
            "value": {
                "column1": 1,
                "column2": 2
            }
        }
    ]
}

Will result in a transform action:

{
    "type": "transform",
    "completeMsgObject": true,
    "aggregateOne": [
        {
            "$set": {
                "model.data": {
                    "$concatArrays": [
                        {
                            "$cond": [
                                {
                                    "$isArray": [
                                        "$model.data"
                                    ]
                                },
                                "$model.data",
                                []
                            ]
                        },
                        [
                            {
                                "column1": 1,
                                "column2": 2
                            }
                        ]
                    ]
                }
            }
        }
    ]
}

Example to remove an item from an array by index:

Remove the third row from a fixed data table.

{
    "type": "modify",
    "id": "fixedTableWidget",
    "removeFromArray": [
        {
            "name": "model.data",
            "idx": 2
        }
    ]
}

Will result in a transform action:

{
    "type": "transform",
    "completeMsgObject": true,
    "aggregateOne": [
        {
            "$project": {
                "model.data.1": 0
            }
        }
    ]
}
The index provided in the removeFromArray action is zero based, and must be a static number. This begs the question "How do I delete a row based on user input?". The answer is to do the modify in two steps.

* Set the removeFromArray property in the message payload passed to the modify action
* Then do the 'modify', leaving out the removeFromArray property, which will be taken from the message payload.

The following example should make this clearer.

Example to remove an item from an array based on a match expression:

Remove the rows from a fixed data table for which the name field equals Inside Temperature and the value field is equal to 26.

{
    "type": "modify",
    "id": "fixedTableWidget",
    "removeFromArray": [
        {
            "name": "model.data",
            "item": {
                "name": "Inside Temperature",
                "value": 26
            }
        }
    ]
}

Alternative structure:

{
    "type": "modify",
    "id": "fixedTableWidget",
    "removeFromArray": [
        {
            "name": "model.data",
            "item": [
                {
                    "name": "name",
                    "value": "Inside Temperature"
                },
                {
                    "name": "value",
                    "value": 26
                }
            ]
        }
    ]
}

Will result in a transform action:

{
    "type": "transform",
    "completeMsgObject": true,
    "aggregateOne": [
        {
            "$set": {
                "model.data": {
                    "$filter": {
                        "input": "$model.data",
                        "as": "item",
                        "cond": {
                            "$or": [
                                {
                                    "$ne": [
                                        "$$item.name",
                                        "Inside Temperature"
                                    ]
                                },
                                {
                                    "$ne": [
                                        "$$item.value",
                                        26
                                    ]
                                }
                            ]
                        }
                    }
                }
            }
        }
    ]
}
Make sure the fields referenced in the item property exist in the array to be modified. Referring to model fields which are not present while deciding which rows to remove from an array, can yield unexpected results.

To illustrate the point, consider the following:

Suppose we have a table with name and value fields as before and want to remove all rows where the value is null. We create the modify action with a typo in the item property (referring to valueX rather than value):

{
    "type": "modify",
    "id": "fixedTableWidget",
    "removeFromArray": [
        {
            "name": "model.data",
            "item": {
                "valueX": null
            }
        }
    ]
}

When this action executes, the transform filter will look up model.data.valueX in each row, which always yields null since the field is not there, and compare it to the target value of null, resulting in all rows being deleted.

Example to filter items from an array:

Filters the rows from a fixed data table of which the column value is greater than or equal to 20. The condition is an Aggregation Pipeline filter condition. Within the condition $$item is reserved for referencing an item in the array.

{
    "type": "modify",
    "id": "fixedTableWidget",
    "filter": [
        {
            "name": "model.data",
            "condition": {
                "$gte" : [
                    "$$item.value", 20
                ]
            }
        }
    ]
}

Will result in a transform action:

{
    "type": "transform",
    "completeMsgObject": true,
    "aggregateOne": [
        {
            "$set": {
                "model.data": {
                    "$filter": {
                        "input": "$model.data",
                        "as": "item",
                        "cond": {
                            "$gte" : [
                                "$$item.value", 20
                            ]
                        }
                    }
                }
            }
        }
    ]
}

Using modify to collect model information

The modify action can also be used to copy properties from the widget’s work model into the message payload from where these are available to downstream actions. In doing so, the source widget is not changes at all. Consequently there is no need to invoke update and refresh lifecyle hooks which are not triggered in this scenario.

The table below summarizes the behavior of the modify action based on the setting of the refresh flag, and whether the model was modified.

refresh value Model altered Lifecycle hooks triggered

undefined

false

No refresh or update

undefined

true

Refresh and update

false

false

No refresh and no update

false

true

Only update

true

false

Refresh and update

true

true

Refresh and update

Example:

Suppose we want to toggle the background color of a text widget between say green and transparent each time it is clicked. To achieve this a switch action can be used which "looks at" the current background color and set the opposite one in onClick. The tricky part is getting access to the current color. The json snippet below shows how this might be achieved:

{
    "actions": {
        "onClick": [
            { // Start by reading the current background color.
                "type": "modify",
                "id": "self",
                "set": [
                    { // Note how the model and payload are referenced to
                      // read the background color and save it in the message payload
                        "name": "payload",
                        "value": "$model.options.style.backgroundColor"
                    }
                ]
            },
            { // Swop the colors in the message payload.
                "type": "switch",
                "case": [
                    {
                        "match": { // if the current bg is transparent
                            "payload": "transparent"
                        },
                        "action": { // then set it to green
                            "type": "passthrough",
                            "message": {
                                "payload": "green"
                            }
                        }
                    },
                    {
                        "match": {}, // otherwise
                        "action": {  // set to back to transparent
                            "type": "passthrough",
                            "message": {
                                "payload": "transparent"
                            }
                        }
                    }
                ]
            },
            { // Apply the payload color to the widget background.
                "type": "modify",
                "id": "self",
                "set": [
                    {
                        "name": "model.options.style.backgroundColor",
                        "value": "$payload"
                    }
                ]
            }
        ]
    },
}

Notify

Displays a notification on the top right of WebStudio.

{
    "type": "notify",
    "title": "Copied",
    "text": "📋 Copied to Clipboard",
    "duration": 2500,
    "transition": "slide"
}

title is optional and is by default set to the title defined in the widget captionBar. In case the title is not defined in the caption bar the name or id of the widget will be shown.

duration is optional and by default 3000.

transition is optional with a default value of slide.

  • slide Smooth sliding of notification

  • bounce Bounce in of the notification

  • zoom Zoom in and out

  • flip Flips the notification

Opens a hypermedia link.

{
    "type": "openLink",
    "url": "https://www.lipsum.com",
    "target": "_blank"
}

target is optional and by default _blank.

  • _self Opens the document in the same window/tab as it was clicked.

  • _blank Opens the document in a new window or tab.

Passthrough

The input message will be passed through to the next action with the option to merge with a defined message.

{
    "type": "passthrough",
    "message": {
        "someField": "This field will be merged besides input message topic and payload",
        "payload": {
            "attrib": "This field will be merged within the input message payload"
        }
    }
}

Prompt (Dialog)

This action causes a popup dialog (prompt) to be shown.

  • Content: The content/model of the prompt is declared in a message payload and must be a single widget. It is possible to indirectly show a complete compilation by using a tabs widget containing one or more tab instances. The appearance of a single compilation is achieved by defining one tab and hiding the indicator.

  • Dialog title: Text to display in the dialog title bar can be defined using the captionBar.title element of the widget.

     {
        "type": "prompt",
        "width": "500px",
        "height": "500px",
        "message": {
            "payload": {
                "type": "text",
                "text": "Hello world",
                "captionBar": {
                    "title": "Prompt Title"
                },
                "actions": {
                    "onClick": {
                        "type": "action",
                        "name": "some-action"
                    }
                }
            }
        }
    }

    Since the prompt repurposes the widget’s captionBar, any of the captionBar properties can be set. If the widget-edit button {} is visible (see showDevTools), the work model of the prompt can be inspected, albeit not edited.

  • Closing the prompt: The most direct way to close the prompt dialog is to click on the "X" icon in the title bar. Pressing the ESC key on the keyboard will also hide the prompt.

    If you choose not to show the title bar, then users of your compilations may be left wondering how to close the prompt, since there is no visual indication that the ESC key can be used. In this situation you can invoke the dismiss action from an onClick handler as shown below.

    {
        "type": "prompt",
        "message": {
            "payload": {
                "type": "text",
                "text": "Click or press ESC to Hide prompt",
                "captionBar": {
                    "hidden": true
                },
                "options": {
                    "style": {
                        "textAlign": "center",
                        "fontSize": "20px",
                        "fontWeight": "bold"
                    }
                },
                "actions": {
                    "onClick": {
                        "type": "dismiss"
                    }
                }
            }
        }
    }
  • Using Named Actions: Actions implemented inside the prompt can be made to manipulate widgets outside its scope by using named actions.

    When invoking named actions declared at root compilation or source widget level, the delegate action is usually required to ensure that the changes are applied in the appropriate context and directed at the right widget.

    To make sense of this statement it might be helpful to refer to the prompt-02 example. It shows how to load a prompt from a click in the main compilation, containing a tabs widget with its indicator hidden. The text widgets in the prompt are clickable, resulting in a further popup and changes to other text widgets.

    Named actions declared on the widget from which the prompt was invoked take precedence over named actions at compilation level.

Read

Reads the dynamic value of an object or the value of an object property by executing a Read endpoint request. The action model comes in three variants depending on how the path and options are specified:

Path and options provided at root level

This form of the read request returns only the value of the object or property referred to in the path, omitting the timestamp, id and quality.

{
    "type": "read",
    "path": "/System/Core/Examples/Demo Data/Process Data/FC4711",
    "opt" : {
        "q": "COREPATH"
    }
}
Name Description

path

Path to the object or property.

opt

Optional query details field, used to request information other than the object value. Supported arguments are described in the Read endpoint section in the Web API documentation.

Example read object’s dynamic value.

{
    "type": "read",
    "path": "/System/Core/Examples/Demo Data/Process Data/FC4711"
}

The result is returned as a single value assigned to the message payload. For example:

{
    "payload": 49.87
}

Example read object’s property value.

{
    "type": "read",
    "path": "/System/Core/Examples/Demo Data/Process Data/FC4711.OPCEngUnit"
}

Example to read a Lua KPI table object.

{
    "type": "read",
    "path": "/System/Core/Examples/Demo Data/Process Data/FC4711",
    "opt": {
        "STARTTIME": "2020-10-13T00:00:00.000Z",
        "ENDTIME": "2020-10-14T00:00:00.000Z",
        "TIMESTAMP": "1602633600"
    }
}

Example of the query mechanism support by the Read Web API endpoint.

{
    "type": "read",
    "path": "/System/Core/Examples/Demo Data/Process Data/DC4711",
    "opt": {
        "q": "COREPATH"
    }
}

Path and options provided in the item property

Read all VQT properties for a single entity.

{
    "type": "read",
    "item": {
        "p": "/System/Core/Examples/Demo Data/Process Data/FC4711",
        "opt": {} // options
    }
}
Name Description

item

Single item for which to read the VQT values.

p

Path to the object or property.

opt

Optional query details field, used to request information other than the object value. Supported arguments are described in the Read endpoint section in the Web API documentation.

The output from the read looks something like this:

{
    "i": 281474980511744,
    "p": "/System/Core/Examples/Demo Data/Process Data/FC4711",
    "q": 0,
    "t": "2022-04-27T14:07:06.781Z",
    "v": 36.58641815185547
}

Path and options provided in the items property

Read all VQT properties for a list of entities.

{
    "type": "read",
    "items": [
        {
            "p": "/System/Core/Examples/Demo Data/Process Data/FC4711",
            "opt": {} // options
        },
        {
            "p": "/System/Core/Examples/Demo Data/Process Data/DC4711",
            "opt": {} // options
        }
    ]
}
Name Description

items

Array of items for which to read the VQT values.

p

Path to the object or property.

opt

Optional query details field, used to request information other than the object value. Supported arguments are described in the Read endpoint section in the Web API documentation.

The output from the read looks something like this:

[
    {
        "i": 281474980511744,
        "p": "/System/Core/Examples/Demo Data/Process Data/FC4711",
        "q": 0,
        "t": "2022-04-27T14:12:45.781Z",
        "v": 43.22888946533203
    },
    {
        "i": 281474980118528,
        "p": "/System/Core/Examples/Demo Data/Process Data/DC4711",
        "q": 0,
        "t": "2022-04-27T14:12:24.781Z",
        "v": 10.687927246093746
    }
]

Read-write

Can be used for widgets which support reading and writing.

{
    "dataSource": {
        "type": "read-write",
        "path": "/System/Core/Examples/Tables/Example01"
    }
}

Refresh

Refresh a widget. No message will be send to the target widget. In case you want to send a message to a widget, use a send action with the message topic set to refresh.

{
    "type": "refresh",
    "id": "Place the ID of the widget here"
}

The id field can have the value self should the action pipeline need to refresh its own widget. It can also be a route expression allowing widgets embedded in nested tabs compilations to be refreshed.

Send

Widgets data exchange

Sending message from one widget to another can be done using the send action. This action does not change the output message. The output message is the same as the input message.

{
    "type": "send",
    "to": "Place the ID of the widget here"
}

The value of the to field can be set to self in case the pipeline needs to send data to its own widget. The update behavior differs per widget.

Using a route to point to a widget within a tab

{
    "type": "send",
    "to": {
        "route" : [
            "MyTabs",       // ID of the Tabs widget.
            "Tab01",        // ID of the single tab.
            "TextWidget"    // Pointing to a Text widget which has an ID of 'TextWidget` which.
        ]
    }
}

Supported topics:

  • refresh: the recipient widget will perform a refresh. (default)

  • update: the receiving widget will perform an update. This will bypass the data source action.

Refresh Topic

The recipient widget will perform a refresh. It will execute the data source action (pipeline) with the provided message payload. The refresh life cycle will be performed including a fetch if a data source is present.

Example to send a message to another component to refresh itself:

{
    "type" : "send",
    "to" : "Place the ID of the widget here",
    "message": {
        "topic": "refresh" // Can be omitted because it is default.
        "payload": {} // Can be any type of value. Typically an object is used.
    }
}

Update Topic

The recipient widget will perform an update. It only updates its known properties with the provided message payload. The update life cycle will be performed.

{
    "type" : "send",
    "to" : "Place the ID of the widget here",
    "message": {
        "topic": "update",
        "payload": {} // Can be any type of value. Typically an object is used.
    }
}

Subscribe

Subscribe to object data changes in the system. Typically used in dataSource configurations. If necessary, use the willUpdate action hook to transform the data.

{
    "type": "subscribe",
    "path": "/System/Core/Examples/Variable"
}

Switch

Execute actions based on rules. A rule will be checked by performing a queryOne transformation. If the result of the queryOne transformation is something other than null the action(s) defined in the case statement will be executed. If one rule matches, its action will be executed and further testing of the subsequent rules will be stopped.

When checkAll is set to true, the 'initial' input message of the switch will be passed to each action pipeline of the matched rules. The output message of the last executed action pipeline, will be the output message of this switch action. When no rule matches, the output of the switch action is the same as the input.

To declare a default action which applies when none of the rules match, add a extra rule at the end of the case array with an empty match condition.

{
    "type": "switch",
    "checkAll": false,
    "case": [
        {
            "match" : { // test if payload.temp == 10
                "temp": 10
            },
            "action" : { // Can be a single action or action pipeline.
                "type": "action",
                "name": "doSomething"
            }
        },
        {
            "match" : { // test if payload.temp >= 20
                "temp": {
                    "$gte": 20
                }
            },
            "action" : [ // Can be a single action or action pipeline.
                {
                    "type": "action",
                    "name": "doSomethingFirst"
                },
                {
                    "type": "action",
                    "name": "doSomethingExtra"
                }
            ]
        },
        {
            "match" : {}, // Default action
            "action" : [] // Can be a single action or action pipeline.
        }
    ]
}

In its simplest form, a match expressions tests if the named payload field equals the provided value. An explicit comparison operator can also be used as illustrated above.

Even more complex match conditions can be formulated by using the mongoDB $expr operator. For example:

{
    "match" : {
        "$expr": {
            "$lt": [ // check if payload.value < payload.maxValue
                "$value",
                "$maxValue"
            ]
        }
    }, // ...
}

TPM-OEE

This action reads table models relating to OEE monitoring from the back end. It is typically used in the dataSource of either a timeperiodtable or a table widget. Depending on the value of the subject property, the action returns widget schema, actions and data elements for specific OEE tables, which can then be edited in WebStudio.

{
    "type": "tpm-oee",
    "subject": "big-table_stops",
    "path": "/Enterprise/Site/Area-S1-A2/Cell-S1-A2-C1/OEE/Production/Equipment Stops"
}
Name Description

path

Path to an OEE object. The specific one referred to depends on the selected subject as explained below.

subject

The selected subject determines which table model to return and what object type should be referenced in the path property. The options are:

  • "big-table_stops": Return a timeperiodtable model which can be used to edit/annotate machine stops. The path property must point to the "Equipment Stops" object associated with a TPMOEEEquipmentMonitor object.

    {
        "type": "timeperiodtable",
        "name": "Time Period Table",
        "description": "Time Period Table",
        "starttime": "*-1h",
        "endtime": "*",
        "dataSource": {
            "type": "tpm-oee",
            "subject": "big-table_stops",
            "path": "/Enterprise/Site/Area/Cell/OEE/Production/Equipment Stops"
        }
    }

    The Stop reasons will be shown using the language selected in your browser matching the available translations populated in the translations tables of the TPMStopReasonConfiguration object. In other words, if you select say Spanish as your preferred browser language and the translation tables contain Spanish translations, then those translation are used. If not, the default English text is shown.

  • "big-table_production-runs": Returns a timeperiodtable model which can be used to update production quantities. The path property must point to the "Production Runs" object associated with a TPMOEEEquipmentMonitor object.

    {
        "type": "timeperiodtable",
        "name": "Time Period Table",
        "description": "Time Period Table",
        "starttime": "*-1h",
        "endtime": "*",
        "dataSource": {
            "type": "tpm-oee",
            "subject": "big-table_production-runs",
            "path": "/Enterprise/Site/Area/Cell/OEE/Production/Production Runs"
        },
    }
  • "translation-config-table_reason-range", "translation-config-table_reason", "translation-config-table_custom-reason": Return table models used to configure reason translations. The path property in each case must point to a TPMStopReasonConfiguration object.

    {
        "type": "table",
        "name": "Fixed Data",
        "description": "Fixed Data",
        "dataSource": {
            "type": "tpm-oee",
            "subject": "translation-config-table_reason-range",
            "path": "/Enterprise/Site/Custom Stop Reasons"
        },
        "captionBar": true,
        "options": {
            "editable": false,
            "multi": false,
            "pageSize": 20,
            "pagination": true,
            "allowSorting": true,
            "alternateRowColoring": true
        }
    }

    The action logic associated with the returned table ensures that only one translation row can be present for each reason code. For the built in reason coding groups, TPMReasonRangeCodesTranslation and TPMReasonCodesTranslation, this means that the available entries to select are pre-defined.

    Custom stops reason entries need to be created first in the TPMCustomStopReasonConfiguration table before the translations can be configured.

Transform

Transformation of data can be performed by means of the MongoDB Aggregation framework. The input data is normally the value of the payload field of the input message. In case the whole message object needs to be available to the transform logic you can set completeMsgObject to true.

The aggregation pipeline often returns an array of one or more elements. In most cases, pipeline actions which ingest the output of the transformation are however looking for a single object. As a convenience the WebStudio specific aggregateOne options can be used instead of aggregate. It return the first element from the resulting transformation.

Besides aggregate and aggregateOne, query and queryOne can also be used in scenarios where filtering is required. As expected queryOne returns the first matching object.

MongoDB Documentation:

Transform Example 01

This example is to transform the data to an object with a different key.

  • Input message payload:

{
    "name": "Company A",
    "location": "Eindhoven"
}
  • Transform logic:

{
    "type": "transform",
    "aggregateOne": [
        {
            "$project": {
                "company": "$name"
            }
        }
    ]
}

Output message payload:

{
    "company": "Company A"
}

Example to search for the object with 'location' value 'Cologne':

  • Input message payload:

[
    {
        "name": "Company A",
        "location": "Cologne"
    },
    {
        "name": "Company B",
        "location": "Eindhoven"
    }
]
  • Transform logic:

{
    "type": "transform",
    "aggregate": [
        {
            "$match": {
                "location": "Cologne"
            }
        }
    ]
}
  • Output message payload:

[
    {
        "name": "Company A",
        "location": "Cologne"
    }
]

Note: Even though only one element was matched, the returned massage payload is still an array. If aggregateOne were used instead of aggregate, the output would look like this:

{
    "name": "Company A",
    "location": "Cologne"
}

Transform Example 02

This example uses a query to search for the object with 'location' value of 'Cologne'.

  • Input message payload:

[
    {
        "name": "Company A",
        "location": "Cologne"
    },
    {
        "name": "Company B",
        "location": "Eindhoven"
    },
    {
        "name": "Company C",
        "location": "Cologne"
    }
]
  • Transform logic:

{
    "type": "transform",
    "query": {
        "location": "Cologne"
    }
}
  • Output message payload:

[
    {
        "name": "Company A",
        "location": "Cologne"
    },
    {
        "name": "Company C",
        "location": "Cologne"
    }
]

Transform Example 03

This example uses queryOne to search for the object with 'location' value 'Cologne'. Like aggregateOne, it returns only the first match.

  • Input message payload:

[
    {
        "name": "Company A",
        "location": "Cologne"
    },
    {
        "name": "Company B",
        "location": "Eindhoven"
    },
    {
        "name": "Company C",
        "location": "Cologne"
    }
]
  • Transform logic:

{
    "type": "transform",
    "queryOne": {
        "location": "Cologne"
    }
}
  • Output message payload:

{
    "name": "Company A",
    "location": "Cologne"
}

Wait

Wait before the next actions will be executed. duration is in milliseconds.

{
    "type": "wait",
    "duration": 1000
}

Write

Writes a value to an object in the system. The write action inspects the message payload passed to it, looking for variables v, item or items which it uses to update the object identified by the path or p property, depending on the variant of the action used.

Fixed path with "v" read from message payload

{
    "type": "write",
    "path": "/System/Core/Examples/Variable"
}
Name Description

path

Path to the object or property to update.

v

Value to write. The value is read from the payload of the incoming message. It can be a property of the payload.

{
    "payload": {
        "v": 79.0
    }
}

The value parameter can also be assign directly to the payload itself.

{
    "payload": 79.0
}

The write action assigns the payload.v or payload to the v parameter of the action.

Write parameters are provided in the "item" property

This version of the write action can be used to update any of the VQT properties of an object. Only the action type is mandatory. The item can be statically set or read from the message payload.

{
    "type": "write",
    "item": {
        "p": "/System/Core/Examples/Variable",
        "v": 215.24,
        "t": "2022-04-27T15:25:10.331Z",
        "q": 0 // Quality is GOOD
    }
}
Name Description

item

Details of item to be written to. The property is usually not explicitly configured, be read from the payload field of the incoming message.

p

Path to the object or property to update.

v

Value to write.

t

If the timestamp field t is omitted, the current time is assumed. If present t must be expressed as an ISO 8601 time string.

q

The OPC quality value can be omitted in most cases in which case 0 (GOOD) is assumed.

if the write was successful, the message is updated with the values written.

{
    "payload": {
        "i": 281474983198720,
        "p": "/System/Core/Examples/Variable",
        "v": 215.24,
        "q": 0,
        "t": "2022-04-27T15:25:10.331Z",
    }
}

Write parameters are provided in the "items" property

This variant of the write action is very similar to the previous one, except that instead of a single item, an array of items is provided. The items can be statically configured or read from the message payload.

{
    "type": "write",
    "items": [
        {
            "p": "/System/Core/Examples/Variable",
            "v": 215.24,
            "t": "2022-04-27T15:25:10.331Z",
            "q": 0 // Quality is GOOD
        },
        {
            "p": "/System/Core/Examples/Variable",
            "v": 215.24,
            "t": "2022-04-27T15:26:13.331Z",
            "q": 0 // Quality is GOOD
        }
    ]
}