Rewrite Scripts with HAPI

Here you'll find useful information on how you can simplify your current scripts with HAPI, and learn some helpful tips too.

Reasons to Rewrite Current Scripts

There are many reasons you should simplify your scripts with HAPI:

  • It significantly reduces script length, making them easier to read.
  • HAPI scripts are easier to maintain, reducing the time spent troubleshooting when scripts stop working.
  • Your HAPI functions will not break when Atlassian updates their underlying API, as we maintain the functions.
  • We maintain HAPI functions, ensuring they remain unaffected when Atlassian updates their underlying API
  • Scripts that are 100% HAPI will not need modifying during the migration process. In fact, scripts built with HAPI are easier to migrate when moving to Jira Cloud. HAPI in ScriptRunner for Jira Cloud works the same as in Data Center, making migration smoother. Scripts fully written with HAPI require no modifications during migration, while those partially using HAPI are still easier to migrate compared to scripts without HAPI.

How to Simplify Scripts Using HAPI

This script automates the copying of every label from an issue to all issues linked to it, specifically targeting those with an outward link.

Example 1 Before:

groovy
// Specify the IssueKey final issueKey = "TEST-1" // Make a rest call to return the Issue def issue = get("/rest/api/2/issue/${issueKey}") .header('Content-Type', 'application/json') .asObject(Map) .body as Map def fields = issue.fields as Map // Get the labels from the issue def labels = fields.labels // Find the linked issues def linkedIssues = fields.issuelinks as List<Map> // Loop over each linked issue def successStatusByIssueKey = linkedIssues.collectEntries { Map linkedIssue -> if (!linkedIssue.outwardIssue) { def inwardIssue = linkedIssue.inwardIssue as Map return [(inwardIssue.key): "Link is not outward for linked issue ${inwardIssue.key}"] } // Update each linked issue to set the labels using the key for each linked issue def outwardIssue = linkedIssue.outwardIssue as Map def updateLinkedIssue = put("/rest/api/2/issue/${outwardIssue.key}") .header('Content-Type', 'application/json') .body([ fields: [ labels: labels ] ]) .asString() if (updateLinkedIssue.status == 204) { // Print to the log tabs what iteration the loop is on and the key of the issue that was updated logger.info("Labels on ${outwardIssue.key} issue has been set.") } else { // Print to the log tabs what iteration the loop is on and the key of the issue that was not updated logger.error("Labels on ${outwardIssue.key} issue has not been set.") } [(outwardIssue.key): (updateLinkedIssue.status == 204)] } """Status by issue key (labels copied?): ${successStatusByIssueKey}. Please see the 'Logs' tab for more information on what issues were updated."""

Example 1 After :

groovy
// Specify the Issue by key def issue = Issues.getByKey("TEST-9"); String[] labels = issue.labels.collect{it.toString()}.toArray(new String[0]); // Loop linked issue, outward links specifically def successStatusByIssueKey = issue.getOutwardLinks().collect { linkedIssue -> Issues.getByKey(linkedIssue.outwardIssue.key).update { setLabels (labels) } linkedIssue.outwardIssue.key } successStatusByIssueKey ? """Labels successfully copied to issues: ${successStatusByIssueKey}. Please see the 'Logs' tab for more information on what issues were updated.""" : "No outward links found. No labels copied."

This script enables you to select issues that match criteria specified in a JQL query and set the impediment flag on each of them.

Example 2 Before:

groovy
// Define a JQL query to search for the issues on which you want to set the impediment flag def query = "<JQLQueryHere>" // Look up the custom field ID for the flagged field def flaggedCustomField = get("/rest/api/2/field") .asObject(List) .body .find { (it as Map).name == "Flagged" } as Map // Search for the issues we want to update def searchReq = get("/rest/api/2/search") .queryString("jql", query) .queryString("fields", "Flagged") .asObject(Map) // Verify the search completed successfully assert searchReq.status == 200 // Save the search results as a Map Map searchResult = searchReq.body // Iterate through the search results and set the Impediment flag for each issue returned searchResult.issues.each { Map issue -> def result = put("/rest/api/2/issue/${issue.key}") .queryString("overrideScreenSecurity", Boolean.TRUE) .header("Content-Type", "application/json") .body([ fields:[ // The format below specifies the Array format for the flagged field // More information on flagging an issue can be found in the documentation at: // https://confluence.atlassian.com/jirasoftwarecloud/flagging-an-issue-777002748.html // Initialise the Array (flaggedCustomField.id): [ [ // set the component value value: "Impediment", ], ] ] ]) .asString() // Log out the issues updated or which failed to update if (result.status == 204) { logger.info("The ${issue.key} issue was flagged as an Impediment.") } else { logger.warn("Failed to set the Impediment flag on the ${issue.key} issue. ${result.status}: ${result.body}") } } // end of loop "Script Completed - Check the Logs tab for information on which issues were updated."

Example 2 After:

groovy
// Define a JQL query to search for the issues on which you want to set the impediment flag def query = "<JQLQueryHere>" // Iterate through the search results and set the Impediment flag for each issue returned Issues.search(query).each { issue -> issue.update { setCustomFieldValue("Flagged", "Impediment") } logger.info("The ${issue.key} issue was flagged as an Impediment.") } "Script Completed - Check the Logs tab for information on which issues were updated."

This script adds all the values of a custom field from all the issues and subtasks contained in an epic, and sets the sum as the value of the same custom field in the epic issue.

Example 3 Before:

groovy
//Epic Link Custom Field ID final epicLinkCf = get("/rest/api/2/field") .asObject(List) .body .find { (it as Map).name == 'Epic Link' } as Map logger.info("epicLinkCf : ${epicLinkCf}") //Number Custom Field ID final numberCf = get("/rest/api/2/field") .asObject(List) .body .find { (it as Map).name == 'Custom Field 1' } as Map logger.info("numberCf : ${numberCf}") def issueFields = get("/rest/api/2/issue/$issue.key") .asObject(Map) .body .fields as Map //If issue has no parent or is not related with an epic, the script will not be executed def issueParent = issueFields.find { it.key == 'parent' }?.value as Map def epicKey = issueFields.find { it.key == epicLinkCf.id }?.value logger.info("issueParent: ${issueParent}, epicKey: ${epicKey}") if (!epicKey && !issueParent) { return } logger.info("issue: ${issue}") //If the issue is sub-task, Epic Key is obtained from the parent issue if (issue.fields.issuetype.subtask) { def parentIssueField = get("/rest/api/2/issue/$issueParent.key") .asObject(Map) .body .fields as Map epicKey = parentIssueField.find { it.key == epicLinkCf.id }.value } //Obtain all related epic issues, including sub-tasks def allChildIssues = get("/rest/api/2/search") .queryString('jql', "linkedissue = $epicKey") .header('Content-Type', 'application/json') .asObject(Map) .body .issues as List<Map> def sum = 0 def issues = allChildIssues.findAll { it.key != epicKey } issues.each { def fields = get("/rest/api/2/issue/$it.key") .header('Content-Type', 'application/json') .asObject(Map) .body .fields as Map def numberCfValue = fields[numberCf.id] ?: 0 sum += numberCfValue as Integer } put("/rest/api/2/issue/$epicKey") .header("Content-Type", "application/json") .body([ fields: [ (numberCf.id): sum ] ]).asString()

Example 3 After:

groovy
//the name of the custom field. This code will work just as well with the custom field ID instead i.e. 10124L def sumCustomFieldName = 'custom field name i.e. Sum Of Value' def eventIssue = Issues.getByKey(issue.key as String) //for regular issues the parent is an Epic def epicKey = eventIssue.getParentObject()?.key ?: null if(!epicKey) { // Checks the 'Epic Link' custom field epicKey = eventIssue.getEpic()?.key ?: null } if (!epicKey && eventIssue.issueType.subtask) { epicKey = eventIssue.parentObject?.epic?.key ?: null } if (!epicKey) { logger.info("We did not find an Epic in the hierarchy of this issue. Exiting.") return } //find all issue linked to the epic, taking care to exclude the epic itself as we sum over this list def epicIssue def sum = Issues.search("linkedissue = ${epicKey}").sum { issue -> if (issue.key == epicKey) { epicIssue = issue // Store the epic issue return 0 // Exclude the epic issue from the sum } return issue.getCustomFieldValue(sumCustomFieldName) ?: 0 // Safely sum non-epic values } epicIssue.update { setCustomFieldValue(sumCustomFieldName, sum) }

Once the code has been updated you might not need some imports - hover over them to see if they’re still required in your script.


On this page