In a server script, it is typical to call issue.setX() many times to update several fields of an issue at a time. When using ScriptRunner for Jira Cloud these updates should be put into one issue update POST call to ensure that they are all included in the same changeset. Multiple calls to update the issue will issue multiple notifications which is not desirable. For example, see the following two scripts which are from Workflow Functions:

import com.atlassian.Jira.component.ComponentAccessor

import java.sql.Timestamp

def versionManager = ComponentAccessor.getVersionManager()
def projectComponentManager = ComponentAccessor.getProjectComponentManager()
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def userUtil = ComponentAccessor.getUserUtil()

def project = issue.getProjectObject()

def version = versionManager.getVersion(project.getId(), "1.1")
def component = projectComponentManager.findByComponentName(project.getId(), "MyComponent")

if (version) {
    issue.setFixVersions([version])
}

if (component) {
    issue.setComponentObjects([component])
}

// a text field
def textCf = customFieldManager.getCustomFieldObject("customfield_12345")
issue.setCustomFieldValue(textCf, "Some text value")

// a date time field - add 7 days to current datetime
def dateCf = customFieldManager.getCustomFieldObject("customfield_12346") // Date time fields require a Timestamp
issue.setCustomFieldValue(dateCf, new Timestamp((new Date() + 7).time))

// a user custom field
def userCf = customFieldManager.getCustomFieldObject("customfield_12347")
issue.setCustomFieldValue(userCf, userUtil.getUserByName("admin")) // User CFs require an ApplicationUser

// system fields
issue.setDescription("A generated description")
issue.setDueDate(new Timestamp((new Date() + 1).time)) // set due date to tomorrow
// get custom fields
def customFields = get("/rest/api/2/field")
        .asObject(List)
        .body
        .findAll { (it as Map).custom } as List<Map>

def toUpdate1CfId = customFields.find { it.name == 'Custom Field 1' }?.id
def toUpdate2CfId = customFields.find { it.name == 'Custom Field 2' }?.id
def toUpdate3CfId = customFields.find { it.name == 'Custom Field 3' }?.id

def tomorrowStr = (new Date() + 1).format("yyyy-MM-dd'T'HH:mm:ssZ", TimeZone.getTimeZone("UTC")) // date format in iso8601
def resp = put("/rest/api/2/issue/\${issue.key}")
        .header('Content-Type', 'application/json')
        .body([
        fields: [
                fixVersions      : ['1.1'],
                component        : ['MyComponent'],
                description      : 'A generated description',
                (toUpdate1CfId): 'Some text value',
                (toUpdate2CfId): tomorrowStr,
                (toUpdate3CfId): 'admin'
        ]
])
        .asString()
assert resp.status == 204
GROOVY



Notice that the Cloud script is much shorter, and all of the updates occur in the same API call. There are no managers involved or other API calls. The documentation for the update is much simpler and smaller than the equivalent server implementation too, however, there are a couple of things that should be pointed out. The fixVersion and component must exist or the whole update will fail, and the fields must be visible on screens.

Fields must be visible on the edit screens for them to be updated by the issue edit API.

Assertions for response codes will print out the response body and relevant information. A good pattern is to use assert resp.status == 200 (replace 200 with the correct response code). Successful APIs call may respond with 204, others with 200, 201 or 303 depending on the API in use.