Script Listener Examples

Condition Script Examples

The condition script will be evaluated before your code is executed. In the case where a value other than true is returned then we get a false result, and the code will not execute. The condition is evaluated using the Jira Expression Framework.

You can refer to our Jira Expression Examples page for further details.

Populate a user picker field from a text field

The example code below shows how you can create a field that is populated with users. Specifically, when creating an issue for the Jira Service Desk, you can specify a user in the description, so this then populates in the user picker field.

groovy
import java.util.regex.Matcher; import java.util.regex.Pattern; // Get all custom fields def customFields = get("/rest/api/3/field") .asObject(List) .body .findAll { (it as Map).custom } as List<Map> // Get the ID for your user picker field field def userPickerField = customFields.find { it.name == 'Demo User Picker' }?.id // Change to name of your field here // Get the current issue summary and todays date def descriptionVal = issue.fields.description // Define the regular expression pattern used to find the user in the description text def regex = '(?<=The owner of this ticket is:).*'; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(descriptionVal); // Extract the name of the user after the text The owner of this ticket is: if(matcher.find()){ def user = matcher.group(0) // Find the user details for the user matched from the description def userSearchRequest = get("/rest/api/3/user/search") .queryString("query", user) .asObject(List) // Assert that the API call to the user search API returned a success response assert userSearchRequest.status == 200 // Get the accountId for the user returned by the search def userAccountId = userSearchRequest.body.accountId[0] // Update the issue to set the user picker field to the returned user def setUserPickerField = put('/rest/api/3/issue/' + issue.key) .header('Content-Type', 'application/json') .body([ fields: [ (userPickerField): [id: userAccountId], ] ]) .asString() }

Line 17: Note that the username is picked up after the text 'The owner of this ticket is:', however, if required you can change this text to something more suitable.

Calculate Custom Field on Issue Update

Right now in Jira Cloud we can’t do a real calculated custom field, but we can update a field in response to an IssueUpdated event, creating a simple calculated custom field. The following example has 3 number custom fields configured. The code is designed to be used with the Issue Updated event:

groovy
// get custom fields def customFields = get("/rest/api/2/field") .asObject(List) .body .findAll { (it as Map).custom } as List<Map> def input1CfId = customFields.find { it.name == 'Custom Field 1' }?.id def input2CfId = customFields.find { it.name == 'Custom Field 2' }?.id def outputCfId = customFields.find { it.name == 'Output Custom Field' }?.id def projectKey = "TP" if (issue == null || issue.fields.project.key != projectKey) { logger.info("Wrong Project ${issue.fields.project.key}") return } def input1 = issue.fields[input1CfId] as Integer def input2 = issue.fields[input2CfId] as Integer if (input1 == null || input2 == null) { logger.info("Calculation using ${input1} and ${input2} was not possible") return } def output = input1 + input2 if (output == (issue.fields[outputCfId] as Integer)) { logger.info("already been updated") return } put("/rest/api/2/issue/${issue.key}") //.queryString("overrideScreenSecurity", Boolean.TRUE) .header("Content-Type", "application/json") .body([ fields:[ (outputCfId): output ] ]) .asString()

Line 10: First check to see if the issue is in the required project.

Line 12: Grab the values of the custom fields from the issue.

Line 20: Sanity check for null values - the issue may not have a value for any input.

Line 27: Here we check to see if the output field already has the calculated value, if it does do nothing. This is important because an IssueUpdated event will fire for the update we are about to perform.

Line 32: Update the value of the custom field.

Line 33: If using the Add-on user to run the script, it is possible to set the overrideScreenSecurity property to modify fields that are not on the current screen.

The example described above is for numeric fields. In case your field is of a different type or you don’t know how to retrieve the value, we recommend to use an example code bellow to retrieve the custom field from an issue and check the value. The example is retrieving value of a select field.

groovy
package com.adaptavist.sr.cloud.samples.events def issueKey = 'EXAMPLE-1' def customFieldName = 'customfield_10003' def result = get("/rest/api/2/issue/${issueKey}?fields=${customFieldName}") .header('Content-Type', 'application/json') .asObject(Map) if (result.status == 200) { return result.body.fields[customFieldName] } else { return "Error retrieving issue ${result}" } // Would return //{ // "self": "https://example.atlassian.net/rest/api/2/customFieldOption/10000", // "value": "To Do", // "id": "10000" //}

You can see from the example that you need to access the value of the select field via result.body.fields[customFieldName].value to be used in further computations.

Create Issues on Project Creation

When a project is created it can sometimes be desirable to create a set of setup issues to say create a confluence space or produce some project documentation. The following example creates issues on a Project Created event. It contains examples of creating individual issues and creating issues in bulk.

groovy
def projectKey = project.key as String logger.info(projectKey) def issueTypesReq = get("/rest/api/2/issuetype").asObject(List) assert issueTypesReq.status == 200 def taskType = issueTypesReq.body.find { it.name == "Task" } assert taskType // Must have an issue type named Task // create two issues post("/rest/api/2/issue") .header("Content-Type", "application/json") .body( [ fields: [ summary : "Create Confluence space associated to the project", description: "Don't forget to do this!.", project : [ id: project.id ], issuetype : [ id: taskType.id ] ] ]) .asString() post("/rest/api/2/issue") .header("Content-Type", "application/json") .body( [ fields: [ summary : "Bootstrap connect add-on", description: "Some other task", project : [ id: project.id ], issuetype : [ id: taskType.id ] ] ]) .asString() // example of bulk update: post("/rest/api/2/issue/bulk") .header("Content-Type", "application/json") .body( [ issueUpdates: [ [ fields: [ summary : "Bulk task one", description: "First example of a bulk update", project : [ id: project.id ], issuetype : [ id: taskType.id ] ] ], [ fields: [ summary : "Bulk task two", description: "2nd example of a bulk update", project : [ id: project.id ], issuetype : [ id: taskType.id ] ] ] ] ]) .asString()

Line 4: First get all Issue Types to find the Task type, we will use this id to create all our tasks.

Line 11: Create a single issue of type task.

Line 50: Note in the bulk update we use instead of using a single fields structure we nest fields inside of issueUpdates.

Email Notify on Priority Change

When particular fields change it might be necessary to notify users who wouldn’t usually get notified, with some specific text to grab some attention. The following example shows how to send a notification by email to all interested parties (reporter, assignee, watchers, voters), the user admin and the jira-administrators group when the priority is changed to Highest. This functionality is useful to send messages beyond those that are usually sent by Jira.

The notify API which this script uses contains a validation rule which prevents users from notifying themselves. This means that the execution will fail if the user being notified is the same user who executed the script.
groovy
import groovy.xml.MarkupBuilder import groovy.xml.XmlSlurper def priorityChange = changelog?.items.find { it['field'] == 'priority' } if (!priorityChange) { logger.info("Priority was not updated") return } logger.info("Priority changed from {} to {}", priorityChange.fromString, priorityChange.toString) if (priorityChange.toString == "Highest") { def writer = new StringWriter() // Note that markup builder will result in static type errors as it is dynamically typed. // These can be safely ignored def markupBuilder = new MarkupBuilder(writer) markupBuilder.div { p { // update url below: a(href: "http://myjira.atlassian.net/issue/${issue.key}", issue.key) span(" has had priority changed from ${priorityChange.fromString} to ${priorityChange.toString}") } p("You're important so we thought you should know") } def htmlMessage = writer.toString() def textMessage = new XmlSlurper().parseText(htmlMessage).text() logger.info("Sending email notification for issue {}", issue.key) def resp = post("/rest/api/2/issue/${issue.id}/notify") .header("Content-Type", "application/json") .body([ subject: 'Priority Increased', textBody: textMessage, htmlBody: htmlMessage, to: [ reporter: issue.fields.reporter != null, // bug - 500 error when no reporter assignee: issue.fields.assignee != null, // bug - 500 error when no assignee watchers: true, voters: true, users: [[ name: 'admin' ]], groups: [[ name: 'jira-administrators' ]] ] ]) .asString() assert resp.status == 204 }

Line 9: Only do anything here if the priority change was to Highest.

Line 13: Generate a HTML message using MarkupBuilder

Line 17: The issue URL here is created manually. It should be updated.

Line 23: Quick hack to produce a text only message from the HTML string.

Line 26: Post to the Notify endpoint. Note there is a Jira bug where a missing reporter or assignee will cause a 500 error. The example here is of sending the message to the reporter and assignee, any watchers and voters as well as a user and group list.

Store Sub-Task Estimates in Parent Issue on Issue Events

When a sub-task is created, updated or deleted you might want to update the parent issue with some information. This example stores the sum of the Estimated fields from each sub-task in their parent issue.

groovy
if (!issue.fields.issuetype.subtask) { return } // Get the parent issue as a Map def parent = (issue.fields as Map).parent as Map // Retrieve all the subtasks of this issue's parent def parentKey = parent.key def allSubtasks = get("/rest/api/2/search") .queryString("jql", "parent=${parentKey}") .queryString("fields", "parent,timeestimate") .asObject(Map) .body .issues as List<Map> logger.info("Total subtasks for ${parentKey}: ${allSubtasks.size()}") // Sum the estimates def estimate = allSubtasks.collect { Map subtask -> subtask.fields.timeestimate ?: 0 }.sum() logger.info("Summed estimate: ${estimate}") // Get the field ids def fields = get('/rest/api/2/field') .asObject(List) .body as List<Map> def summedEstimateField = fields.find { it.name == "Summed Subtask Estimate" }.id logger.info("Custom field ID to update: ${summedEstimateField}") // Now update the parent issue def result = put("/rest/api/2/issue/${parentKey}") .header('Content-Type', 'application/json') .body([ fields: [ (summedEstimateField): estimate ] ]) .asString() // check that updating the parent issue worked assert result.status >= 200 && result.status < 300

Line 1: Stop running the script if this issue isn’t a subtask. If you wanted to restrict the script listener to only issues in a particular project you could use: if (!(issue.fields.issuetype.subtask && issue.fields.project.key == 'EXAMPLE')) {

Line 13: Only retrieve the fields we’re interested in, to speed the search up.

Line 21: Here we handle the scenario where the timeestimate is not set.

Line 30: This line means we don’t need to put the custom field ID in our script, we can refer to it by name.

Store Sub-Task Story Points Summed Value in Parent Issue on Issue Events

When a sub-task is created, updated or deleted you might want to update the parent issue with some information. This example stores the sum of the story points field from each sub-task in their parent issue.

groovy
if (!issue.fields.issuetype.subtask) { return } // Get the parent issue as a Map def parent = (issue.fields as Map).parent as Map // Retrieve all the subtasks of this issue's parent def parentKey = parent.key // Get the field ids def fields = get('/rest/api/2/field') .asObject(List) .body as List<Map> // Get the story points custom field to use in the script def storyPointsField = fields.find { it.name == "Story Points" }.id logger.info("The id of the story points field is: $storyPointsField") // Note: The search API is limited that to only be able to return a maximum of 50 results def allSubtasks = get("/rest/api/2/search") .queryString("jql", "parent=${parentKey}") .queryString("fields", "parent,$storyPointsField") .asObject(Map) .body .issues as List<Map> logger.info("Total subtasks for ${parentKey}: ${allSubtasks.size()}") // Sum the estimates def estimate = allSubtasks.collect { Map subtask -> subtask.fields[storyPointsField] ?: 0 }.sum() logger.info("Summed estimate: ${estimate}") // Store the summed estimate on the Story Points field of the parent issue def summedEstimateField = fields.find { it.name == "Story Points" }.id logger.info("Custom field ID to update: ${summedEstimateField}") // Now update the parent issue def result = put("/rest/api/2/issue/${parentKey}") .header('Content-Type', 'application/json') .body([ fields: [ (summedEstimateField): estimate ] ]) .asString() // check that updating the parent issue worked assert result.status >= 200 && result.status < 300

Line 1: Stop running the script if this issue isn’t a subtask.

Line 23: Here we only retrieve the story points field to make the search faster.

Line 32: Here we get the story points value of each sub task so that they can be summed together.

Line 38: This line means we don’t need to put the custom field ID in our script, we can refer to the Story Points field by name.

Store the Number of Sub-Tasks in the Parent

Similar to the example above, you might want to store the number of sub-tasks in the parent issue when a sub-task is created, updated or deleted.

groovy
if (!issue.fields.issuetype.subtask) { return } // Retrieve all the subtasks of this issue's parent def parentKey = issue.fields.parent.key def allSubtasks = get("/rest/api/2/search") .queryString("jql", "parent=${parentKey}") .queryString("fields", "[]") .asObject(Map) .body .issues as List<Map> logger.info("Total subtasks for ${parentKey}: ${allSubtasks.size()}") // Get the field ids def fields = get('/rest/api/2/field') .asObject(List) .body as List<Map> def subtaskCount = fields.find { it.name == "Subtask Count" }.id logger.info("Custom field ID to update: ${subtaskCount}") // Now update the parent issue def result = put("/rest/api/2/issue/${parentKey}") .header('Content-Type', 'application/json') .body([ fields: [ (subtaskCount): allSubtasks.size() ] ]) .asString() // check that updating the parent issue worked assert result.status >= 200 && result.status < 300

Line 1: Stop running the script if this issue isn’t a subtask.

Line 8: You could extend this query string to only return unresolved subtasks, or subtasks of a particular type etc.

Line 13: We don’t need any fields because we just want the total number of results.

Line 20: This line means we don’t need to put the custom field ID in our script, we can refer to it by name.

Add a Comment on Issue Created

If you have a support project, you might want to respond to newly created issues with a canned response. This script adds a comment to each new issue in a particular project.

groovy
def projectKey = 'SUPPORT' // Restrict this script to only run against issues in one project if (issue.fields.project.key != projectKey) { return } def author = issue.fields.creator.displayName // Add a plain-text comment, see https://docs.atlassian.com/jira/REST/cloud/#api/2/issue/{issueIdOrKey}/comment-addComment // for more details def commentResp = post("/rest/api/2/issue/${issue.key}/comment") .header('Content-Type', 'application/json') .body([ body: """Thank you ${author} for creating a support request. We'll respond to your query within 24hrs. In the meantime, please read our documentation: http://example.com/documentation""" ]) .asObject(Map) assert commentResp.status == 201

Post to Slack When Issue Created

Add this listener to the Issue Created event type to post a notification to Slack when an issue is created.

groovy
// Specify the key of the issue to get the fields from def issueKey = issue.key // Get the issue summary def summary = issue.fields.summary // Get the issue description def description = issue.fields.description // Specify the name of the slack room to post to def channelName = '<ChannelNameHere>' // Specify the name of the user who will make the post def username = '<UsernameHere>' // Specify the message metadata Map msg_meta = [ channel: channelName, username: username ,icon_emoji: ':rocket:'] // Specify the message body which is a simple string Map msg_dets = [text: "A new issue was created with the details below: \n Issue key = ${issueKey} \n Issue Sumamry = ${summary} \n Issue Description = ${description}"] // Post the constructed message to slack def postToSlack = post('https://slack.com/api/chat.postMessage') .header('Content-Type', 'application/json') .header('Authorization', "Bearer ${SLACK_API_TOKEN}") // Store the API token as a script variable named SLACK_API_TOKEN .body(msg_meta + msg_dets) .asObject(Map) .body assert postToSlack : "Failed to create Slack message check the logs tab for more details"

Adds the current user as a watcher

java
final issueKey = issue.key def result = get('/rest/api/2/user/assignable/search') .queryString('issueKey', "${issueKey}") .header('Content-Type', 'application/json') .asObject(List) def usersAssignableToIssue = result.body as List<Map> def currentUser = get('/rest/api/3/myself') .asObject(Map) .body usersAssignableToIssue.forEach { Map user -> def displayName = user.displayName as String logger.info(displayName + " : " + currentUser) if (displayName == currentUser.displayName) { logger.info("Match") def watcherResp = post("/rest/api/2/issue/${issueKey}/watchers") .header('Content-Type', 'application/json') .body("\"${currentUser.accountId}\"") .asObject(List) if (watcherResp.status == 204) { logger.info("Successfully added ${displayName} as watcher of ${issueKey}") } else { logger.error("Error adding watcher: ${watcherResp.body}") } } }

Examples of Additional Bindings and Parameters

The Script Context is a set of parameters/code variables that are automatically available in your script to provide contextual data for the script listeners. They are displayed above the code editor under the ‘Show Me’ link. The parameters and variables in the Script Context are different for each Listener Event. 

EventBinding/ContextParameter Examples

Attachment Created

attachment

The attachment details as a Map. See Get Attachment REST API reference for details.

attachment.filename, attachment.author.displayName, attachment.content
Attachment Deleted
Board Created

board

The board details as a Map. See Get Board REST API reference for details.

board.id,
board.name,
board.type

Board Deleted
Board Updated
Board Configuration Changed
Comment Created

comment

The comment details as a Map. See Get Comment REST API reference for details.

issue

Limited issue details as a Map. It has id, self, key and fields(status, priority, assignee, project, issuetype, summary) properties.

comment.body, comment.author.displayName,
issue.key
Comment Updated
Comment Deleted
Issue Created

issue

The issue details as a Map. See Get Issue REST API reference for details.

user

The user details as a Map. See Get User REST API reference for details.

issue_event_type_name

A String containing: issue_created

For Issue Updated Only

changelog

The changelog details as a Map. See Webhook Changelog Example for details
issue.key,
user.displayName, issue.fields.project.key
Issue Updated
Issue Deleted
Issue Link Created

issueLink

The issue link details as a Map, available fields: id, sourceIssueId, destinationIssueId, issueLinkType. See Get Issue Link Type REST API reference for details.


Issue Link Deleted
Issue Type Created

issueType

The issue type details as a Map. See Issue Type REST API reference for details.

project.id,
project.key,
issuetype.id,


Issue Type Updated
Issue Type Deleted
Option Attachments Changed

property

The Jira configuration as a Map. Available fields: self, key, value.







property.key,
property.value
Option Issuelinks Changed
Option Subtasks Changed
Option Timetracking Changed
Option Unassigned Issues Changed
Option Voting Changed
Option Watching Changed 
Project Created

project

The project details as a Map. See Get Project REST API reference for details.

project.key,
project
.lead.displayName
Project Deleted
Project Updated
Sprint Created

sprint

The sprint details as a Map. See Get Sprint REST API reference for details.

sprint.name,
sprint
.id
Sprint Started
Sprint Closed
Sprint Deleted
Sprint Updated
User Created

user

The user details as a Map. See Get User REST API reference for details.

user.displayName,
user
.accountId
User Updated
User Deleted
Version Created

version

The Project Version details as a Map. See Get Version REST API reference for details.




version.name,
version
.self,
version
.id,
version
.description,
version
.archived,
version
.released,
version
.overdue,
version
.userReleaseDate,
version
.projectId
Version Updated
Version Deleted
Version Moved
Version Released
Version Unreleased
Worklog Created

worklog

The Worklog details as a Map. See Get Worklog REST API reference for details.

worklog.id,
worklog
.author.displayName, worklog.updateAuthor.displayName
Worklog Updated
Worklog Deleted


On this page