ScriptRunner for Jira Cloud Logo

Getting Started

JIRA Extension Points

Releases

ScriptRunner for JIRA Cloud allows you to extend the functionality of JIRA Cloud, executing scripts to interact with JIRA as Post Functions or Event Listeners. Scripts can take actions such as updating an issue during a transition or performing a calculation and storing the result in a custom field on the issue. Administrators have the power of the Groovy programming language at their disposal to respond to events and transitions, manipulating JIRA using the REST API. Additionally, you can use the Enhanced Search feature to run JQL Functions in JIRA Cloud and create filters for dashboards and scrum boards that use those functions.

Differences to ScriptRunner for JIRA Server

ScriptRunner for JIRA Cloud follows the same principles as the original ScriptRunner but due to the differences in extension points between the Atlassian Connect framework used to write add-ons in the cloud and the Plugins V2 framework used in behind the firewall implementations, the execution model is significantly different. Scripts in ScriptRunner Cloud do not execute within the same process as JIRA and so must interact with JIRA using the REST APIs rather than the JAVA APIs. Atlassian Connect is also inherently asynchronous which means that when a script executes the user may see the page load before the script has completed.

At this time it is not possible to implement the Behaviours feature within ScriptRunner for Jira Cloud.

We have included a table which provides a comparison of the features between the ScriptRunner for Jira Server and ScriptRunner for Jira Cloud Versions.

Asynchronous

Asynchronous execution is at the heart of the Atlassian Connect Framework that ScriptRunner must use to extend JIRA Cloud. JIRA fires webhooks when events and transitions occur and any user interface elements are loaded in iframes. It is not possible to veto, cancel or otherwise prevent JIRA performing an action using ScriptRunner. Scripts themselves are written synchronously, that is REST API calls are made in a blocking style (although async methods are provided too). This page has an excellent introduction to the Atlassian Connect framework for those that are interested.

Scopes

When installing the add-on a list of permissions, or scopes, are presented that ScriptRunner for JIRA Cloud requires to run successfully. A list of the scopes required for each REST API endpoint that JIRA Cloud provides can be found here. Below is a detailed explanation of why we need each of those scopes:

Act on a JIRA user’s behalf, even when the user is offline

Scripts can be configured to execute as either the add-on, or as the user who initiated that script. For example, if a user transitions an issue, then the Workflow Post Function will be initiated by that user, so it makes sense to execute the Post Function as the user who transitioned the issue. This ensures that each user’s permissions are respected, and provides a much clearer history of who’s made changes to the issues in your system.

Administer JIRA

This scope allows for the creation, update and deletion of issue types and issue link types, as well as for creating custom fields when running a script as the ScriptRunner Add-on user.

Administer JIRA projects

This allows you to write scripts that execute as the ScriptRunner Add-on user for creating, updating or removing Projects, Components and Versions so that you don’t need to grant those permissions to the rest of your user base.

Delete JIRA data

This scope is required in order to delete issues, comments, worklogs, issuelinks etc while running a script as the ScriptRunner Add-on user.

Write data to JIRA

This scope is required in order to create issues, comments, worklogs etc while running a script as the ScriptRunner Add-on user.

Read JIRA data

This scope is required in order to view issues, comments, worklogs etc while running a script as the ScriptRunner Add-on user.

Isolated Execution

All user provided code is executed in an isolated container. ScriptRunner for JIRA Cloud is a service that is shared by many customers so isolation is very important that when executing scripts. Isolation is a goal that has been core to the design of ScriptRunner for JIRA from its inception. Each customer has a container that is theirs and theirs only. It is not possible for the code of one customer to have been run in the container of another. Containers are also designed to be single use, however it is possible that containers will be re-used for execution on occasion, but script writers should not rely on this. Disk storage in these containers is ephemeral, any changes written to the disk will be discarded.

REST APIs

All interaction with JIRA must be through the provided REST APIs. Atlassian provides comprehensive documentation including JSON Schema for responses and request bodies which describe the shape of the JSON. For effective usage of ScriptRunner it is important to have an understanding of how to interact with JIRA using the REST API.

Authentication and Authorisation

All REST requests made from ScriptRunner are performed as either the ScriptRunner user or as the user that initiated the action. The initiating user is the current user of the Script Console, the user that performed the action to cause an event to fire or the user that performed a workflow transition that caused a Post Function to run. Atlassian Connect Add-ons must also register for API scopes. These work in a similar way to the permissions that are granted to iOS or Android apps, when installing the JIRA Administrator can see the scopes that any given add-on is requesting. ScriptRunner requests all scopes as it is not know beforehand what any given user will want to do. Even given this there are restrictions on the APIs that are available to ScriptRunner.

  • No private APIs are available, only those specifically white-listed by Atlassian

  • Scopes are documented for

  • It is possible for Project Administrators to remove access to a particular add-on by modifying the permissions for a project.

Authentication from user scripts is handled by an authentication proxy builtin to ScriptRunner. Each script invocation is given a temporary authentication token to use to make requests into this proxy which will then perform the necessary request signing to make the authenticated request to your JIRA instance. In this way authenticated requests happen transparently. Tokens that are handed to scripts are only valid for two minutes. Responses that come through the proxy have URLs modified to go through the proxy so that URLs in the JSON response can be used directly without manipulation. For example the following code shows how to fetch a project from an issue get.

String projectUrl = get("/rest/api/2/issue/EX-1").asObject(Map).body['fields']['project']['self']

get(projectUrl).asObject(Map).body.name

Unirest

The HTTP library provided by ScriptRunner is Unirest. Unirest is a simple, lightweight library that makes interacting with REST APIs simple and straightforward. It was chosen due to the minimal dependencies (based on Apache HTTP Client 4.5), flexibility (JSON mapping support is built in and object mapping provided by Jackson) and the clarity of API.

Unirest, Unirest.get, Unirest.post, Unirest.put, Unirest.head and Unirest.options are included in all scripts as import static which means no imports are needed in order to make HTTP requests. The base url to the ScriptRunner authentication proxy is filled in along with the authentication headers so making REST calls is as simple as copying and pasting from the JIRA REST documentation

Examples

Get issue types:

get('/rest/api/2/issuetype').asString().body

Create an issue:

def projectKey = 'TP'
def taskType = get('/rest/api/2/issuetype').asObject(List).body.find { it['name'] == 'Task' }['id'] (1)

post('/rest/api/2/issue') (2)
        .header('Content-Type', 'application/json')
        .body(
        [
                fields: [
                        summary    : 'Task Summary',
                        description: "Don't forget to do this!.",
                        project    : [
                                key: projectKey
                        ],
                        issuetype  : [
                                id: taskType
                        ]
                ]
        ])
        .asString().body (3)
  1. Use the issuetype REST endpoint to get all issuetypes, parse into a list, and find the type with name Task, then take the id of that type

  2. Make the post request to create the issue in the specified project with the specified issuetype

  3. It is important to note that the request is only made when one of the as* methods are called. asString() takes response and makes it available in .body as a String. asObject(Object) will use Jackson to parse the response from JSON

Update an issue:

def issueKey = 'TP-1' (1)
def newSummary = 'Updated by a script'

def result = put("/rest/api/2/issue/${issueKey}") (2)
    //.queryString("overrideScreenSecurity", Boolean.TRUE) (3)
    .header('Content-Type', 'application/json') (4)
    .body([
        fields: [
                summary: newSummary
        ]
    ])
    .asString() (5)

if (result.status == 204) { (6)
    return 'Success'
} else {
    return "${result.status}: ${result.body}"
}
  1. Issue key and new summary to set

  2. Create the rest PUT request - see documentation

  3. You must pass overrideScreenSecurity=true if you are trying to amend fields that are not visible on the screen - Note you must use the Add-on user when setting overrideScreenSecurity=true

  4. Important to set the content type of the PUT, and then the body content as a Groovy Map

  5. Calling .asString() executes the put and parses the result as a string

  6. The REST request responds with a 204 (no content) so there is no point reading the body

Tip
It is really important to pass overrideScreenSecurity in request url, if your request wants to change fields that are not visible on the screen. If you don’t, request response will be 403 with message that you don’t have permission to do that.

Static Type Checking

Static type checking is a feature intended to provide information to you about whether your script is correctly written.

The first thing to understand about Groovy is that it’s a dynamic language. Amongst many other facets of dynamic languages, this means that method and property names are looked up when your code is run, not when it’s compiled, unlike java.

Take a very simple but complete script:

foo.bar()

That is, call the method bar() on the object foo. Unlike Java, this script will compile without errors, but when you run it you will get a MissingPropertyException, as foo hasn’t been defined. This behaviour is useful as there are many circumstances that could make this code execute successfully, for instance an object called foo, or a closure getFoo(), being passed to the script’s binding.

Although Groovy is a dynamic language, we can compile scripts in a manner that checks method and property references at compile time. The static type checking (STC) feature does just that, so that you can see any problems in your scripts when you are writing them, as opposed to when they execute.

It’s important to understand that when your scripts are executed, they are always compiled dynamically. When they are compiled for STC, the resulting generated bytecode is thrown away.

There are limitations to the type checker - it’s quite possible to write code that is displayed as having errors, but which is perfectly valid and will execute fine. Some examples of this are:

  • using certain Builders

  • using closures where the parameter types can’t be inferred

  • nested maps where type information is not available

Tip
At the risk of repetition, the type checker is not always correct. It is a best-efforts attempt to let you know if there are problems with your code - but please let us know if you find something that should compile but doesn’t. Note also that your code could still have runtime errors that won’t be found until it executes.

If we were writing a condition where we wanted to check the number of issues returned from a search is zero, we might inadvertently write:

boolean empty = isssues.total = 0

STC is telling us that we are trying to set the total, rather than retrieve it. We meant to use the equality operator:

boolean empty = isssues.total == 0

Code areas will show a green dot, which tells us that the code is syntactically correct, and the methods and properties have been checked.

STC in the cloud will provide additional help for the script context, ensuring that properties access matches the JSON Schema provided by Atlassian