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.
groovyimport 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.
groovypackage 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.
groovydef 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.
groovyimport 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.
groovyif (!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.
groovyif (!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.
groovyif (!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.
groovydef 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
javafinal 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.
Event | Binding/Context | Parameter 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 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, |
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, |
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. |
|
Issue Type Updated | ||
Issue Type Deleted | ||
Option Attachments Changed | property The Jira configuration as a Map. Available fields: self, key, value. | property.key, |
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 Deleted | ||
Project Updated | ||
Sprint Created | sprint The sprint details as a Map. See Get Sprint REST API reference for details. | sprint.name, |
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 Updated | ||
User Deleted | ||
Version Created | version The Project Version details as a Map. See Get Version REST API reference for details. | version.name, |
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 Updated | ||
Worklog Deleted |