ScriptRunner: Set Value for Cascade Select from Linked issue Key

Andrew Wolpers June 22, 2022

Hey folks,

I'm spinning trying to get a scriptrunner script to work (either on Post Function or Automation). This script will process/fire when an "Automation for Jira" job picks an issue up and creates a linked issue. Essentially, the script consists of two parts:

Part 1:

From the new linked issue, get all issue links. Return with the Key of the issue links of a particular type. At this point it should only be 1 (the trigger issue) as it was just created. I have been working with something like this:

import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.ModifiedValue

IssueManager im = ComponentAccessor.getIssueManager()
MutableIssue issue = im.getIssueObject("ADEMO-11") //TODO: Get current issue instead of static issue
// For later, to get the issue its on: def issue = ComponentAccessor.issueManager.getIssueByCurrentKey(issueKey)

def issueService = ComponentAccessor.issueService
def loggedInUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser
def links = ComponentAccessor.getIssueLinkManager().getOutwardLinks(issue.getId())

def keys = ""

for( l in links) {

keys = keys + l.getDestinationObject()

}

return keys

This is great, and in the future I will be updating issue to point to the current issue.

Part 2:

A switch or if statement to compare the linked issue keys with Regex. For the sake of testing right now, I'm just using a text field: 

if(issue){

//get the value
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def cField = customFieldManager.getCustomFieldObject("customfield_18701")
def cFieldValue = issue.getCustomFieldValue(cField)

//update the value
def changeHolder = new DefaultIssueChangeHolder()
cField.updateValue(null, issue, new ModifiedValue(cFieldValue, "Testing if this works!"),changeHolder)

//TODO: Configure to use Cascading select list

}else {
return "Nothing is linked"
}

With this part, i'm able to pass a string. However, I really want to be passing what I'm returning as `keys` in part 1. I feel like I'm missing something very obvious. 

My questions:

  1. How do I get the linked issue 'keys' to populate in that field? I can get them to print in logs and console, but when I try to send them as NOT a string it silently fails.
  2. Ultimately I want to be setting the value of a cascading select list based off of the project key. I'm confident with doing the regex or compare statement, but I'm not finding any documentation for setting a cascading select. Can someone point me in the right direction, or share an example? I'm only finding ways to do this over REST.

1 answer

1 accepted

Suggest an answer

Log in or Sign up to answer
1 vote
Answer accepted
Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 22, 2022

You are overcomplicating things I think ...

Let me check if I understand your requirement.. you want to select an option in a cascading select that contains a list of Project Keys. The option to select will be determined by an issue link of a specific type.

What happens if you have more than one link of that same type? 

Do you only need to select the first field in the cascading select from the linked issue? What's the second field based on?

Here is some sample code to get the project key (no regex required) from the linked issue and how you can set that value to the cascading select field.

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.util.ImportUtils

def im = ComponentAccessor.issueManager
def lm = ComponentAccessor.issueLinkManager
def cfm = ComponentAccessor.customFieldManager
def om = ComponentAccessor.optionsManager
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser

def issue = im.getIssueObject('ADEMO-11') //in a workflow postfunction, you can just comment out this line, the script binding already includes an issue variable

def issueLinks = lm.getOutwardLinks(issue.id)

def linkType = 'Cloners' //specify the link type name you want to filter your issue links with

def
issueLinksOfCorrectType = issueLinks.findAll{it.issueLinkType.name == linkType}

//issueLink object contain a destinationObject of type issue, you can ket the key specifically from that issue object
def
listOfLinkedIssueKeys = issueLinksOfCorrectType.collect{it.destinationObject.key}

//and further, you can drill into the issue object's project object and get the project Key
def
listOfProjectKeys = issueLinksOfCorrectType.collect{it.destinationObject.projectObject.key}

def cascadingSelectCfId = 18702 //or whatever number
def csFieldCf = cfm.getCustomFieldObject(cascadingSelectCfId)

def config = csFieldCf.getRelevantConfig(issue) //this is the configuration that contains all your options
def options = om.getOptions(config)

//getting the option that corresponds to the project of the first linked issue
def projectKeyOption = options.find{it.value == listOfProjectKeys[0]}

//change the issue object (in memory only)
issue.setCustomFieldValue(csFieldCf, [null:projectKeyOption])
//if you know the "child option" to select, it would look like this
//issue.setCustomFieldValue(csFieldCf, [null:projectKeyOption, '1':childOption])

//this saves the changed issue to the db
im.updateIssue(currentUser,issue, EventDispatchOption.DO_NOT_DISPATCH,false)

//now we have to re-index the issue, this doesn't happen automatically after you update
def wasIndexing = ImportUtils.indexIssues
ImportUtils.indexIssues = true
ComponentAccessor.getComponent(IssueIndexingService).reIndex(issue)
ImportUtils.indexIssues = wasIndexing


Note that the "cField.updateValue()"  method is rarely indicated or recommended. My preference is to use the issueManager.updateIssue() method and manually re-index.

You could alternatively also use the issueService

def is = ComponentAccessor.issueService
def iip = is.newIssueInputParameters()
iip.addCustomFieldValue(csFieldCf.id, projectKeyOption.optionId.toString())
//if you know the child option
iip.addCustomFieldValue(csFieldCf.id + ':1', projectKeyOption.optionId.toString())

def validateResult = is.validateUpdate(currentUser, issue.id, iip)
if (validateResult.valid) {
//this will update the issue and re-index it
is.update(currentUser, validateResult, EventDispatchOption.DO_NOT_DISPATCH, false)
}
Andrew Wolpers June 22, 2022

Thanks Peter, this is super helpful. It's very close, but I think I didn't lay out my use case well enough. Essentially, here's what we're doing:

  1. In a separate automation, a user transitions an issue. This can be a number of various projects that rely on the shared service team. If that issue meets certain conditions, it will create and link an issue (via automation plugin) in project FOO.
  2. When the issue is created in FOO, it has the user who created the request- but it does not capture the team information. Currently, they are manually setting this team information in the Cascading Select List field (This field is called "Request From", the top level of the field is the greater department and the child-level is the "Team")
  3. We want to create a map to use the value we receive from the IssueLinks and trigger a Switch or If/Else statement

For example, if the BAR team were to have an issue sent over to the FOO team, we would do something like:

...

if (linkedIssueKeys =~BAR^){
issue.setCustomFieldValue(csFieldCf, [Marketing:Bar Team]
}
else if (linkedIssueKeys =~GIT^){
issue.setCustomFieldValue(csFieldCf, [Engineering:Red Team]
}
...

Or something of the like. Essentially, we just want to identify if there is a linked issue at the time of creation AND use that linked issue's key to set the value(s) based off a pre-defined statement.

Hope that helps, I appreciate the examples and insight so far. 

Peter-Dave Sheehan
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
June 22, 2022

I would use a map object rather than a series of if-else or switch.

Try something like this:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.util.ImportUtils

def im = ComponentAccessor.issueManager
def is = ComponentAccessor.issueService
def lm = ComponentAccessor.issueLinkManager
def cfm = ComponentAccessor.customFieldManager
def om = ComponentAccessor.optionsManager
def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser

def projectTeamMap = [
BAR: [dept: 'Marketing', team: 'Bar Team'],
GIT: [dept: 'Engineering', team: 'Red Team'],
// repeat as needed
]
def linkType = 'Cloners' //specify the link type name you want to filter your issue links with
def cascadingSelectCfId = 18702 //or whatever number

def issue = im.getIssueObject('ADEMO-11') //in a workflow postfunction, you can just comment out this line, the script binding already includes an issue variable

def issueLinks = lm.getOutwardLinks(issue.id)
def issueLinksOfCorrectType = issueLinks.findAll { it.issueLinkType.name == linkType }

def listOfProjectKeys = issueLinksOfCorrectType.collect { it.destinationObject.projectObject.key }
assert listOfProjectKeys: "No linked issue of type $linkType were found"
def projectKey = listOfProjectKeys.first()

def csFieldCf = cfm.getCustomFieldObject(cascadingSelectCfId)
def config = csFieldCf.getRelevantConfig(issue)
def options = om.getOptions(config)

//here we get the department and team from the map using the project key
def
department = projectTeamMap[projectKey].dept
def
team = projectTeamMap[projectKey].team

assert team && department : "$projectKey is not defined in projectTeamMap: ${projectTeamMap.keySet()}"

def departmentOption = options.find{it.value == department}
def teamOption = departmentOption.childOptions.find{it.value == team}

def iip = is.newIssueInputParameters()
iip.addCustomFieldValue(csFieldCf.id, departmentOption.optionId.toString())
iip.addCustomFieldValue(csFieldCf.id + ':1', teamOption.optionId.toString())

def validateResult = is.validateUpdate(currentUser, issue.id, iip)
assert validateResult.valid : validateResult.errorCollection
def updateResult = is.update(currentUser, validateResult, EventDispatchOption.DO_NOT_DISPATCH, false)
assert updateResult.valid : updateResult.errorCollection

I added some power assertion to help catch error cases. 

Andrew Wolpers June 23, 2022

A map definitely makes more sense, and that appears to have done the trick. The only updates I needed to make were to fit my use case of using Inward links instead of outward.

EDIT: Unfortunately I now understand why you were using outward instead of inward. Unfortunately, although the link and link type appears to exist, it fails when collecting with the following error:

java.lang.AssertionError: No linked issue of type Relates were found. Expression: listOfProjectKeys. Values: listOfProjectKeys = [] at org.codehaus.groovy.runtime.InvokerHelper.assertFailed(InvokerHelper.java:417) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.assertFailed(ScriptBytecodeAdapter.java:670) at Script1645.run(Script1645.groovy:27) at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:317) at org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.eval(GroovyScriptEngineImpl.java:155) at java.scripting/javax.script.AbstractScriptEngine.eval(Unknown Source)

As you can see below, there is at least one link present on my test issue and I have updated the link type to "Relates" to reflect this:

Screen Shot 2022-06-23 at 8.48.25 AM.png

For some reason, getting these links in a meaningful way is my kryptonite. Sincerely appreciate all the help so far! 

TAGS
AUG Leaders

Atlassian Community Events