In this article:
Introduction - Overview of the Sensor Action Expression Language
Precedence - Controlling the order of execution
Operations - Overview of the available operations in the SAEL
Operation User Guide - Detailed breakdown of the usage of each operation
Further Information - Where to go for further information
Introduction
The Sensor Action Expression Language is used to execute sensors and actions in multiple places throughout the platform, including workflow activities, and many areas of the OneSite Patch product.
This guide focuses on the syntax and features of the language, as well as how to construct Sensor Action Expressions.
Precedence
When constructing Sensor Action Expressions, the expression will calculate the order of evaluation based on the operation being performed. The operations listed below are shown in ascending level of precedence.
The precedence can be modified by the user by using paratheses to change the order in which the operators will be evaluated.
For example, the below expression will evaluate to 3 because the multiplication is evaluated before the addition (1*2 = 2, + 1 = 3)
1 + 1 * 2
Whereas, the below expression will evaluate to 4, because the parenthesis force the addition to be evaluated first (1+1=2, * 2 = 4)
(1 + 1) * 2
Operations
The following list of operations is shown in order of precedence as per above.
Operation | Description | Usage Example |
! | Logical Complement (unary) "NOT" | !(true) |
- | Unary Minus | +2 |
+ | Unary Plus | -2 |
Property() | Property Retrieval Operator | Property(\"General.IsBugFix\") |
[] | Row Array Indexing | Sensor(123)[2] |
. | Column Selection | Sensor(123).StatusCode |
/ | Division | 4/2 |
* | Multiplication | 2*4 |
% | Modulus (Remainder) | 4%2 |
- | Subtraction | 4-2 |
+ | Addition | 2+4 |
== | Equal To | 4==2 |
!= | Not Equal To | 2!=4 |
> | Greater Than | 4>2 |
>= | Greater Than or Equal | 4>=2 |
< | Less Than | 2<4 |
<= | Less Than or Equal | 2<=4 |
& | Bitwise AND | 2&4 |
| | Bitwise OR | 2|4 |
^ | Bitwise XOR | 2^4 |
&& | Short-circuited AND | 2<4 && 4>2 |
|| | Short-circuited OR | 2<4 || 4>2 |
= | Parameter Set Operation | "FilePath"="C:\\temp\\file.txt" |
Sensor() | Sensor Operation | Sensor(123) |
SensorVolatile() | Volatile Sensor Operation | SensorVolatile(123) |
Action() | Action Operation | Action(456) |
Filter() | Filter Operation | Filter(Sensor(123), "C:\\temp", "Select \"FileName\" from $ROWS WHERE \"SizeInBytes" > 50") |
IfMatches() | IfMatches Operation | IfMatches("abc",".*","xyz") |
IfEquals() | IfEquals Operation | IfEquals("abc","bcd","def") |
IfBlank() | IfBlank Operation | IfBlank("","abc","xyz") |
IfTrue() | IfTrue Operation | IfTrue("true","abc","xyz") |
CharAt() | Character At Position | CharAt("abc",0) |
CompareTo() | Compare Strings | CompareTo("abc","xyz") |
EndsWith() | String Ends With | EndsWith("abc","bc") |
IndexOf() | Index Of String in String | IndexOf("abc","bc") |
LastIndexOf() | Last Index of String in String | LastIndexOf("abccba","b") |
Length() | Length of String | Length("abc") |
MatchesRegex() | String Matches Regular Expression | MatchesRegEx("abc","a.*") |
Replace() | String Replace | Replace("abc","bc","aa") |
StartsWith() | String Starts With | StartsWith("abc","a") |
Contains() | String Contains | Contains("abc","b") |
Lowercase() | Convert to Lowercase | Lowercase("ABC") |
Uppercase() | Convert to Uppercase | Uppercase("abc") |
Trim() | String Trim | Trim(" abc ") |
Substring() | Part of String | Substring("abcdef",1,3) |
EqualIgnoreCase() | Case Insensitive String Compare | EqualIgnoreCase("abc","ABC") |
GetRegexMatch() | RegEx Retrieval |
GetRegexMatch("abc","b.*") |
Operation Usage Guide
Logical Complement ( ! )
This unary operator takes a single Boolean (true/false) argument and reverse it's value.
Example:
The following expression evaluates to TRUE
Sensor(123).Exists
Therefore next expression evaluates to FALSE
!Sensor(123).Exists
Unary Plus ( + )
This unary operator turns the sign of a Whole Number or Fractional Number to positive
Example:
+99
Unary Minus ( - )
This unary operator turns the sign of a Whole Number or Fractional Number to negative
Example:
-99
Property Retrieval Operator ( Property() )
This operator returns the Text property value of the given property
Example:
The following property (MyProperty) has a value of "MyValue"
Property(\"MyProperty\")
The data returned would be a string containing:
MyValue
Note the escape characters used to escape the double-quotes in the Property Name
Row Array Indexing ( [ ] )
This operator can only be used on a SensorActionExecutionResult to index the output rows returned by executing a sensor or action operation.
It uses 0-based indexing, placing the first element at index 0, second at index 1, etc.
Example:
This expression would execute Sensor with ID 123 and return the first row of it's output.
Sensor(123)[0]
Column Selection ( . )
This operator returns the column value of a Row, Array of Rows, or SensorActionExecutionResult
In the case of Array of Rows, or SensorActionExecutionResult, the column of the first output row is selected.
Column selection can also be used to extract the following values from a SensorActionExecutionResult (the result of a Sensor() or Action() operation):
StatusCode | Whole Number | 0=Success, 1=Failure, 2=Cancelled, 3=In Progress |
ReasonCode | Whole Number | 0=Success, Else Reason Code for Failure |
ReasonText | Text | Reason why the sensor/action succeeded or failed |
OutputRows | Array of Rows | Array of rows output by the Sensor/Action |
Column selection can also be used to extract the following values from a SensorActionExecutionResult (the result of a Sensor() or Action() operation) OR an Array of Rows:
RowCount | Whole Number | Number of output rows returned by the Sensor/Action |
Examples:
The following expression executes sensor with ID 123 and returns the StatusCode:
Sensor(123).StatusCode
The following expression executes sensor with ID 123, filters the results to only those rows where the size property is greater tha 50, and returns the number of rows.
Filter(Sensor(123),\"Select * from $ROWS where \"size\" > 50\").RowCount
Note the escape characters used to escape the double quotes around the query text and the size property name.
Division ( / )
Used to perform division.
Example:
The following example would return the result of 12 divided by 3 = 4
12 / 3
Multiplication ( * )
Used to perform multiplication.
Example:
The following example would return the result of 3 multiplied by 4 = 12
3 * 4
Modulus (Remainder) ( % )
The % operator performs the modulus operation, taking the remainder when dividing whole numbers
Example:
The following example would return the remainder of 17 divided by 5 = 2 (17/5= 3 with 2 remaining)
17 % 5
Subtraction ( - )
Used to subtract two numbers, whether they are whole numbers or fractional numbers
Example:
Both of the two examples below would return 5
15 - 10
15.5 - 10.5
Addition ( + )
Used to add two numbers together.
Example:
The following example would return 1337
961 + 376
Equal To ( == )
This operator is used to compare the equality of two values and returns a Boolean to indicate whether they are equal
Example:
The following example would return true:
(2 + 4) == (4 + 2)
The following example would return false:
(2 + 2) == (4 + 4)
Not Equal To ( != )
This operator is used to compare the equality of two values and returns a Boolean to indicate whether they are NOT equal
Example:
The following example would return false:
(2 + 4) != (4 + 2)
The following example would return true:
(2 + 2) != (4 + 4)
Greater Than ( > )
This operator is used to compare two values by checking if the value on the left hand side of the equation is greater than the value on the right hand side of the equation
Example:
The following example would return true because 10 is greater than 8:
10 > 8
Great Than or Equal ( >= )
This operator is used to compare two values by checking if the value on the left hand side of the equation is greater than or equal to the value on the right hand side of the equation
Example:
The following example would return true because 10 is greater than 8:
10 >= 8
The following example would also return true because 10 is equal to 8 + 2.
10 >= (8 + 2)
Less Than ( < )
This operator is used to compare two values by checking if the value on the left hand side of the equation is less than the value on the right hand side of the equation
Example:
The following example would return false because 10 is greater than 8:
10 < 8
Less Than or Equal ( <= )
This operator is used to compare two values by checking if the value on the left hand side of the equation is less than or equal to the value on the right hand side of the equation
Example:
The following example would return false because 10 is greater than 8:
10 <= 8
The following example would return true because 10 is equal to 8 + 2.
10 <= (8 + 2)
Bitwise AND ( & )
The Bitwise AND is used to perform a bitwise AND operation between corresponding bits of two operands. It returns 1 if both bits are 1, and 0 otherwise.
Example:
The following example would calculate 12 & 7 (1100 & 0111 in binary) and return 0100 (4 in decimal)
12 & 7
Bitwise OR ( | )
The Bitwise OR is used to perform a bitwise OR operation between corresponding bits of two operands. It returns 1 if at least one of the bits is 1, and 0 otherwise.
Example:
The following example would calculate 12 | 7 (1100 | 0111 in binary) and return 1111 (15 in decimal)
12 | 7
Bitwise XOR ( ^ )
This Bitwise XOR is used to perform a bitwise XOR operation between corresponding bits of two operands. It returns 1 if the bits are different (one is 0 and the other is 1), and 0 if the bits are the same.
Example:
The following example would calculate 12 ^ 7 (1100 ^ 0111 in binary) and return 1011 (11 in decimal)
12 ^ 7
Short-circuited AND ( && )
Takes two Boolean arguments and evaluates them into a single Boolean
The expression evaluates to true only if BOTH Boolean values are true.
If the left hand side of the equation evaluates to false, then the right hand side of the equation is not evaluated.
Example:
The following example evaluates to true because 2+2 does equal 4, and 4/2 does equal 2.
(2+2=4) && (4/2=2)
The following example evaluates to false, because only the left hand side of the equation evaluates to true. The right hand side is not evaluated.
(2+2=4) && (4/2=12)
Short-circuited OR ( || )
Takes two Boolean arguments and evaluates them into a single Boolean
The expression evaluates to true if EITHER Boolean values are true.
If the left hand side of the equation evaluates to true, then the right hand side of the equation is not evaluated.
Example:
The following example evaluates to true because 2+2 does equal 4. The right hand side is not evaluated.
(2+2=4) || (4/2=12)
The following example evaluates to false, because both the left hand side of the equation and the right hand side of the equation evaluate to false.
(2+2=8) || (4/2=12)
Parameter Set Operation ( = )
This operator should only be used when specifying a parameter of a Sensor/Action Operation.
In place of an actual value, you may define the input row column name and value you want to set for the input of this sensor/action operation
Example:
The following example sets the FilePath input column to the value "C:\\temp\\file.txt"
"FilePath"="C:\\temp\\file.txt"
Note the escape characters for the backslashes in the file path.
Sensor Operation ( Sensor() )
Executes the specified sensor with the specified input parameters.
The Sensor can be specified using ID or Name.
The difference between Sensor() and SensorVolatile() is that Sensor() caches its results for future calls to the same sensor, while SensorVolatile() does not.
This operation has a variable number of inputs, depending on the sensor that is being executed.
Sensors that do not have input parameters only require the sensor ID or name.
Syntax:
Sensor(<Sensor>, <input column 1>, <input column 2>, ..., <input column n>)
Examples:
Sensor by ID: Sensor ID is 123 and it takes one FileName parameter:
Sensor(123, "C:\\temp\\test.txt")
Sensor by Name: Sensor name is "FileDetails" and it takes one FileName parameter:
Sensor("FileDetails", "C:\\temp\\test.txt")
You may optionally specify a maximum number of rows to be returned by the sensor by specifying the number after the operation name, but before the opening parenthesis.
Example:
The following example will execute a sensor with ID 123, with no parameters, and return only the first 10 rows:
Sensor:10(123)
If a Sensor contains non-mandatory input parameters, you may leave them blank when calling the operation, but you MUST leave an empty space for the parameter.
Example:
The following example will execute a sensor with ID 123 that takes 3 parameters. The first one has been specified, the last two have been left blank:
Sensor(123,"Test",,)
Volatile Sensor Operation ( SensorVolatile() )
Executes the specified sensor with the specified input parameters.
The Sensor can be specified using ID or Name.
The difference between Sensor() and SensorVolatile() is that Sensor() caches its results for future calls to the same sensor, while SensorVolatile() does not.
This operation has a variable number of inputs, depending on the sensor that is being executed.
Sensors that do not have input parameters only require the sensor ID or name.
Syntax:
SensorVolatile(<Sensor>, <input column 1>, <input column 2>, ..., <input column n>)
Examples:
Sensor by ID: Sensor ID is 123 and it takes one FileName parameter:
SensorVolatile(123, "C:\\temp\\test.txt")
Sensor by Name: Sensor name is "FileDetails" and it takes one FileName parameter:
SensorVolatile("FileDetails", "C:\\temp\\test.txt")
You may optionally specify a maximum number of rows to be returned by the sensor by specifying the number after the operation name, but before the opening parenthesis.
Example:
The following example will execute a sensor with ID 123, with no parameters, and return only the first 10 rows:
SensorVolatile:10(123)
If a Sensor contains non-mandatory input parameters, you may leave them blank when calling the operation, but you MUST leave an empty space for the parameter.
Example:
The following example will execute a sensor with ID 123 that takes 3 parameters. The first one has been specified, the last two have been left blank:
SensorVolatile(123,"Test",,)
Action Operation ( Action() )
Executes the specified Action with the specified input parameters.
The Action can be specified using ID or Name.
This operation has a variable number of inputs, depending on the Action that is being executed.
Actions that do not have input parameters only require the Action ID or name.
Syntax:
Action(<Action>, <input column 1>, <input column 2>, ..., <input column n>)
Examples:
This example executes an action with ID 123 and no input parameters:
Action(123)
This example executes an action with ID 123, with the "FilePath" parameter set to "C:\\temp", and the 2nd parameter not specified:
Action(123, "FilePath"="C:\\temp",)
Filter Operation ( Filter() )
Filters the rows of a SensorActionExecutionResult or an Array of Rows based on a specified SQL query, returning an array of rows that result from the query's output
Note: The subset of SQL that is used for this operation is known as HQL (Hibernate Query Language), which may have some small differences to other SQL implementations.
Syntax:
Filter(<output rows>, <query>)
The specified SQL query MUST use $ROWS in place of a table name
e.g.:
SELECT * FROM $ROWS
The specified SQL query must surround the column names with quotes, which must be escaped using backslash.
e.g.:
SELECT \"FileName\" FROM $ROWS WHERE \"SizeInBytes\" > 50
Example:
The following example would execute Sensor with ID 123 and input parameter FilePath set to "C:\\temp", then filter it's output based on the size in bytes.
If the size in bytes is greater than 50, a new Array of Rows is returned with a single column: "FileName"
Filter(Sensor(123, "C:\\temp"), "SELECT \"FileName\" FROM $ROWS WHERE \"SizeInBytes\" > 50")
IfMatches Operation ( IfMatches() )
Evaluates to the respective replacement text depending on if the input text matches the provided regular expression pattern.
Syntax:
IfMatches(<input text>, <pattern>, <replacement text if matches>)
The value for pattern must be a valid regular expression.
Example:
The following example will evaluate the input text "Adaptiva" against the regular expression "Ada.*". Because the regular expression Ada.* will return true for any string that starts with "Ada", the expression will replace the input text with the replacement text of "Avitpada". The output of the expression is therefore Avitpada
IfMatches("Adaptiva", "Ada.*", "Avitpada")
IfEquals Operation ( IfEquals() )
Evaluates to the respective replacement text depending on if the input text is equal to the provided value.
Syntax:
IfEquals(<input text>, <value text>, <text replacement if equal>, <replacement text if not equal>)
Example:
The following example will evaluate the input text from the Name column of Sensor 123 against the provided text "Adaptiva". If the Name column of the first row from Sensor 123 equals "Adaptiva" then the expression will return "ADA1". If it does not equal "Adaptiva" then it will return "ADA2".
IfEquals(Sensor(123).Name, "Adaptiva", "ADA1", "ADA2")
IfBlank Operation ( IfBlank() )
Evaluates to the respective replacement text depending on if the input text is blank.
Syntax:
IfBlank(<input text>, <text replacement if blank>, <replacement text if not blank>)
Example:
The following example will evaluate the input text from the Name column of Sensor 123. If the Name column of the first row from Sensor 123 is blank then the expression will return "ADA1". If it is not blank then it will return "ADA2".
IfBlank(Sensor(123).Name, "ADA1", "ADA2")
IfTrue Operation ( IfTrue() )
Evaluates to the respective replacement text depending on if the input text is equal to "true".
Syntax:
IfTrue(<input text>, <text replacement if blank>, <replacement text if not blank>)
Example:
The following example will evaluate the input text from the Name column of Sensor 123. If the Name column of the first row from Sensor 123 is equal to "true" then the expression will return "ADA1". If it is not equal to "true" then it will return "ADA2".
IfTrue(Sensor(123).Name, "ADA1", "ADA2")
Character At Position ( CharAt() )
Evaluates to a length 1 string of the character at the given index in the input text
The method is zero-indexed so the retrieval begins at character 0.
Example:
In this example, the character at position 1 (the 2nd character) is retrieved from the string "abc".
This would return b.
CharAt("abc",1)
Compare Strings ( CompareTo() )
Lexicographically compares two text strings, returning a positive number if the first is greater than the second, a negative number if the second is greater than the first, or 0 if they are equal.
Examples:
In this example, because "apple" would come before "banana" lexicographically, a result of -1 would be returned
CompareTo("apple", "banana")
In this example, because the comparison has been swapped, a result of +1 would be returned
CompareTo("banana", "apple")
In this example, because both text strings are the same, then a result of 0 would be returned:
CompareTo("apple", "apple")
String Ends With ( EndsWith() )
Returns true if the given input text ends with the given suffix
Example:
In this example, the expression would return true because the input string "Adaptiva" ends with "iva"
EndsWith("Adaptiva", "iva")
Index Of String in String ( IndexOf() )
Returns the starting index of the FIRST occurrence of the specified index text inside of the input, or -1 if it does not appear.
The resulting value is 0-indexed, so if the index text is found at the start of the input, the resulting value will be 0.
Examples:
In this example, the text "Ada" can be found at the start of the input "Adaptiva" and so the expression would return 0
IndexOf("Adaptiva", "Ada")
In this example, the text "apt" is found at the 3rd to 6th characters in the input "Adaptiva" and so the expression would return 2
IndexOf("Adaptiva", "apt")
Last Index of String in String ( LastIndexOf() )
Returns the starting index of the LAST occurrence of the specified index text inside of the input, or -1 if it does not appear.
The resulting value is 0-indexed, so if the index text is only found at the start of the input, the resulting value will be 0.
Examples:
In this example, the text "Ada" can be found at the start of the input "Adaptiva" and so the expression would return 0
IndexOf("Adaptiva", "Ada")
In this example, the text "a" is found at the first, third, and eighth characters in "Adaptiva" and so the expression would return 7 (the last index of 'a')
IndexOf("Adaptiva", "a")
Length of String ( Length() )
Returns the length of the input text
Example:
The length of the word Adaptiva is 8 characters, so the expression would return 8
Length("Adaptiva")
String Matches Regular Expression ( MatchesRegex() )
Returns true if the given input matches the given regular expression
Example:
In this example, the expression would return true because the regular expression ".*apt.*" (meaning any combination of characters, followed by apt, followed by any combination of characters) is found inside the input text "Adaptiva"
MatchesRegex("Adaptiva", ".*apt.*")
String Replace ( Replace() )
Replaces all occurrences of the regular expression in the input text with the specified replacement text, returning the resultant string
Example:
In this example, the regular expression looks for the letters pti followed by any combination of characters, and if found, replaces them with mSandler. As the input text "Adaptiva" contains "pti" and other characters, the expression would output AdamSandler
Replace("Adaptiva", "pti.*", "mSandler")
String Starts With ( StartsWith() )
Returns true if the given input text starts with the given prefix
Example:
In this example, the expression would return true because the input string "Adaptiva" starts with "Ada"
StartsWith("Adaptiva", "Ada")
String Contains ( Contains() )
Returns true if the given input text contains the given sub text
Example:
In this example, the expression would return true because the input string "Adaptiva" contains "iv"
Contains("Adaptiva", "iv")
Convert to Lowercase ( Lowercase() )
Converts the specified input string to lowercase
Examples:
All of the below examples would return "adaptiva" in lowercase.
Lowercase("Adaptiva")
Lowercase("ADAPTIVA")
Lowercase("AdApTiVa")
Convert to Uppercase ( Uppercase() )
Converts the specified input string to uppercase
Examples:
All of the below examples would return "ADAPTIVA" in uppercase.
Lowercase("Adaptiva")
Lowercase("adaptiva")
Lowercase("AdApTiVa")
String Trim ( Trim() )
Removes leading and trailing whitespace such as tab, space, and carriage return characters, from the specified input string
Example:
In this example, all whitespace is removed and the expression would return "Adaptiva"
Trim(" Adaptiva ")
Part of String ( Substring() )
Returns part of the specified input string, from the start index (inclusive) to the end index (exclusive)
Both the start index and end index are zero-indexed, meaning to start at the beginning of the string, you would use an index of 0.
Examples:
In this example, the start index is 0 and the end index is 5, so the result will be abcde
Substring("abcdefg", 0, 5)
In this example, the start index is 1 and the end index is 5, so the result will be bcde
Substring("abcdefg", 1, 5)
Case Insensitive String Compare ( EqualIgnoreCase() )
Returns true if the specified input text matches the other text, ignoring character casing
Example:
In this example, the expression would return true because AdAptiVA and Adaptiva are the same, ignoring the character casing.
EqualIgnoreCase("AdAptiVA", "Adaptiva")
RegEx Retrieval ( GetRegExMatch() )
Returns the first substring of the specified input text that matches the given regular expression
Example:
In this example, the expression would return "daptiva" because it is the first instance where the regular expression "dap.*" can be found on the string "Adaptiva"
GetRegexMatch("Adaptiva", "dap.*)
Example Expressions
Return the Size property of a sensor with ID 123 that passes in a parameter for FilePath
Sensor(123, "C:\\temp\\test.txt").Size
Execute Sensor 123 to retrieve ClientFolderPath and pass that as the input parameter to Action 987 and return either true or false to indicate whether the StatusCode returned by the action is equal to 0.
Action(987, Sensor(123).ClientFolderPath).StatusCode == 0)
Execute Sensor 123, passing in "C:\\temp" as the Sensor's input property, and then filter the results to show the Name column for only those rows where the IsDirectory column is not true (i.e. files not directories).
Filter(Sensor(123, "C:\\temp"), "SELECT \"Name\" FROM $ROWS WHERE NOT \"IsDirectory\"")
Execute two Actions in a single expression.
The first Action (123) takes 3 parameters, only the first is specified ("C:\\temp\\dirOne").
The second Action (456) takes only a single parameter ("C:\\temp\\myfile.txt").
The first Action is executed and the StatusCode checked. If the StatusCode is 0 then the second Action is executed and the StatusCode checked. If that StatusCode is also 0 then the overall expression evaluates to true. If the second Action StatusCode is no 0, then the overall expression evaluates to false.
Action(123, "C:\\temp\\dirOne",,).StatusCode == 0 && Action(456, "C:\\temp\\myFile.txt").StatusCode == 0
Execute Sensor with ID 123 and limit the results to the first 50 rows.
This would be useful if the Sensor is likely to return a large number of rows such as the example, which could be a sensor that recursively returns all files in the specified folder.
Sensor:50(123, "C:\\")
Notes
1) When specifying filters for filter queries, any column names referenced must be surrounded by quotes, requiring the use of escape characters within the query itself.
Example:
SELECT \"Name\" FROM $ROWS WHERE NOT \"IsDirectory\"
2) When chaining Boolean values with the && and || operators, they will group the entire right hand side of the operator as a single expression
Example:
A && B || C && D
is the equivalent to:
(A && (B || (C && D)))
To avoid this automatic grouping, you can explicitly define parentheses where appropriate
3) To check if a Sensor or Action has successfully executed, check "StatusCode == 0"
4) To check if a Sensor or Action has failed to execute, check "StatusCode != 0"
5) When specifying a text value in the expression language, there are two special characters that must be escaped:
* and \
Example:
The follow text:
"Hello" \ World
Would need to be expressed as:
\"Hello\" \\ World
6) When writing an expression as a text value itself ("Sensor(123)" instead of Sensor(123)), double-escaping must be used to escape the quotations and backslashes in text values.
Example:
Filter(Sensor(123), \"SELECT * FROM $ROWS WHERE \\\"Size\\\" > 50\")
Notice how \\\"Size\\\" had to be double-escaped since it was within the text input for the filter operation.
Further Information
For further information, please see the other resources in the Technical Reference Library or speak to a member of Adaptiva Support.
If you experience any issues or suspect there is a bug with the Sensor and Action Expression Language, please log a support ticket and a member of the Adaptiva support team will be touch as soon as possible.
Comments
0 comments
Please sign in to leave a comment.