Modify or create a new workflow.

Add a new condition.

Most of the built-in workflow functions can be customized with Condition code or Additional actions code, so if you can use a built-in script, you should.


Example 1. Where to put your scripts?

Give either the absolute path of the script, or path relative to one of your script roots. Relative paths are more portable and make switching servers easier.

If your scripts/classes have a package declaration, obviously, you will need the correct directories under there.

Simple built-in scripts like Simple Scripted Condition can be used to avoid most of the boilerplate in writing a condition function.

Script Binding

For each type of workflow function, the plugin will provide the current issue and transientVars in the script binding. That means you can refer to them using these variables, without declaring them.

log.debug issue.getKey()
GROOVY

If you use Intellij IDEA, you can add a dynamic property for these variables of the correct type. Alternatively you can just redeclare it with type information:

import com.atlassian.jira.issue.Issue;
Issue issue = issue
GROOVY

Condition functions define whether an action is visible and available for a particular issue. Jira runs condition functions more often than you would expect, so avoid doing any long processing in them.

Set the variable passesCondition to true or false depending on whether you want the action to be permitted or not. Note that passesCondition defaults to true, so if it is unset in your script, then the action will be permitted.

Avoid complex scripts with heavy processing requirements, as the condition is evaluated by Jira several times if it is applicable instead of once.

Instead of a custom condition you may prefer to use Simple Scripted Condition, where you can just return true or false.

Technical Stuff for Advanced Users

The conditions are mini groovy scripts. When they are modified, they are automatically compiled and cached.

The following variables are available:

Name

Type

Sample Usage

cfValues

java.util.Map

Table of custom field names (not IDs) to their values for this issue, used for quickly looking up CF values

currentUser

com.atlassian.crowd.embedded.api.User

User doing the transition, name is available as currentUser.name

attachmentManager

com.atlassian.jira.issue.AttachmentManager

Used for getting attachments

issue

com.atlassian.jira.issue.MutableIssue

Current issue in this transition

issueLinkManager

com.atlassian.jira.issue.link.IssueLinkManager

Used for checking links

customFieldManager

com.atlassian.jira.issue.CustomFieldManager

Could be used if the cfValues is not sufficient, or you need to check values on other issues

originalIssue

com.atlassian.jira.issue.Issue

In validators, a copy of this issue before the user changed it in this transition. So for instance, to ensure the assignee was changed during a transition, in your validation condition you might put: issue.assignee != originalIssue.assignee

baseUrl

java.lang.String

Base URL of Jira, used for building URL links

Many of the built-in functions have a condition option, as it’s not possible in Jira to only activate a workflow post-function if some other condition is true. You can test conditions before setting them up for real on the Condition Tester admin script. If there is a syntax error it will be shown.

Safe navigation

Let’s say you want to check the resolution is Won’t Fix. You could write

issue.resolution.name == "Won't Fix"
GROOVY

However, if the resolution is not set at all, ie is null, this code would produce an error:

Therefore it should be written with the safe-navigation operator, ie:

issue.resolution?.name == "Won't Fix"
GROOVY

Note the question mark.

Power Assertions

Power assertions are a very useful debugging tool. On the questionable line you are having problems with, simply begin it with assert.

As an example, let’s say I am trying to check the value for a custom field called MySelect, which has two values Yes and No. I want my workflow function to happen only if the value is set to Yes. As a naive first step, I try cfValues['MySelect'] == "Yes", this doesn’t work for an issue where it should, so I add the assert:

assert cfValues['MySelect'] == "Yes"
GROOVY

which gives the following failure:

Note this is a bit counter-intuitive, as apparently both sides are "Yes", yet the == was false. Whenever you see something odd like this check the type of class:

assert cfValues['MySelect'].class == String.class
               |            |     |
               Yes          |     false
                           class com.atlassian.jira.issue.customfields.option.LazyLoadedOption
GROOVY

This tells us that value for this custom field key is actually a LazyLoadedOption, and we should call the getValue() method. It just so happens that the toString() method of Option returns the value, but you can’t compare them as strings.

Similarly, a multiselect is stored as a List of Options, so the correct code to use to check to see if a particular value is present is:

cfValues['MyMultiSelect']*.value.contains("AAA")
GROOVY

That is, use the spread-dot operator to call the getValue() method on every item, then we check if "AAA" is in that list.

In short, if you are having problems use the Condition Tester built-in script and add assert statements.

Remove the assert before using as a real condition - the assertion doesn’t return anything as such so your condition will always evaluate to false if you leave the assert in.