Rate this page:
Jira expressions is a domain-specific language designed with Jira in mind, evaluated on the Jira Cloud side. It can be used to evaluate custom code in the context of Jira entities.
Several REST API operations and app modules make use of Jira expressions:
The REST API operation to evaluate expressions can be used to test expressions. The operation can also be a useful way to retrieve data. For example, you can build an efficient and lightweight view of issue comments by fetching a small set of data (ID, author, and the content excerpt) with this expression:
1 2 3 4 5
issue.comments.map(c => {
id: c.id,
author: c.author.displayName,
excerpt: c.body.plainText.slice(0, 200) + '...'
})
The web condition for Connect modules provides extensive control over the visibility of web elements. For example, use the following expression to show your web panel only if the current user commented on the issue:
1 2 3
issue.comments
.filter(c => c.author.accountId == user.accountId)
.length > 0
Jira expressions follow JavaScript syntax. You can think of them as a JavaScript dialect.
The following constructs are supported (this list may not be exhaustive):
Static and computed member access
Static member access is used when you want to access an object's field and know the field's name at the time of writing the expression.
For example, issue.key
is an expression that accesses the key
field from issue
.
Computed member access is used when you want to dynamically create the name of the field you are accessing,
or if the name contains special characters, in which case accessing the field using the static member access will not be allowed by the syntax.
It is especially useful when accessing entity properties, which usually contain dots or dashes in their names.
For example, issue.properties['com.your.app.property-name']
.
Indexed access
Individual members of lists can be accessed by index.
For example, to get the first issue comment, write: issue.comments[0]
.
Mathematical operators
Jira expressions allow all the usual kinds of mathematical operations.
You can add, subtract, multiply, or divide numbers.
For example, to check if the number of comments on an issue is even, write: issue.comments.length % 2 == 0
.
Boolean operators
The usual logical operators are available: conjunction (&&
), disjunction (||
) and negation (!
).
If used with boolean values (true
or false
), their behavior follows the rules of classical boolean algebra.
Each of these operators can also be used with any type, following the JavaScript semantics in this case.
The latter is especially useful for defining default values.
For example, to get the value of the issue property "myProperty"
while also providing a default value in case it's not defined, write:
issue.properties["myProperty"] || "default value"
.
Comparisons
Values can be compared to each other in different ways, depending on the type. For example,
it's possible to check if one number is lesser or greater than another number, but lists or strings can be tested only for equality.
Only values of the same type can be compared together.
For example, to check if the issue has more than 0 comments, write: issue.comments.length > 0
.
1
issue.comments.length > 0 ? issue.comments[0].author : null
1 2 3 4
issue.comments
.map(c => c.body.plainText)
.filter(text => text.length > 100)
.length
includes()
method to test if the actual value is one of the two listed:
1
['Bug', 'Task'].includes(issue.issueType.name)
1 2 3 4
issue.comments.map(c => {
author: c.author,
body: c.body.plainText
})
The following examples demonstrate how to write Jira expressions and what they can do.
Get contents of all comments added by the current user in the current issue:
1 2 3
issue.comments
.filter(c => c.author.accountId == user.accountId)
.map(c => c.body)
Check if the current user is the one stored in a project's entity property:
1
user.accountId == project.properties['special-user'].accountId
Check if the issue type is either a Bug or Task (using a regular expression):
1
issue.issueType.name.match('^(Bug|Task)$') != null
Retrieve IDs of all linked issues along with the link name:
1 2 3 4
issue.links.map(link => {
name: link.type[link.direction],
issue: link.linkedIssue.id
})
To aggregate data, use the set()
method for Map
and the reduce()
method for List.
This is particularly useful in combination with a JQL query that can be provided in the REST API to load a list of issues into the expression context.
For example, the following expression will count issues by their status name:
1 2 3 4 5
issues.reduce((result, issue) =>
result.set(
issue.status.name,
(result[issue.status.name] || 0) + 1),
new Map())
Note that map[issue.status.name] || 0
will return either the current mapping for the given status,
or 0
if there is no mapping yet. This is a handy way to declare default values.
If the above expression is evaluated using the REST API, the result will be, for example:
1 2 3 4 5 6 7
{
"value": {
"To Do": 2,
"In Progress": 10,
"Done": 5
}
}
Depending on the context in which a Jira expression is evaluated, different context variables may be available:
user
(User): The current user. Equal to null
if the request is anonymous. app
(App): The Connect app
that made the request or provided the module.
Always available for expressions used in Connect modules, and also in REST API request made by Connect Apps
(read more here: Authentication for Connect apps). issue
(Issue): The current issue. issues
(List<Issue>): The list of issues
available when a JQL query is specified in the request context when using the REST API. project
(Project): The current project. sprint
(Sprint): The current sprint. board
(Board): The current board. serviceDesk
(ServiceDesk): The current service desk. customerRequest
(CustomerRequest): The current customer request. These variables are registered in the global scope. For example, to check if the request is not anonymous, write: user != null
.
To check if a context variable is available, use the typeof
operator. It will return "undefined" for unavailable variables.
For example, to check if the current project is next-gen, but only if the expression is evaluated in the context of a project, write:
1
typeof project == 'undefined' || project.style == 'next-gen'
The above will return true in any of these occur:
the expression is evaluated in the context of a project and the project is next-gen.
There are two ways to interact with Jira entities:
Loading an object is done with type constructors. For example, to get a summary of an issue with key HSP-1, write:
1
new Issue('HSP-1').summary
The method above loads the issue from the database using the given key, then gets the issue’s summary. Note that null is returned by the constructor if the issue doesn't exist or you don't have permission to view it.
Consult the type reference to learn which types have similar constructors.
Keep in mind that loading data this way is considered an expensive operation, and there is a limit of how many expensive operations a single expression evaluation can have. Because of this, only load data where it’s not possible to use context variables.
Using Jira expressions, it is possible to access entity properties of any entity that supports them, that is:
issue, project, issue type, comment, user, board, sprint.
App properties
are also available.
To do this, get the properties
field of the appropriate object.
For example, app.properties. The field returns what can be thought of as a map of all properties, indexed by their keys.
Read more about how to interact with properties in the EntityProperties object documentation.
Fields that contain timestamps, such as issue.created
or issue.resolutionDate
,
are returned as objects of the Date type,
which is based on the JavaScript Date API.
Fields that contain dates only, such as issue.dueDate
, are returned as the timezone-agnostic CalendarDate type, which is like Date,
but with a limited set of methods (methods related to time or timezones are not available).
A Date or CalendarDate object can be transformed into three different String formats:
toISOString()
method. This will return a string in the ISO 8601 extended format. For example, issue.created.toISOString()
.issue.created
.toString()
method. This will return a string in the human-readable format, according to the current user's locale and timezone. For example, issue.created.toString()
. The same format is also used if a date is concatenated with a string. For example, 'Due date: ' + issue.dueDate
.A Date
object can also be converted to a CalendarDate
object by using either toCalendarDate()
or toCalendarDateUTC()
.
These methods remove the time information from the object, leaving only the calendar date,
in the current user's timezone or the UTC timezone, respectively.
Date objects of the same type can be compared using regular comparison operators. For example, to get comments that were added after the issue's due date, write:
1 2
issue.comments
.filter(c => c.created.toCalendarDate() > issue.dueDate)
A date can be modified by adding or subtracting units of time. To do this, use the methods below. Each of these methods take a date and a number of units of time, then create a new modified date.
date.plusMonths(Number)
: Creates a new date that is the original date plus the specified number of months.date.plusDays(Number)
: Creates a new date that is the original date plus the specified number of days.date.plusHours(Number)
: Creates a new date that is the original date plus the specified number of hours.date.plusMinutes(Number)
: Creates a new date that is the original date plus the specified number of minutes.(All methods above have a subtraction counterpart. For example, date.minusMonths(Number)
.)
Date modification methods can be used to build expressions that assert when Jira events have occurred. To do this, get the current date and modify it, then compare the modified date to the date of the event. Here's an example of how to check if an issue has been updated in the last three days:
new Date()
).new Date().minusDays(3)
is the current date minus three days.issue.updated
event. For example, issue.updated > new Date().minusDays(3)
will return true if the issue has been updated in the last three days.Jira expressions can be specified in several modules in the app descriptor (for example, web conditions). The execution of these expressions is performed on the Jira side, with no direct interaction with the app. For monitoring and testing purposes, however, it’s useful for apps to know when Jira fails to evaluate their expressions, be it due to invalid syntax, semantic bugs caused by unexpected data, or even a temporary glitch on the Jira side.
To make this possible, Jira allows apps to declare a webhook to be notified whenever such failures occur. For example, you can declare the webhook in your descriptor as follows:
1 2 3 4 5 6 7 8
{
"webhooks": [
{
"event": "jira_expression_evaluation_failed",
"url": "/jira-expressions-monitoring"
}
]
}
You will start receiving POST callbacks to /jira-expressions-monitoring
whenever any of the
expressions provided by your app fails. Expressions from any of the following modules will be included:
Expressions declared by other apps, or expressions evaluated using the REST API are excluded from this mechanism.
Here is an example payload sent in the webhook callback:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
{
"timestamp": 1562571661978,
"webhookEvent": "jira_expression_evaluation_failed",
"expression": "'1' == 1",
"errorMessages": ["Evaluation failed: \"1 == '1'\" - operator \"==\" is not applicable to types: Number and String"],
"moduleKey": "module-that-provided-the-expression",
"validator.id": "bb98deec-4f14-4431-9dbd-fc591cb71f34",
"workflow.name": "HSP: Project Management Workflow",
"context": {
"issue": {
"id": "10000",
"key": "HSP-1"
},
"project": {
"id": "10000",
"key": "HSP"
},
"transition": {
"id": 21,
"name": "Done",
"from": {
"name": "To Do",
"id": "10000"
},
"to": {
"name": "Done",
"id": "10001"
}
}
}
}
The JSON payload always contains the following properties:
timestamp
: The time when the expression failed to evaluate, in epoch milliseconds.webbookEvent
: The name of the event. In this case it will always be jira_expression_evaluation_failed.expression
: The expression that failed to evaluate correctly. errorMessages
: A list of error messages that explain why the evaluation failed. The messages are the same as those returned
by the REST API.context
: A set of context variables available to the expression during its evaluation.
Note that in the example above, some properties of variable values were omitted to keep it succinct.Additionally, these properties may be included if applicable, depending on where the expression comes from:
moduleKey
: The key of the module that provided the expression.
Currently, this is available for workflow conditions and validators,
but will not be included for expressions from web conditions.condition.id
: The ID of the evaluated workflow condition.validator.id
: The ID of the evaluated workflow validator.workflow.name
: The name of the workflow the evaluated condition or validator belong to.Some restrictions apply to the evaluation of expressions. While the limits are high enough not to interfere with any intended usage, it's important to realize that they do exist.
The expression can execute at most 10 expensive operations. Expensive operations are those that load additional data, such as entity properties, comments, or custom fields.
Most issue fields are optimized to execute just one expensive operation even if the expression is processing large data sets. However, it may happen that expression's complexity depends on the data size. If that's the case, we encourage you to create a feature request in the ACJIRA project, to allow such expressions to be processed with constant complexity.
Operations on unbounded collections (for example, issue comments) may appear to compute in constant time,
but they are limited to processing up to 1,000 items. For example, issue.comments.map(c => c.properties.myProperty)
computes in constant time for the first 1,000 comments on the issue, but consumes one expensive operation
for each comment after that. To make sure your expression evaluates successfully, always limit the data set
that is being processed, for example, by using the slice
method: issue.comments.slice(0, 1000).map(c => c.properties.myProperty)
.
The max number of results returned by the Evaluate Jira expression REST API operation is 10,000 primitive values or 1,000 Jira REST API objects.
Expressions may execute up to 50,000 steps, where a step is a high-level operation performed by the expression. A step is an operation such as arithmetic, accessing a property, accessing a context variable, or calling a function. In most practical cases, a Jira expression is unlikely to exceed the step limit.
The expression's size is limited to 1,000 characters or 100 syntactic elements.
Use the Analyse Jira expression REST API operation to statically check the characteristics of your expression without evaluating it.
For example, to analyse an expression that fetches the number of comments and attachments for a list of issues:
1 2 3 4 5
issues.map(issue => {
key: issue.key,
comments: issue.comments.length,
attachments: issue.attachments.length
})
Send the following request:
POST https://your-domain.atlassian.net/rest/api/2/expression/analyse?check=complexity
1 2 3 4 5
{
"expressions": [
"issues.map(issue => { key: issue.key, commentsCount: issue.comments.length, attachments: issue.attachments.length })"
]
}
Which results in the following response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
{
"results": [
{
"expression": "issues.map(issue => { key: issue.key, comments: issue.comments.length, attachments: issue.attachments.length })",
"valid": true,
"type": "List<{attachments: Number, comments: Number, key: String}>",
"complexity": {
"expensiveOperations": "N + 1",
"variables": {
"N": "issues"
}
}
}
]
}
This response shows that:
List<{attachments: Number, comments: Number, key: String}>
, that is
it returns a List where each item is a Map with these properties: N + 1
, where N
is the length of the issues
list.
This complexity means the expression will fail if the number of issues is greater than 9. This is caused by accessing the “heavy-weight” comments
property.You can also execute expressions with the Evaluate Jira expression REST API operation
and use the meta.complexity
expand parameter to see the runtime complexity.
Rate this page: