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 
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()

  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.

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.

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) 
    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
}

  1. Only do anything here if the priority change was to Highest

  2. Generate a HTML message using MarkupBuilder

  3. The issue URI here is created manually. It should be updated

  4. Quick hack to produce a text only message from the HTML string

  5. 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

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

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

  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

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:

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)

Issuelink Created

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)

Issuelink Deleted

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

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:


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.lead.displayName)

Project Updated

Additional Bindings available in the Script:


logger.info("Project: {} that is led by {} has been updated.", project.key, project.lead.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.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:


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

User Updated

Additional Bindings available in the Script:


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

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)