# Changes to Expr in Structure 7

Structure 7.0 brings major improvements to the Expr language, making it an order of magnitude more powerful. This page lists the main additions to the language and the function library and is intended for the users of Structure 6 and earlier, already familiar with Expr.

## New Features

### Arrays

Expr now supports arrays, or lists of values. For example, the Fix Versions field may contain multiple values - in Structure 7.0, there are functions that pull out specific values from these fields, does an operation to every value, and more!

We've also added several new functions to work with arrays: Array Functions

### Properties

Formulas can now get the value of a particular property of an item using the following notation: `object.property`

`fixVersion.releaseDate`

This returns the release date for the fixVersion.

You can also string multiple property calls together:

`project.lead.email`

This returns the email address of the lead for the project.

For a complete list of supported properties, see Item Property Reference.

### Chained Function Calls

This new way to call a function allows you to conveniently apply a sequence of functions to a value by listing each function one after the other, separated by a ( . ) dot.

- Old method:
`F3(F2(F1(x)))`

- New method:
`x.F1().F2().F3()`

With this new method, the value that comes before the dot becomes the first argument for the function. If the function takes multiple arguments, the rest of the arguments must be written in the parentheses.

For example:

`created.FORMAT_DATETIME("yyyy").CONCAT(" year issue")`

* If the issue was created in 2021, the result would be:* "2021 year issue"

The chain method can be applied to any expression and any function - including User Functions and Arrays: `ARRAY(1, 2, 3).MAX().SQR()`

### User Functions

A User Function allows you to define a locally-used function within a formula. User functions can be defined in a similar manner as local variables:

```
WITH square(x) = x * x :
square(impactField) / square(storyPoints)
```

In this example, the user function is given a name ("square") and then used to perform the same calculation on multiple fields. To learn more, see the language reference.

#### User Functions for Arrays - introducing the "$" character

When you need to perform an operation on each element in an array, you can use “$” to indicate each element in the array.

For example, if you want to filter work logs based on whether the author is the current user, you could write:

`worklogs.FILTER($.author = ME())`

In this formula, Structure replaces "$" with each element of the array and calculates the value of "$.author = ME()" expression - if the author is the current user, it returns true and that work log will be included in the FILTER results.

This method becomes very powerful when you combine multiple user functions together. To learn more, see the language reference.

### Embedded Queries

It is now possible to include JQL and S-JQL (our own Structured JQL) within a formula:

```
JQL { assignee = currentUser() }
SJQL { leaf and child of [ assignee = currentUser() ] }
```

The result will be a boolean value – true (number 1) if the issue (that the formula is calculated for) satisfies the query.

Since JQL is a Jira-based query, it will work only on issues; the result will be false (`0)`

on other types of items. S-JQL can be used for more complex queries applicable to the whole structure.

For example:

```
// Collect total story points from all sub-issues assigned to members of Team2 group, unless the stories are under folder "Special"
SUM {
IF JQL { assignee in membersOf("Team2") } :
IF NOT SJQL { descendant of folder("Special") } :
storyPoints
}
```

### Enhanced IF Expression

It is now possible to create complex IF statements, as well as IF/ELSE statements within a formula.

```
WITH total = x + y :
IF total > 0 :
x / total
ELSE : error
```

*Note: the ":" after "ELSE" is optional – in the example above, we've included it for readability.*

### Concatenation Operator

The operator CONCAT allows you to join two text strings together:

```
issueLink.source
CONCAT
" depends on "
CONCAT
issueLink.destination
```

*If the link.source is issue PR-111 and the link.destination is PR-231, the above code would return: *`PR-111 depends on PR-231`

### Text Snippets

Text Snippets allow you to generate texts using variables and expression. This is particularly helpful in formulas that utilize wiki markup.

When using text snippets:

- The snippet should be enclosed with
`"""`

- It may be multi-line
is replaced with the value of a variable var`$var`

is replaced with the value of the expression`${expression}`

```
""" $var1 + $var2 = ${var1 + var2} """
""" this $glass is half-${
IF optimist:
'full' ELSE: 'empty'} """
```

## New Functions

The following functions have been added or updated in Structure 7.0. For details about each function and how to use them, see Expr Function Reference.

### Array Functions

- ARRAY - Creates an array from a list of elements.
- GET - Retrieves an element from an array based on its index. Array indexes are 0-based. Returns undefined if index is out of array bounds.
- UNIQUE - Removes duplicates from the array. The order of non-duplicate elements is preserved.
- COMPACT - Removes all undefined values from the array.
- FLATTEN - Given an array of arrays, makes a one-step flattening.
- RECURSIVE_FLATTEN - Performs recursive flattening and compacting of the array, producing an array guaranteed to be flat and not contain undefined values.
- REVERSE - Reverses the order of elements in the array.
- FILTER - Filters the array so that it retains only elements for which a specified user function returns a truthy value.
- MAP - Applies a user function to every element of the array.
- SORT - Produces a sorted array.
- SORT_BY - Sorts the array by comparing the values produced by calling a user function for each array element.
- REDUCE - Reduces an array to a single value.
- GROUP - Groups array elements into buckets based on the value produced by a user function.
- JOIN - Produces a text representing the array by converting each element to text and joining them together.
- CONTAINS - Returns true (1) if the array contains a specified element.
- ANY - Checks if any of the array elements satisfies a condition.
- ALL - Checks if all of the array elements satisfy a condition.
- NONE - Checks that none of the array elements satisfy a condition.
- SEQUENCE - Creates an array of sequential integer numbers.
- INDEXES - Creates an array of indexes of another array.
- SIZE - Returns the number of elements in an array.
- MERGE_ARRAYS - Produces a single array with the elements of all parameter arrays.
- SUM - Produces a total of all numeric elements in the array.
- MUL - Produces the product of all numeric elements in the array.
- SUBARRAY - Produces an array with some elements from the given array.
- CONTAINS_ALL - Checks if one array contains all elements of another array.
- CONTAINS_ANY - Checks if one array contains at least one element from another array.
- INDEX_OF / LAST_INDEX_OF - Returns the index of a first/last occurrence of an element in an array.
- FIRST / LAST - Returns first/last element of the array.
- WITHOUT - Returns the input array with all elements except those equal to a specified value.
- IS_EMPTY - Checks if array is empty.

### Statistical Functions

- MIN/MAX - When used with multiple arguments, MIN/MAX find the smallest / largest value among the numbers passed as parameters. When used with an array, finds the minimum/maximum number in the array.
- UMIN/UMAX - Same as MIN/MAX, but does a universal comparison, accepting any type of values, including entities.
- UMIN_BY/UMAX_BY - Returns the minimum/maximum by comparing them using values calculated by calling a user function for each element.
- PERCENTILE - Calculates Nth percentile of the values in the given array.
- MEDIAN - Calculates a median value of an array of numbers.
- QUARTILE - Calculates a quartile value of an array of numbers.
- AVERAGE - Calculates an average of the numbers in the array.
- STDEV/STDEVP - Calculates standard deviation (based on a sample or on entire population).

### Other Functions

- URL_ENCODE - Translates a text into application/x-www-form-urlencoded format.
- URL_DECODE - Decodes an application/x-www-form-urlencoded text.
- SPLIT - Produces an array from a text by splitting it using a separator.
- ACCESS - Tries to access a property of an item.

### Aggregate Functions

The following Aggregate functions have been added to work with the new features and functions introduced in Structure 7.0. See Aggregate Function Reference for more detail.

- ARRAY { } - Calculates an expression for each sub-issue (or other set of rows as defined by the modifiers), and collects the values in an array.
- VALUES { } - Same as ARRAY { }, but removes duplicates, undefined values, and expands (flattens) the array values produced by the inner expression. For example,
would produce a list of all fix versions set for the sub-issues.`VALUES { fixVersion }`

- MEDIAN { }, PERCENTILE { }, and other - calculates a numerical expression for each sub-issue (or another set of issues), and then calculates the corresponding statistical value.

#### Changes and Notes for Existing Aggregate Functions

The following changes have been made to the existing aggregate functions because in Structure 7.0 the inner expression (the one inside curly braces) can now produce new types – arrays, items, key-value maps and user functions.

Aggregate Function | Function | Change in Structure 7.0 | Expr: Array | Expr: Item | Expr: Key-Value Map |
---|---|---|---|---|---|

SUM | Sums the result of the inner expression for each subject row. If #distinct is used, does not count the same | If the inner expression produces an array, sums elements of that array. | Sums elements of the array. | Error | Error |

MIN | Finds a minimum value of expr calculated for each row. | If expr produces an array, finds the minimum element within that array. | Uses minimum value from the array. | Uses items's order. | Sorted as if it was an undefined value |

MAX | Finds a maximum value of expr calculated for each row. | If expr produces an array, finds the maximum element within that array. | Uses maximum value from the array. | Uses items's order. | Sorted as if it was an undefined value |

JOIN | Creates a string representation of values produced by expr, joined together as a list or hierarchically. If #distinct is used, does not include the same | If expr produces an array, joins elements of that array. | Uses conversion to Text/Joined. | Uses conversion to Text/Joined. | Error |

*Note: If the inner expression produces a user function, these will always result in an error.*

## Backward Compatibility

Some of the enhancements we've made required us to change the way formulas work with certain items or characters. Because of this, some old formulas may need to be updated, as they will either no longer work with Structure 7, or will cause an unexpected result.

Please review the following chart and update your existing formulas as necessary.

Change | Sample Formula | Result Before Structure 7.0 | Result After Structure 7.0 |
---|---|---|---|

Dot character (".") is no longer a valid character for a variable. | WITH my.data = 5 : ... CONCAT("user", user.name) | valid calculation | parse error user.name undefined |

TRIM() function on array values will trim each element. |
TRIM(fixVersion) | "v1 , v2"
| "v1, v2" Or ["v1", "v2"] when treated as an array |

MIN / MAX functions now require specific parameters; otherwise, they can now result in errors. | min(1, "aha!") | 1 | "VALUE?" error |

Third party attribute loaders that provide values in any format except TEXT, DURATION, TIME, NUMBER, BOOLEAN will not be loaded by formula anymore. The possibility to add it via variable selector stands. | x x is bound to a loader with an unsupported format. | text representation of the loaded value | undefined value |

Formula errors are now propagated through aggregations | join { 1/0 } | "?" | "DIV/0" error |

We introduced execution limits. Formulas that require too many calculation steps will return an error because they will take too long to process | overly-complex formula | valid calculation | "LIMIT!" error |

Array size limit may influence variables usage and join#distinct | fixVersions join#subtree#distinct{ key } | valid calculation | "ARRAY!" error |

Join#distinct functions a little differently. Previously, in the example to the right, it would have interpreted the two sets of labels as "a, b" and "b, c" - now it's able to identify the individual labels within each set ( "a" and "b" and "b" and "c"). | join#distinct{labels}, labels are (a, b) and (b, c) | a, b, b, c | a, b, c |

Previously it was possible to write one field to another using an effector, even if the parameters did not match. For example, you could write a resolution to a status. This is no longer possible, if types do not coincide. Workaround - In some cases, this will still work if you wrap the field to change its type: instead of "resolution" use "text(resolution)". | resolution | attempts to write the value | an error |