Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in
Celebration

Earn badges and make progress

You're on your way to the next level! Join the Kudos program to earn points and save your progress.

Deleted user Avatar
Deleted user

Level 1: Seed

25 / 150 points

Next: Root

Avatar

1 badge earned

Collect

Participate in fun challenges

Challenges come and go, but your rewards stay with you. Do more to earn more!

Challenges
Coins

Gift kudos to your peers

What goes around comes around! Share the love by gifting kudos to your peers.

Recognition
Ribbon

Rise up in the ranks

Keep earning points to reach the top of the leaderboard. It resets every quarter so you always have a chance!

Leaderboard

Come for the products,
stay for the community

The Atlassian Community can help you and your team get more value out of Atlassian products and practices.

Atlassian Community about banner
4,456,408
Community Members
 
Community Events
176
Community Groups

Update Issue's History tab after listener script makes changes

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

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

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)
}
}
}

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.

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

@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
TAGS

Atlassian Community Events