Script Listeners
ScriptRunner script listeners run a script when they are triggered by one or more webhook events. Webhooks are fired after an action takes place in Jira, for example, when an issue is updated or a project is created.
For example, you can use a script listener to populate a project with initial issues when it is created. Script listeners can also be used to send notifications outside of the standard notification scheme.
Warning
|
When creating listeners, be careful that you do not create an 'endless loop scenario'. For example, configuring a listener to add a comment on an event which triggers a comment related event. This can cause performance issues. To fix this, disable the problem listener. |
Create a Script Listener
-
Navigate to ScriptRunner → Script Listeners.
-
Click Create Script Listener.
-
Enter the name of the listener in Script Listener Called.
-
Select the event(s) you wish the listener script to trigger on in On These Events, for example, Issue Updated.
-
Select the projects you want the listener to be active for; you can select All Projects or a number of individual projects.
NoteProject settings only apply to issue, project, issuelink (source issue), version, and comment related events. -
Choose the user you wish to run the listener as in As This User. See As This User for more information.
-
Enter a condition on which the code will run. See Evaluate Condition for more information.
-
Write your script in the Code to Run field. This code is executed when the Evaluate Condition is true. Click on the examples under the code field for common use cases that can be edited to suit needs.
TipSee Examples for use cases. -
Click Save.
Edit a Script Listener
-
Navigate to ScriptRunner → Script Listeners. A list of all listeners is shown.
-
Click Edit Script Listener on the listener you wish to edit.
-
When all changes have been made, click Save.
Fields
As This User
Script listeners can make requests back to Jira using either the add-on user (ScriptRunner Add-On User) or the user that is setting up the listener (Current User).
When using the Initiating User, any action occurring as a result of the function is registered as being performed by them. For example, if an issue is commented on, the comment comes from the Initiating User rather than the obscure ScriptRunner Add-on User who may have nothing to do with the issue/project affected.
Permissions are considered when executing actions. The user selected in the Run as User field must have the correct permissions to do the action specified. Typically the ScriptRunner Add-on User has project admin permissions; however, this can be restricted. The Initiating User may have higher permissions than the ScriptRunner Add-on User.
Script listeners can make requests back to Jira using either the ScriptRunner Add-on user or the user that performed the action to cause the event to be fired. See the Run As User section of Workflow Extensions for more information.
Evaluate Condition
The condition will be evaluated before the execution of your code. In case it returns other value than true, the code will not execute. The condition is evaluated using Evaluate Jira expression, so please refer to the documentation for correct syntax and context variables available.
There is default Context variables "user" (app is not supported yet by Atlassian) and variables specific to the chosen event. e.g for issue related events the Context variables will be "issue" and "user", for sprint related events the Context variables will be "sprint" and "user"
Condition Examples
-
issue related events, "issue.comments.map(c ⇒ c.body)" stops the execution as the result is not true but a list of objects
-
issue related events, "issue.comments.length == 2" succeeds for issues with 2 comments
-
sprint related events, "issue.comments.length == 2" errors as issue is not available in the context of sprint event, code will not execute
-
issue related events, "issue.issueType.name.match('^(Bug|Task)$') != null" succeeds only for issues with issue type, Bug or Task
-
issue related events, "issue" stops the execution, you can see the returned value from the execution in the execution detail and check available fields
-
all events, "user.key == 'special-user-key'", runs the code only if the user performing the event is 'special-user-key'
Script Context
The Script Context is a set of parameters/code variables that are automatically injected into your script to provide contextual data for the script listeners. They are displayed immediately above the code editor. The parameters in the Script Context are different for each Event.
Common parameters in the Script Context for all the events are:
-
baseUrl - Base url to make API requests against. This is the URL used for relative request paths e.g. if you make a request to
/rest/api/2/issue
we use the baseUrl to create a full request path. -
logger - Logger to use for debugging purposes. Check the methods available org.slf4j.Logger
-
timestamp - The timestamp of the event in milliseconds e.g. 1491562297883
-
webhookEvent - The webhook event type. Atlassian Connect Webhook Documentation
Event-specific Script Context are listed below for each event.
Projects
Script listeners are by default set to run for all projects. This can be changed per Script Listener to run for selected projects only.
This filtering only applies to issue, issuelink (source issue), version, project and comment related events. In case you require filtering on issuelinks on both source and destination issue add both projects to the filter.
Examples
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
// 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) { (1)
logger.info("Wrong Project ${issue.fields.project.key}")
return
}
def input1 = issue.fields[input1CfId] as Integer (2)
def input2 = issue.fields[input2CfId] as Integer
if (input1 == null || input2 == null) { (3)
logger.info("Calculation using ${input1} and ${input2} was not possible")
return
}
def output = input1 + input2
if (output == (issue.fields[outputCfId] as Integer)) { (4)
logger.info("already been updated")
return
}
put("/rest/api/2/issue/${issue.key}") (5)
//.queryString("overrideScreenSecurity", Boolean.TRUE) (6)
.header("Content-Type", "application/json")
.body([
fields:[
(outputCfId): output
]
])
.asString()
-
First check to see if the issue is in the required project
-
Grab the values of the custom fields from the issue
-
Sanity check for null values - the issue may not have a value for any input
-
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
-
Update the value of the custom field
-
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.
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.
def projectKey = project.key as String
logger.info(projectKey)
def issueTypesReq = get("/rest/api/2/issuetype").asObject(List) (1)
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") (2)
.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: [ (3)
[
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()
-
First get all Issue Types to find the
Task
type, we will use this id to create all our tasks -
Create a single issue of type task
-
Note in the bulk update we use instead of using a single
fields
structure we nestfields
inside ofissueUpdates
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.
Note: 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.
import groovy.xml.MarkupBuilder
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") { (1)
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) (2)
markupBuilder.div {
p {
// update url below:
a(href: "http://myjira.atlassian.net/issue/${issue.key}", issue.key) (3)
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() (4)
logger.info("Sending email notification for issue {}", issue.key)
def resp = post("/rest/api/2/issue/${issue.id}/notify") (5)
.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
}
-
Only do anything here if the priority change was to Highest
-
Generate a HTML message using
MarkupBuilder
-
The issue URI here is created manually. It should be updated
-
Quick hack to produce a text only message from the HTML string
-
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.
if (!issue.fields.issuetype.subtask) { (1)
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") (2)
.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 (3)
}.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 (4)
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
-
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')) {
-
Only retrieve the fields we’re interested in, to speed the search up
-
Here we handle the scenario where the timeestimate is not set
-
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.
if (!issue.fields.issuetype.subtask) { (1)
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") (2)
.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 (3)
}.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 (4)
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
-
Stop running the script if this issue isn’t a subtask.
-
Here we only retrieve the story points field to make the search faster.
-
Here we get the story points value of each sub task so that they can be summed together.
-
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.
if (!issue.fields.issuetype.subtask) { (1)
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}") (2)
.queryString("fields", "[]") (3)
.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 (4)
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
-
Stop running the script if this issue isn’t a subtask.
-
You could extend this query string to only return unresolved subtasks, or subtasks of a particular type etc
-
We don’t need any fields because we just want the total number of results
-
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.
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.
// 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"
Additional Examples
Attachment Created
Additional Bindings available in the Script:
-
attachment - The attachment details as a Map. See Get Attachment REST API reference for details.
logger.info("Attachment: {} has been created by {}. If you have a permission to view it, you can download it via link: {}",
attachment.filename, attachment.author.displayName, attachment.content)
Attachment Deleted
Additional parameters available from the Script Context:
-
attachment - The attachment details as a Map. See Get Attachment REST API reference for details.
logger.info("Attachment: {} has been deleted by {}.", attachment.filename, attachment.author.displayName)
Board Configuration Changed
Additional Bindings available in the Script:
-
property - The Jira configuration as a Map. Available fields: self, key, value.
logger.info("Configuration for board: {} with type: {} has been changed.", configuration.name, configuration.type)
def getConfigurationResponse = get("/rest/agile/1.0/board/${configuration.id}/configuration")
.asObject(Map)
if (getConfigurationResponse.status == 200) {
def newConfig = getConfigurationResponse.body
logger.info("New configuration is: {}", newConfig)
} else {
logger.error("Failed to get board configuration with id: {}", configuration.id)
}
Board Created
Additional Bindings available in the Script:
-
board - The board details as a Map. See Get Board REST API reference for details.
logger.info("Board with id: {}, name: {} and type: {} has been created.", board.id, board.name, board.type)
Board Deleted
Additional Bindings available in the Script:
-
board - The board details as a Map. See Get Board REST API reference for details.
logger.info("Board with id: {}, name: {} and type: {} has been deleted.", board.id, board.name, board.type)
Board Updated
Additional Bindings available in the Script:
-
board - The board details as a Map. See Get Board REST API reference for details.
logger.info("Board with id: {}, name: {} and type: {} has been updated.", board.id, board.name, board.type)
Comment Created
Additional Bindings available in the Script:
-
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.
logger.info("Comment: '{}' has been created by: {} for issue with key: {}", comment.body, comment.author.displayName, issue.key)
Comment Deleted
Additional Bindings available in the Script:
-
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.
logger.info("Comment: '{}' has been deleted by: {} from issue with key: {}", comment.body, comment.author.displayName, issue.key)
Comment Updated
Additional Bindings available in the Script:
-
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.
logger.info("Comment: '{}' has been updated by: {} in issue with key: {}", comment.body, comment.author.displayName, issue.key)
Issue Created
Additional Bindings available in the Script:
-
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
logger.info("Issue: {} has been created by: {} in project: {}", issue.key, user.displayName, issue.fields.project.key)
Issue Deleted
Additional Bindings available in the Script:
-
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.
logger.info("Issue: {} has been deleted by: {} in project: {}", issue.key, user.displayName, issue.fields.project.key)
Issue Updated
Additional Bindings available in the Script:
-
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.
-
changelog - The changelog details as a Map. See Webhook Changelog Example for details
-
issue_event_type_name - A String containing: issue_updated
logger.info("Issue: {} has been updated by: {} in project: {}", issue.key, user.displayName, issue.fields.project.key)
Issuelink Created
Additional Bindings available in the Script:
-
issueLink - The issue link details as a Map, available fields: id, sourceIssueId, destinationIssueId, issueLinkType. See Get Issue Link Type REST API reference for details.
def sourceIssueResponse = get("/rest/api/2/issue/${issueLink.sourceIssueId}").asObject(Map)
assert sourceIssueResponse.status == 200
def sourceIssueKey = sourceIssueResponse.body.key
def sourceIssueSummary = sourceIssueResponse.body.fields.summary
def destinationIssueResponse = get("/rest/api/2/issue/${issueLink.destinationIssueId}").asObject(Map)
assert destinationIssueResponse.status == 200
def destinationIssueKey = destinationIssueResponse.body.key
def destinationIssueSummary = destinationIssueResponse.body.fields.summary
logger.info("A link has been created: Issue {} {} issue {}", sourceIssueKey, issueLink.issueLinkType.outwardName, destinationIssueKey)
Issuelink Deleted
Additional Bindings available in the Script:
-
issueLink - The issue link details as a Map, available fields: id, sourceIssueId, destinationIssueId, issueLinkType. See Get Issue Link Type REST API reference for details.
def sourceIssueResponse = get("/rest/api/2/issue/${issueLink.sourceIssueId}").asObject(Map)
assert sourceIssueResponse.status == 200
def sourceIssueKey = sourceIssueResponse.body.key
def sourceIssueSummary = sourceIssueResponse.body.fields.summary
def destinationIssueResponse = get("/rest/api/2/issue/${issueLink.destinationIssueId}").asObject(Map)
assert destinationIssueResponse.status == 200
def destinationIssueKey = destinationIssueResponse.body.key
def destinationIssueSummary = destinationIssueResponse.body.fields.summary
logger.info("A link has been deleted: Issue {} {} issue {}", sourceIssueKey, issueLink.issueLinkType.outwardName, destinationIssueKey)
Option Attachments Changed
This event is only fired when the Allow Attachments setting on the Attachments advanced settings page in Jira has changed. Additional Bindings available in the Script:
-
property - The Jira configuration as a Map.
Example code will be delivered soon.
Option Issuelinks Changed
This event is only fired when activating or deactivating Issue linking on the Issue linking page in Jira changes. Additional Bindings available in the Script:
-
property - The Jira configuration as a Map. Available fields: self, key, value.
Example code will be delivered soon.
Option Sub-tasks Changed
Additional Bindings available in the Script:
-
property - The Jira configuration as a Map. Available fields: self, key, value.
Example code will be delivered soon.
Option Timetracking Changed
Additional Bindings available in the Script:
-
property - The JIRA configuration as a Map. Available fields: self, key, value.
logger.info("Your configuration has changed. Key: {} has a new value: {}", property.key, property.value)
Option Unassigned Issues Changed
This event is only fired when you change the Allow unassigned issues setting on the General configuration page in Jira is changed. Additional Bindings available in the Script:
-
property - The Jira configuration as a Map. Available fields: self, key, value.
Example code will be delivered soon.
Option Voting Changed
This event is only fired when you change the Voting setting on the General configuration page in Jira is changed. Additional Bindings available in the Script:
-
property - The Jira configuration as a Map. Available fields: self, key, value.
logger.info("Your configuration has changed. Key: {} has a new value: {}", property.key, property.value)
Option Watching Changed
This event is only fired when you change the Watching setting on the General configuration page in Jira is changed. Additional Bindings available in the Script:
-
property - The Jira configuration as a Map. Available fields: self, key, value.
logger.info("Your configuration has changed. Key: {} has a new value: {}", property.key, property.value)
Project Created
Additional Bindings available in the Script:
-
project - The project details as a Map. See Get Project REST API reference for details.
def projectKey = project.key as String
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
def response = post("/rest/api/2/issue/bulk")
.header("Content-Type", "application/json")
.body(
[
issueUpdates: [
[
fields: [
summary : "Issue summary",
description: "Issue description",
project : [
id: project.id
],
issuetype : [
id: taskType.id
]
]
],
[
fields: [
summary : "Second issue summary",
description: "Second issue descriptor",
project : [
id: project.id
],
issuetype : [
id: taskType.id
]
]
]
]
])
.asString()
if (response.status == 201) {
logger.info("Successfully added issues to project with key: {}", projectKey)
} else {
logger.error("Failed to bulk add issues to project with key: {}", projectKey)
}
Project Deleted
When a project is deleted it will be moved to “Trash” before it will be automatically deleted after 60 days. Projects can also be permanently deleted manually before the 60-day window expires. This event is fired when a project is deleted. To handle moved to "Trash" events, use Project Soft Deleted webhooks.
Additional Bindings available in the Script:
-
project - The project details as a Map. See Get Project REST API reference for details.
logger.info("Project: {} that was led by {} has been deleted.", project.key, project.lead.displayName)
Project Soft Deleted
This event is only fired after a project is moved to "Trash".
Additional Bindings available in the Script:
-
project - The project details as a Map. See Get Project REST API reference for details.
logger.info("Project: {} that was led by {} has been deleted.", project.key, project.lead.displayName)
Project Updated
Additional Bindings available in the Script:
-
project - The project details as a Map. See Get Project REST API reference for details.
logger.info("Project: {} that is led by {} has been updated.", project.key, project.lead.displayName)
Sprint Closed
Additional Bindings available in the Script:
-
sprint - The sprint details as a Map. See Get Sprint REST API reference for details.
def getBoardResponse = get("/rest/agile/1.0/board/${sprint.originBoardId}").asObject(Map)
assert getBoardResponse.status == 200
def board = getBoardResponse.body
logger.info("Sprint '{}' with id: {} has been closed. Its origin board is: '{}' with type: {}", sprint.name, sprint.id, board.name, board.type)
Sprint Created
Additional Bindings available in the Script:
-
sprint - The sprint details as a Map. See Get Sprint REST API reference for details.
def issuesToAdd = ["ISSUE-1", "ISSUE-2"]
def addIssueToSprintResponse = post("/rest/agile/1.0/sprint/${sprint.id}/issue")
.header("Content-Type", "application/json")
.body(
[issues: issuesToAdd
])
.asString()
if (addIssueToSprintResponse.status == 204) {
logger.info("To sprint '{}' with id: {} added issues: {}", sprint.name, sprint.id, issuesToAdd)
} else {
logger.error("Failed to add issues: {} to sprint '{}' with id: {}", issuesToAdd, sprint.name, sprint.id)
}
Sprint Deleted
Additional Bindings available in the Script:
-
sprint - The sprint details as a Map. See Get Sprint REST API reference for details.
logger.info("Sprint '{}' with id: {} has been deleted. It was connected with board id: {}", sprint.name, sprint.id, sprint.originBoardId)
Sprint Started
Additional Bindings available in the Script:
-
sprint - The sprint details as a Map. See Get Sprint REST API reference for details.
/** you should make this request paginated if you expect more that some sprints may start with more that 100 issues
* */
def getSprintIssuesResponse = get("/rest/agile/1.0/sprint/${sprint.id}/issue?maxResults=100").asObject(Map)
if (getSprintIssuesResponse.status != 200) {
logger.error("Failed to get issues for script with id: {}. Service responded with code: {}", sprint.id, getSprintIssuesResponse.status)
return
}
def sprintIssues = getSprintIssuesResponse.body
logger.info("Sprint '{}' with id: {} has been started with issues: {}", sprint.name, sprint.id, sprintIssues.issues.collect { it.key })
Sprint Updated
Additional Bindings available in the Script:
-
sprint - The sprint details as a Map. See Get Sprint REST API reference for details.
logger.info("Sprint '{}' with id: {} has been updated. Start creating new one for the same board(id: {})",
sprint.name, sprint.id, sprint.originBoardId)
def newSprintDetails =
[
name: "sprint 1",
startDate: "2020-04-11T15:22:00.000+10:00",
endDate: "2022-04-20T01:22:00.000+10:00",
originBoardId: sprint.originBoardId,
goal: "sprint 1 goal"
]
def createNewSprintRequest = post("/rest/agile/1.0/sprint")
.header("Content-Type", "application/json")
.body(newSprintDetails)
.asString()
if (createNewSprintRequest.status == 201) {
logger.info("Successfully created a new sprint with details: {}", createNewSprintRequest.body)
} else {
logger.error("Failed to create a new sprint with details: {}", newSprintDetails)
}
User Created
Additional Bindings available in the Script:
-
user - The user details as a Map. See Get User REST API reference for details.
def projectKey = "YOUR-PROJECT-KEY"
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
def response = post("/rest/api/2/issue/bulk")
.header("Content-Type", "application/json")
.body(
[
issueUpdates: [
[
fields: [
summary : "Prepare the welcome pack for " + user.displayName,
description: "Please resolve this issue once it's been picked up.",
project : [
key: projectKey
],
issuetype : [
id: taskType.id
]
]
],
[
fields: [
summary : "Please pick up the welcome pack",
description: "It should be available to pick up from the reception desk 3 days after this ticket was created.",
project : [
key: projectKey
],
issuetype : [
id: taskType.id
],
assignee: [
name: user.accountId]
]
]
]
])
.asObject(Map)
if (!response.status == 201) {
logger.error("Failed to create initial tickets for user: {} in project: {}", user.displayName, projectKey)
return
}
logger.info("Successfully created initial tickets: {} for user: {} in project: {}", response.body.issues.collect { it.key }, user.displayName, projectKey)
User Deleted
Additional Bindings available in the Script:
-
user - The user details as a Map. See Get User REST API reference for details.
logger.info('User with name: {} and accountId: {} has been deleted.', user.displayName, user.accountId)
User Updated
Additional Bindings available in the Script:
-
user - The user details as a Map. See Get User REST API reference for details.
logger.info('User with displayName: {} and accountId: {} has been updated.', user.displayName, user.accountId)
Version Created
Additional Bindings available in the Script:
-
version - The Project Version details as a Map. See Get Version REST API reference for details.
def getProjectDetailsRequest = get("/rest/api/2/project/${version.projectId}").asObject(Map)
if (getProjectDetailsRequest.status == 200) {
def projectDetails = getProjectDetailsRequest.body
logger.info("Version with name: {} has been created for project: {}", version.name, projectDetails.name)
} else {
logger.error("Failed to get project details for freshly created version with name: {}", version.name)
}
Version Deleted
Additional Bindings available in the Script:
-
version - The Project Version details as a Map. See Get Version REST API reference for details.
logger.info("Version: [name: {}, self: {}, id: {}, description: {}, archived: {}, released: {}, overdue: {}, userReleaseDate: {}, projectId: {}] has been deleted.",
version.name, version.self, version.id, version.description, version.archived, version.released, version.overdue, version.userReleaseDate, version.projectId)
Version Moved
Additional Bindings available in the Script:
-
version - The Project Version details as a Map. See Get Version REST API reference for details.
logger.info("Version: [name: {}, self: {}, id: {}, description: {}, archived: {}, released: {}, overdue: {}, userReleaseDate: {}, projectId: {}] has been moved.",
version.name, version.self, version.id, version.description, version.archived, version.released, version.overdue, version.userReleaseDate, version.projectId)
Version Released
Additional Bindings available in the Script:
-
version - The Project Version details as a Map. See Get Version REST API reference for details.
logger.info("Version: {} has been released", version.name)
def newVersionName = version.name + "_bugfix"
def createRCVersionRequest = post("/rest/api/2/version")
.header("Content-Type", "application/json")
.body(
[
description: version.description,
name: newVersionName,
archived: version.archived,
released: version.released,
userReleaseDate: version.userReleaseDate,
projectId: version.projectId
])
.asString()
if (createRCVersionRequest.status == 201) {
logger.info("Successfully create new version with name: {}", newVersionName)
} else {
logger.error("Failed to create new version with name: {}", newVersionName)
}
Version Unreleased
Additional Bindings available in the Script:
-
version - The Project Version details as a Map. See Get Version REST API reference for details.
logger.info("Version: [name: {}, self: {}, id: {}, description: {}, archived: {}, released: {}, overdue: {}, userReleaseDate: {}, projectId: {}] has not been released.",
version.name, version.self, version.id, version.description, version.archived, version.released, version.overdue, version.userReleaseDate, version.projectId)
Version Updated
Additional Bindings available in the Script:
-
version - The Project Version details as a Map. See Get Version REST API reference for details.
logger.info("Version with name: {} has been updated", version.name)
def unresolvedIssueCountRequest = get("/rest/api/2/version/${version.id}/unresolvedIssueCount").asObject(Map)
if (unresolvedIssueCountRequest.status == 200) {
def unresolvedIssueBody = unresolvedIssueCountRequest.body
logger.info("There are unresolved issues: {}/{} for version with name: {}", unresolvedIssueBody.issuesUnresolvedCount, unresolvedIssueBody.issuesCount, version.name)
} else {
logger.error("Failed to get unresolver issue count for version with name: {}", version.name)
}
Worklog Created
Additional Bindings available in the Script:
-
worklog - The Worklog details as a Map. See Get Worklog REST API reference for details.
def worklogPropertiesRequest = get("/rest/api/2/issue/${worklog.issueId}/worklog/${worklog.id}/properties").asObject(Map)
if (worklogPropertiesRequest.status == 200) {
logger.info("Worklog with id: {} has been created for issueId: {} by author: {} with properties: {}", worklog.id, worklog.issueId, worklog.author.displayName, worklogPropertiesRequest.body)
} else {
logger.error("Failed to get properties for worklog with id: {}", worklog.id)
}
Worklog Deleted
Additional Bindings available in the Script:
-
worklog - The Worklog details as a Map. See Get Worklog REST API reference for details.
logger.info("Worklog with id: {} that was initially created by: {} has been deleted by {}", worklog.id, worklog.author.displayName, worklog.updateAuthor.displayName)
Worklog Updated
Additional Bindings available in the Script:
-
worklog - The Worklog details as a Map. See Get Worklog REST API reference for details.
logger.info("Worklog with self link: {} has been updated by {}", worklog.self, worklog.updateAuthor.displayName)