# 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
}
]
}``````