Update Issue's History tab after listener script makes changes

Diana
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 20, 2022

I'm using Scriptrunner version 6.54.0 for Jira Server, and I have a custom listener script already built.

It takes a parent issue, looks for a specific link type, finds the child issues in that link type, and any updates made on the parent custom field will be copied down to the child's custom field. That part works, but whenever the listener script runs, on the child issue's History tab under Activity in Jira, it doesn't show any changes made. 

I have looked up references and other questions with similar issues like this: https://community.atlassian.com/t5/Jira-questions/Update-Custom-Value-from-Calculated-Field-using-quot/qaq-p/1059907

But their suggestions does not work. My script still doesn't update the child issue's History tab. I don't need history change to show up on the database. It should be visible for the users on the issue's History tab. 

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.customfields.manager.OptionsManager
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.Customfield
import com.atlassian.jira.issue.history.ChangeLogUtils

def customFieldManager = ComponentAccessor.getCustomFieldManager()
def issueManager = ComponentAccessor.getIssueManager()
def optionsManager = ComponentAccessor.getComponent(OptionsManager)
def issue = event.issue
def indexService = ComponentAccessor.getComponent(IssueIndexingService.class)
def changeLog = even?.changeLog
def issueChanges = issueManager.getIssueObject(issue.id)
def currentUserObj = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

if(issue.issueType.name == "Parent"){
//loop through parent links
List<IssueLink> allOutIssueLunk = ComponentAccessor.getIssueLinkManager().getInwardLinks(issue.getID())

for (Iterator<IssueLink> outIterator = allOutIssueLink.iterator(); outIterator.hasNext();0{
IssueLink issueLink = (IssueLink) outIterator.next()
//only update field if the link is a clone
if (issueLink.getIssueLinkType().getName() == "Cloners"){

MutableIssue linkedIssue = issueLink.getSourceObject() as MutableIssue

//only update child issues
if (linkedIssue.issueType.name == "Child"){

//get custom field A which is a Select List multiple choice type
def customA = customFieldManager.getCustomFieldObjectsByName("Custom A").getAt(0)
def fieldConfigA = customA.getRelevantConfig(linkedIssue)

//get value of parent
def issueValue = issue.getCustomFieldValue(customA)

//update child issue of parent's custom field
def changeHolder = new defaultIssueChangeHolder()
customA.updateValue(null, linkedIssue, new ModifiedValue(linkedIssue.getCustomFieldValue(customA), issueValue), changeHolder)

//changes should appear on issue's History tab and tell Jira to reindex so that change is searchable
ChangeLogUtils.createChangeGroup(currentUserObj, issue, issue, changeHolder.getChangeItems(), false)

boolean wasIndexing = ImportUtil.isIndexIssues()
ImportUtils.setIndexIssues(true)
indexService.reIndex(linkedIssue)
ImportUtils.setIndexIssues(wasIndexing)
}
}
}
}

I imagine the last part I'm doing something wrong. Please let me know what changes need to be made so that updates appear on issue's History tab. 

2 answers

1 accepted

0 votes
Answer accepted
Diana
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 21, 2022

After much delay, the only way I was able to get some kind of entry to work for a custom listener script was to make it add a comment in the child issue.

Final script that worked for us:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.link.IssueLink
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.customfields.manager.OptionsManager
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.Customfield
import com.atlassian.jira.issue.history.ChangeLogUtils

def customFieldManager = ComponentAccessor.getCustomFieldManager()
def issueManager = ComponentAccessor.getIssueManager()
def optionsManager = ComponentAccessor.getComponent(OptionsManager)
def issue = event.issue
def indexService = ComponentAccessor.getComponent(IssueIndexingService.class)
def changeLog = even?.changeLog
def issueChanges = issueManager.getIssueObject(issue.id)
def currentUserObj = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
def commentManager = ComponentAccessor.commentManager

if(issue.issueType.name == "Parent"){
//loop through parent links
List<IssueLink> allOutIssueLunk = ComponentAccessor.getIssueLinkManager().getInwardLinks(issue.getID())

for (Iterator<IssueLink> outIterator = allOutIssueLink.iterator(); outIterator.hasNext();0{
IssueLink issueLink = (IssueLink) outIterator.next()
//only update field if the link is a clone
if (issueLink.getIssueLinkType().getName() == "Cloners"){

MutableIssue linkedIssue = issueLink.getSourceObject() as MutableIssue

//only update child issues
if (linkedIssue.issueType.name == "Child"){

//get custom field A which is a Select List multiple choice type
def customA = customFieldManager.getCustomFieldObjectsByName("Custom A").getAt(0)
def fieldConfigA = customA.getRelevantConfig(linkedIssue)

//get value of parent
def issueValue = issue.getCustomFieldValue(customA)

//update child issue of parent's custom field
def changeHolder = new defaultIssueChangeHolder()
customA.updateValue(null, linkedIssue, new ModifiedValue(linkedIssue.getCustomFieldValue(customA), issueValue), changeHolder)

//add a comment in child issue once changes made
def customAComment = "$customA.name changed to '$issueValue' due to changes made at the parent issue $issue.key"
commentManager.create(linkedIssue, currentUserObj, customAComment, true)

boolean wasIndexing = ImportUtil.isIndexIssues()
ImportUtils.setIndexIssues(true)
indexService.reIndex(linkedIssue)
ImportUtils.setIndexIssues(wasIndexing)
}
}
}
}
0 votes
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.
July 20, 2022

You don't need to manually create the change history.

Instead of using CustomField.updateValue() method, use either IssueManager.updateIssue() or IssueService.update() methods. Either of them will take care of history tab. The seconf option will also take care of the re-indexing.

Try something like this:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.customfields.option.Option
import com.atlassian.jira.issue.link.IssueLink

def customFieldManager = ComponentAccessor.customFieldManager
def issueManager = ComponentAccessor.issueManager
def issueService = ComponentAccessor.issueService
def optionsManager = ComponentAccessor.optionsManager
def parentIssue = event.issue as Issue
def changeLog = even?.changeLog
def issueChanges = issueManager.getIssueObject(parentIssue.id)

def currentUser = ComponentAccessor.jiraAuthenticationContext.loggedInUser


if (parentIssue.issueType.name == "Parent") {
//you don't need to get custom field and option inside each iteration
def customA = customFieldManager.getCustomFieldObjectsByName("Custom A")[0]
def parentOption = parentIssue.getCustomFieldValue(customA) as Option

//loop through parent links
def allOutIssueLink = ComponentAccessor.getIssueLinkManager().getInwardLinks(parentIssue.id)
//filter links for Cloners only
allOutIssueLink = allOutIssueLink.findAll { it.issueLinkType.name == 'Cloners' }
//iterate through each remaining issueLinks
allOutIssueLink.each { issueLink ->
def linkedIssue = issueLink.sourceObject
//only update child issues
if (linkedIssue.issueType.name == 'Child') {
//get a config in case it's different than the parent
def configA = customA.getRelevantConfig(linkedIssue)

def options = optionsManager.getOptions(configA)
def childOption = options.find { it.value == parentOption.value }
assert childOption: "There is no option in $linkedIssue context for $customA.fieldName field that match $parentOption.value"

def iip = issueService.newIssueInputParameters()
iip.setSkipScreenCheck(true)
iip.addCustomFieldValue(customA.id, childOption.optionId.toString())

def validationResult = issueService.validateUpdate(currentUser, linkedIssue.id, iip)
assert validationResult.valid: validationResult.errorCollection.errors
issueService.update(currentUser, validationResult)
}
}
}
Diana
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 25, 2022

Hi @Peter-Dave Sheehan 

Thank you for sending me this script.

I've tested it, but I'm receiving an error, and now the child issue won't inherit the parent's custom field anymore.

"Script function failed on event: com.atlassian.jira.event.issue.IssueEvent, file: null...Cannot invoke method getRelevantConfig() on null object)"

I've also tried using IssueManager.updateIssue()

issueManager.updateIssue(currentUserObj, issueChanges, EventDispatchOption.ISSUE_UPDATED, false)

But still the same error. I tried using the IssueManager.updateIssue() with linkedIssue as a MutableIssue like from my original script, but still the same error. I can't copy over all the detail logs due to airgap environment. I also tried moving the getRelevantConfig method before looping through the links, but same error.

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.
July 25, 2022

This error:

Cannot invoke method getRelevantConfig() on null object

Indicate that the script failed to obtain a valid custom field objects. So it's not possible to get the configuaration for it.

I took your script as is with

def customA = customFieldManager.getCustomFieldObjectsByName("Custom A")[0]

If your custom field has a different label, you have to update that line for the appropriate custom field label

Diana
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
July 28, 2022

@Peter-Dave Sheehan I'm afraid I'll have to admit defeat on this one. I've reached out to our other engineers with suggestions, but the history tab still does not show any changes. Due to the nature of our custom script and the requirements from the teams, we'll have to come up with new solutions than try to manually change Jira.

But I appreciate your quick response! 

Suggest an answer

Log in or Sign up to answer