Rewriting Scripts for Cloud Hints and Tips

Mandatory script rewrite

Be aware that any migration from Server to Cloud (manual or through the Atlassian Cloud Migration Assistant) will require rewriting scripts.

Automatic migration from Server/Data Center to Cloud is not possible due to differences in the programming model and the API between the two products. The process of migration involves the manual rewriting of all scripts to make them compatible with the Cloud platform. 

Every migration is different, but we have collated some useful hints and tips here to help you rewrite your scripts. 

Before You Start

Platform Differences

Jira Cloud uses the Atlassian Connect framework whereas Jira Server/Data Center use the Atlassian Plugins framework, known as Plugins v2 (or P2). There are significant differences between the Connect and P2 frameworks which we have documented here. We recommend you familiarize yourself with these differences before you begin rewriting your scripts.

Feature Parity

Some features available in ScriptRunner for Jira Server/Data Center are not available in ScriptRunner for Jira Cloud due to differences in the platform (explained in the Platform Differences page). See our Feature Parity and Script Alternatives table before starting to rewrite your scripts to check which features have full or partial parity. 


Don’t have the time or capacity to write scripts in-house? Get your Server scripts translated to Cloud without writing a single line of code yourself. Check out our Scripting Service.

shortcut Scripting Service


Where to Start

As a general rule, we suggest you follow the steps set out below when rewriting your Server scripts for Cloud:

  1. List the Java API calls (classes and specific methods you consider important) used in the Server script with brief summary of what they do.
  2. Identify the types of fields that will need to be updated, and check these are available in Cloud
  3. Look at Atlassian Jira Rest API and note down the API endpoints you expect to need as well as relevant body parameters.
  4. Investigate how to update issues in Jira Cloud. The biggest challenge is usually working out the format required for each field type when sending the update JSON.
    Investigation Process:

    1. Create a test Jira Cloud instance here and install a trial version of ScriptRunner.
    2. Create a Test Jira project and a few issues, then navigate to the first new issue you created.
    3. On the Issue View page click the three dot menu in the top right and choose to See the old view then click Admin → Add field
    4. Create the types of the fields we want to test with the Add field button and populate them with values for the current issue.
    5. Use a GET request to the `/issue` endpoint to see how the values are structured in JSON with a rest API tool like curl or Postman.
    6. With this information, we can then plan the expected Body parameters required to update the same fields with a PUT request.
  5. Research the ScriptRunner for Jira Cloud style for writing the required HTTP calls and then rewrite your script.
    1. Start by looking into the library we use in ScriptRunner for Jira Cloud, this is called Unirest. We auto-import this into the scripts, so you can simply call the get(), put(), post() methods mentioned in the Unirest documentation.

    2. Next, take a look at examples of ScriptRunner for Jira Cloud scripts in the Adaptavist Library. This will give you an idea of the style, and code for several common use cases.

    3. Finally, for more working examples, go to the Script Console inside your ScriptRunner for Jira Cloud instance and browse through the example scripts in the Examples drop-down under the script input area.

Quick Tips

Here are are some quick tips that apply to a variety of use cases:

  • If you are unsure how an issue represents its selected values in a REST response, create a test issue and run a simple GET request for that issue, for example:

    https://YourCloudURL.atlassian.net/rest/api/2/issue/YourIssueKey
  • When translating ScriptRunner for Jira Server/Data Center scripts to ScriptRunner for Jira Cloud you will find that a lot of the complex objects can be ignored entirely. This is because complex Java methods with multiple params in Server can be replaced by fairly simple REST calls with structured body parameters in Cloud.

  • You generally cannot run scripts as another user in Cloud (with the exception of the ScriptRunner add-on user). You can pass user account IDs as params to some REST calls, but the calls themselves will run as either the executing user or the ScriptRunner add-on user.
  • You do not need to define REST request authentication headers in ScriptRunner for Jira Cloud scripts as these are already set up for you. The scripts will run as the user that triggers the script, or the ScriptRunner add-on user. These are controlled by a simple drop-down within the ScriptRunner script configuration UI.
  • You can update most fields with string versions of the value rather than having to find option IDs as you would often have to in ScriptRunner for Server/Data Center scripts.

Commonly Used Atlassian Java API Endpoints and their Cloud Equivalents 

This table maps some of the most commonly used Atlassian Java API's (in ScriptRunner for Jira Server/Data Center) to the closest Atlassian REST API endpoints (ScriptRunner for Jira Cloud) to guide you in script conversions.

Java API (Server)REST API (Cloud)
IssueService

Getting issues: GET /rest/api/2/issue/{issueIdOrKey}

Updating issues: PUT /rest/api/2/issue/{issueIdOrKey}

issueService.newIssueInputParameters()

You don't need to do this in cloud, you just send a JSON structure of the fields you want to change in the PUT requests body parameters. For example:

fields: [ (fieldId): newValue, // Text Field ]
issueService.validateUpdateYou don't need to do validation like this prior to updating issues in Jira Cloud as the endpoint itself will return an error if what you try to do is invalid.
issueService.updateTo update issues use: PUT /rest/api/2/issue/{issueIdOrKey}
IssueManager

To get issues use: GET /rest/api/2/issue/{issueIdOrKey}

jiraAuthenticationContext

Used by Server to get user who will run a Java function. This is not required in ScriptRunner for Jira Cloud as you choose to run scripts as the Current User or ScriptRunner Addon User only.

You may need to pass user ID's in the REST API body parameters, but running a script as a user other than Current User or ScriptRunner Add-on User is not currently possible in Cloud.

customFieldManager

To get fields use: GET /rest/api/2/field

To create fields use: POST (create fields) /rest/api/2/field

Do not try to use this endpoint to update values of fields, use the issues PUT endpoint.

optionsManager

Updating field options: Currently, these endpoints are all experimental for Jira Cloud so you may not be able to do exactly the same thing as in Jira Server.

Refer to all the experimental endpoints here.

ProjectManager

Get projects with a paginated search using project Key or Name: GET  /rest/api/2/project/search

Get a project by id or key: GET /rest/api/2/project/{projectIdOrKey}

Update a project by id or key: PUT /rest/api/2/project/{projectIdOrKey}

Delete a project by id or key: DELETE /rest/api/2/project/{projectIdOrKey}

VersionManager

Get all versions for a project: GET /rest/api/2/project/{projectIdOrKey}/versions

Create versions for a project: POST /rest/api/2/version

Update versions within a project: PUT (Update versions) /rest/api/2/version/{id}

Delete/Replace versions in a project: POST /rest/api/2/version/{id}/removeAndSwap

WatcherManager

Get Watchers for an issue: GET /rest/api/2/issue/{issueIdOrKey}/watchers

Add watchers to an issue: POST (Add wachers) /rest/api/2/issue/{issueIdOrKey}/watchers

Delete watchers from an issue: DELETE /rest/api/2/issue/{issueIdOrKey}/watchers

ProjectRoleservice / ProjectRoleManager

Control role actors PUT /rest/api/2/project/{projectIdOrKey}/role/{id}

Get a projects roles GET /rest/api/2/project/{projectIdOrKey}/role

Delete project roles DELETE /rest/api/2/role/{id}

GroupManager

Create a group: POST /rest/api/2/group

Delete a group: DELETE /rest/api/2/group

Get members of a group GET /rest/api/2/group/member

Add user to group POST /rest/api/2/group/user

Remove user from group DELETE /rest/api/2/group/user

UserManager

Get all users GET /rest/api/2/users/search

Search users with query GET /rest/api/2/user/search

User creation and deletion is in experimental state.

It is not possible to find a user by name in Cloud, only by their email or user ID (the automatic ID generated by Jira).

SearchService

Search with JQL using rest GET /rest/api/2/search

If the JQL is too large for a query param use POST /rest/api/2/search

IssueTypeManager

Get all issue types the executing user has permission to see GET /rest/api/2/issuetype

PriorityManager

Get all Issue Priorities GET /rest/api/2/priority

IssueSecurityLevelManager / IssueSecuritySchemeManager

You need the ID to get a security level so you have to follow this:

  1. As an admin user Get all security schemes: GET /rest/api/2/issuesecurityschemes
  2. Look for the name of your  required scheme and get its ID.
  3. Use GET /rest/api/2/securitylevel/{id} to get the security level.

Field Availability

All standard ScriptRunner for Jira field types are available in Cloud. The only caveat is that Project Picker fields are only available as single-select fields. 

Cloud custom field and advanced field types are listed here.

Common Listener Events Availability

Below is a list of common listener events in ScriptRunner for Jira Server/Data Center and their availability in Cloud. More detail on what ScriptRunner can do with the supported Cloud events is documented here.

Server/DC EventAvailable?Notes
Issue CreatedY
Issue UpdatedY
Issue DeletedY
Issue AssignedN
Issue ResolvedN
Issue ClosedN
Issue ReopenedN
Issue MovedN
Issue Link CreatedY
Issue Link DeletedY
Issue Watcher AddedN
Issue Watcher DeletedN
Issue ArchivedNNot a Cloud feature
Comment CreatedY
Comment UpdatedY
Comment DeletedY
Project CreatedY
Project UpdatedY
Project DeletedY
Project Component eventsN
Project Role eventsN
Version CreatedY
Version UpdatedY
Version DeletedY
Version MovedY
Version ReleasedY
Version unreleasedY
User CreatedY
User DeletedY
User EditedAvailable as User Updated in Cloud
Group CreatedN
Group UpdatedN
Group DeletedN

Common Operations

Here we have listed some common operations required in ScriptRunner for Jira Cloud scripts, switch between the tabs to show the Cloud or Server script. These operations can be utilized for many use cases. 

Get field name to ID map

groovy
def fieldNameToIdMap def fields = get('/rest/api/2/field') .header('Content-Type', 'application/json') .asObject(List) if (fields.status == 200){ fieldNameToIdMap = fields.body.collectEntries { [(it.name): it.id] } } else { return "Failed to generate fields map ${fields.status} ${fields.body}" }
groovy
/* For Server/DC you will not likely need to get a map of field names to ID for custom fields as you can just use this simple method to get a collection CustomField objects and then filter by name as shown in the next example. */ import com.atlassian.jira.component.ComponentAccessor def fields = ComponentAccessor.customFieldManager.getCustomFieldObjects()

Get a single field ID with its name

groovy
final customFieldName = 'TextFieldA' def fieldId def fields = get('/rest/api/2/field') .header('Content-Type', 'application/json') .asObject(List) if (fields.status == 200){ fieldId = fields.body.find { it.name == customFieldName }.id } else { return "Failed to generate fields map ${fields.status} ${fields.body}" }
groovy
import com.atlassian.jira.component.ComponentAccessor final FIELD_NAME = 'TextFieldA' def myFieldId = ComponentAccessor.customFieldManager.getCustomFieldObjects().find { it.name == FIELD_NAME }?.id

Update Fields

groovy
def issueKey = 'TEST-2' def sendNotification = false // control if notification email is sent to all watchers def fieldNameToIdMap def fields = get('/rest/api/2/field') .header('Content-Type', 'application/json') .asObject(List) if (fields.status == 200){ fieldNameToIdMap = fields.body.collectEntries { [(it.name): it.id] } } else { return "Failed to generate fields map ${fields.status} ${fields.body}" } def result = put("/rest/api/2/issue/${issueKey}") //.queryString("overrideScreenSecurity", Boolean.TRUE) .header('Content-Type', 'application/json') .body([ notifyUsers: sendNotification, fields: [ (fieldNameToIdMap['SelectListA']): [value: "BBB"], //Single Select List (fieldNameToIdMap['MultiSelectListA']): [[value: "BBBB"], [value: "CCCC"]], //Multi Select List (fieldNameToIdMap['RadioButtonA']): [value: "Maybe"], // Radio Buttons (fieldNameToIdMap['CheckBoxA']): [[value: "Maybe"], [value: "No"]], // Checkboxes (fieldNameToIdMap['TextFieldA']): "QWERTY", // Text Field (fieldNameToIdMap['UserPickerA']): ["accountId": "5b9a84022d389f762bd0bd23"], // Single User Picker (fieldNameToIdMap['MultiUserPickerA']): [["accountId": "5b9a84022d389f762bd0bd23"]],// Multi User Picker (fieldNameToIdMap['DateTimePickerA']): "2021-10-16T15:41:00.000+0100", // Date Time Field (fieldNameToIdMap['DatePickerA']): "2021-10-11", // Date Field (fieldNameToIdMap['ProjectPickerA']): [key: "TP"], // Project Picker (fieldNameToIdMap['LabelFieldA']): ["here", "test"], // Labels field (fieldNameToIdMap['VersionPickerA']): [name: "testv1"], // Single Version Picker (fieldNameToIdMap['MultiVersionPickerA']): [[name: "testv1"], [name: "anotherv2"]], // Multi Version Picker (fieldNameToIdMap['GroupPickerA']): [name: "jira-software-users-mattdevtest"], // Single Group Picker (fieldNameToIdMap['MultiGroupPickerA']): [ //Multi-Group Picker [name:"jira-servicemanagement-users-mattdevtest"], [name:"jira-software-users-mattdevtest"], [name:"jira-workmanagement-users-mattdevtest"], ], ] ]) .asString() if (result.status == 204) { return 'Success' } else { return "${result.status}: ${result.body}" }
groovy
// Example taken from https://library.adaptavist.com/entity/update-the-value-of-custom-fields-through-the-script-console with added multi-version picker example import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.event.type.EventDispatchOption import com.atlassian.jira.issue.fields.CustomField import groovy.transform.Field // the issue key to update @Field final String issueKey = "Test-1" // the name of a 'single select list' custom field final String selectList = "SelectListA" // the name of a 'multi select list' custom field final String multiSelectList = "MultiSelectA" // name of a 'radio button' custom field final String radioButtonField = "RadioButtons" // name of a 'check box' custom field final String checkboxField = "Checkboxes" // the name of a 'text field' custom field final String textField = "TextFieldA" // the name of a 'user picker' custom field final String userPicker = "UserPicker" // the name of a 'multi user picker' custom field final String multiUserPicker = "MultiUserPickerA" // the name of a 'group picker' custom field final String groupPicker = "GroupPicker" // the name of a 'multi group picker' custom field final String multiGroupPicker = "MultiGroupPicker" // the name of a 'date and time' custom field final String dateTimeField = "First DateTime" // the name of a 'date' custom field final String dateField = "Date" // the name of a 'project picker' custom field final String projectPickerField = "ProjectPicker" // the name of a 'label' picker custom field final String labelField = "LabelField" // name of a 'single version picker' custom field final String versionField = "VersionPicker" // name of a 'multi version picker' custom field final String multiVersionField = "VersionsPicker" // change to 'true' if you want to send an email if the update is successful final boolean sendMail = false def issueService = ComponentAccessor.issueService def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser def issue = ComponentAccessor.issueManager.getIssueByCurrentKey(issueKey) assert issue: "Could not find issue with key $issueKey" def issueInputParameters = issueService.newIssueInputParameters().with { // set custom fields with options (select lists, checkboxes, radio buttons) addCustomFieldValue(getSingleCustomFieldByName(selectList).id, *getOptionIdsForFieldByValue(selectList, "BBB")) addCustomFieldValue(getSingleCustomFieldByName(multiSelectList).id, *getOptionIdsForFieldByValue(multiSelectList, "BBB", "CCC")) addCustomFieldValue(getSingleCustomFieldByName(radioButtonField).id, *getOptionIdsForFieldByValue(radioButtonField, "Yes")) addCustomFieldValue(getSingleCustomFieldByName(checkboxField).id, *getOptionIdsForFieldByValue(checkboxField, "Maybe", "Yes")) // set text fields addCustomFieldValue(getSingleCustomFieldByName(textField).id, "New Value") // set user fields addCustomFieldValue(getSingleCustomFieldByName(userPicker).id, "admin") addCustomFieldValue(getSingleCustomFieldByName(multiUserPicker).id, "admin", "anuser") // set group fields addCustomFieldValue(getSingleCustomFieldByName(groupPicker).id, "jira-users") addCustomFieldValue(getSingleCustomFieldByName(multiGroupPicker).id, "jira-users", "jira-administrators") // set custom field of type date addCustomFieldValue(getSingleCustomFieldByName(dateTimeField).id, "04/Feb/12 8:47 PM") addCustomFieldValue(getSingleCustomFieldByName(dateField).id, "04/Feb/12") } // set project picker field def project = ComponentAccessor.projectManager.getProjectObjByKey("SSPA") assert project: "Could not find project" issueInputParameters.addCustomFieldValue(getSingleCustomFieldByName(projectPickerField).id, project.id.toString()) // set custom field of type label issueInputParameters.addCustomFieldValue(getSingleCustomFieldByName(labelField).id, "foo", "bar") // set custom field of type version picker def versionOne = ComponentAccessor.versionManager.getVersions(issue.projectObject).findByName("Version1") assert versionOne: "Could not find version" issueInputParameters.addCustomFieldValue(getSingleCustomFieldByName(versionField).id, versionOne.id.toString()) // set custom field of type multi-version picker def versionTwo = ComponentAccessor.versionManager.getVersions(issue.projectObject).findByName("Version2") assert versionTwo: "Could not find version" issueInputParameters.addCustomFieldValue(getSingleCustomFieldByName(multiVersionField).id, versionOne.id.toString(), versionTwo.id.toString()) def updateValidationResult = issueService.validateUpdate(loggedInUser, issue.id, issueInputParameters) assert updateValidationResult.valid: updateValidationResult.errorCollection def issueUpdateResult = issueService.update(loggedInUser, updateValidationResult, EventDispatchOption.ISSUE_UPDATED, sendMail) assert issueUpdateResult.valid: issueUpdateResult.errorCollection /** * Get a custom field given a custom field name. * If there are than one custom fields with the same name under the same Context then return the first one. * @param fieldName The name of the custom field * @param issue The issue to look for that custom field * @return the custom field, if that exists */ CustomField getSingleCustomFieldByName(String fieldName) { def issue = ComponentAccessor.issueManager.getIssueByCurrentKey(issueKey) def customField = ComponentAccessor.customFieldManager.getCustomFieldObjects(issue).findByName(fieldName) assert customField: "Could not find custom field with name $fieldName" customField } /** * Given a custom field name and option values, retrieve their ids as String * @param customFieldName The name of the custom field * @param values The values in order to get their ids * @return List < String > The ids of the given values */ List<String> getOptionIdsForFieldByValue(String customFieldName, String... values) { def issue = ComponentAccessor.issueManager.getIssueByCurrentKey(issueKey) def customField = getSingleCustomFieldByName(customFieldName) ComponentAccessor.optionsManager.getOptions(customField.getRelevantConfig(issue)).findAll { it.value in values.toList() }*.optionId*.toString() }

Perform JQL Searches

groovy
def query = 'project = TEST' def searchReq = get("/rest/api/2/search") .queryString("jql", query) .asObject(Map) assert searchReq.status == 200 // Save the search results as a Map Map searchResult = searchReq.body // print all the issue keys just to demonstrate what was found searchResult.issues*.key
groovy
// Example from https://library.adaptavist.com/entity/perform-a-jql-search-in-scriptrunner-for-jira import com.atlassian.jira.bc.issue.search.SearchService import com.atlassian.jira.component.ComponentAccessor import com.atlassian.jira.issue.search.SearchException import com.atlassian.jira.web.bean.PagerFilter import org.apache.log4j.Level // Set log level to INFO log.setLevel(Level.INFO) // The JQL query you want to search with final jqlSearch = "project = TEST" // Some components def user = ComponentAccessor.jiraAuthenticationContext.loggedInUser def searchService = ComponentAccessor.getComponentOfType(SearchService) // Parse the query def parseResult = searchService.parseQuery(user, jqlSearch) if (!parseResult.valid) { log.error('Invalid query') return null } try { // Perform the query to get the issues def results = searchService.search(user, parseResult.query, PagerFilter.unlimitedFilter) def issues = results.results issues.each { log.info(it.key) } issues*.key } catch (SearchException e) { e.printStackTrace() null }

Related Content

On this page