Getting Started

JIRA Extension Points

Script Listeners respond to one or more webhook events. Webhooks are fired after an action takes place in JIRA such as when an issue is updated or a project is created. Using Script Listeners it is possible to perform actions such as creating initial issues for a project or notifying users outside of the standard notification scheme about changes to issues.

scriptListener

Run As 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.

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 bellow 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()
1 First check to see if the issue is in the required project
2 Grab the values of the custom fields from the issue
3 Sanity check for null values - the issue may not have a value for any input
4 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
5 Update the value of the custom field
6 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.

Post to HipChat on Version Release

The following example will post to HipChat when a version is released. It is designed to be used with the Version Released event. It demonstrates the use of MarkupBuilder and interacting with outher services

import groovy.xml.MarkupBuilder
import static io.github.openunirest.http.Unirest.get
import static io.github.openunirest.http.Unirest.post

def apiToken = "YOUR_SECRET_TOKEN" (1)

def searchResult = get("/rest/api/2/search") (2)
    .queryString("jql", "fixVersion = ${version.id}")
    .queryString("startAt", 0)
    .queryString("maxResults", 1000)
    .asObject(Map)
    .body

def writer = new StringWriter()
def markupBuilder = new MarkupBuilder(writer)

def writeIssues = { String msg, List<Map> issues ->
    markupBuilder.p ("The following ${issues.size()} issue(s) were $msg in released version ${version.name}") {
        issues.each { issue ->
            ul {
                li {
                    a(href: issue.self.toString(), issue.key)
                    span(" - ${issue.fields.summary}")
                }
            }
        }
    }
} (3)

if (searchResult.total) { (4)
    // group by resolved, unresolved
    searchResult.issues.groupBy { it.resolution as boolean }.sort { !it.key }.each { issueSet ->
        if (issueSet.key) {
            writeIssues("resolved", issueSet.value)
        } else {
            writeIssues("NOT resolved", issueSet.value)
        }
    }
}
else {
    markupBuilder.p("No issues were included in released version: ${version.name}.")
}

Unirest.clearDefaultHeaders() (5)
post("https://api.hipchat.com/v2/room/test room/notification")
        .header("Content-Type", "application/json")
        .queryString('auth_token', apiToken)
        .body([
                message       : writer.toString(),
                notify        : false,
                message_format: "html"
        ])
        .asString() (6)
1 Generate a HipChat API token and use it here
2 Search for all issues that had the released version as a fixVersion, setting maxResults to e the maximum possible. It is assumed that no more than 1000 issues are in this version.
3 Build a HTML list of the issues that were released
4 Finish up the HTML with a summary of the number of issues that were released
5 Clear the Unirest default headers to remove the JWT token header
6 Make the POST request to hipchat with the auth_token

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()
1 First get all Issue Types to find the Task type, we will use this id to create all our tasks
2 Create a single issue of type task
3 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.

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

List<Map> histories = get(issue.self) (1)
        .queryString('expand', 'changelog')
        .asObject(Map)
        .body
        .changelog
        .histories

Map priorityChange = histories.isEmpty() ? null : histories
        .last()
        .items
        .find { it['field'] == 'priority' }

if (priorityChange?.to as Integer == 1) { (2)
    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 { (3)
        p {
            // update url below:
            a(href: "http://myjira.atlassian.net/issue/${issue.key}", issue.key) (4)
            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() (5)

    def resp = post("/rest/api/2/issue/${issue.id}/notify") (6)
        .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
}
1 The issue object passed with the event doesn’t have the changelog, so we need to fetch it again to get it. Once we have it we find the last history change, and then find any items where the priority field changed
2 Only do anything here if the priority change was to Highest
3 Generate a HTML message using MarkupBuilder
4 The issue URI here is created manually. It should be updated
5 Quick hack to produce a text only message from the HTML string
6 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 Subtask Estimates in Parent issue on Issue events

When a subtask 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 subtask 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
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')) {
2 Only retrieve the fields we’re interested in, to speed the search up
3 Here we handle the scenario where the timeestimate is not set
4 This line means we don’t need to put the custom field ID in our script, we can refer to it by name

Store Subtask Story Points Summed value in Parent issue on Issue events

When a subtask 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 subtask 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
1 Stop running the script if this issue isn’t a subtask.
2 Here we only retrieve the story points field to make the search faster.
3 Here we get the story points value of each sub task so that they can be summed together.
4 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 Subtasks in the Parent

Similar to the example above, you might want to store the number of subtasks in the parent issue when a subtask 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
1 Stop running the script if this issue isn’t a subtask.
2 You could extend this query string to only return unresolved subtasks, or subtasks of a particular type etc
3 We don’t need any fields because we just want the total number of results
4 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

Additional Examples

Attachment Created

Additional Bindings available in the Script:

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:

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:

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:

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:

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:

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:

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:

logger.info("Issue: {} has been updated by: {} in project: {}", issue.key, user.displayName, issue.fields.project.key)

Additional Bindings available in the Script:

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)

Additional Bindings available in the Script:

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

Additional Bindings available in the Script:

  • property - The JIRA configuration as a Map.

Example code will be delivered soon.

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 Subtasks 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

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

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

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:

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

Additional Bindings available in the Script:

logger.info("Project: {} that was led by {} has been deleted.", project.key, project.projectLead.displayName)

Project Updated

Additional Bindings available in the Script:

logger.info("Project: {} that is led by {} has been updated.", project.key, project.projectLead.displayName)

Sprint Closed

Additional Bindings available in the Script:

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:

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:

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:

/** 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:

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:

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.name]
                                ]
                        ]
                ]
        ])
        .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:

logger.info('User with name: {} and key: {} has been deleted.', user.name, user.key)

User Updated

Additional Bindings available in the Script:

logger.info('User with name: {}, key: {}, emailAddress: {} and displayName: {} has been updated.', user.name, user.key, user.emailAddress, user.displayName)

Version Created

Additional Bindings available in the Script:

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:

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:

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:

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:

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:

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:

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:

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:

logger.info("Worklog with self link: {} has been updated by {}", worklog.self, worklog.updateAuthor.displayName)