Scriptrunner Listener not picking up all subtasks

Anthony B January 22, 2021

Hello,

I have an issue that I am struggling to understand why its happening.  I have created a Scriptrunner Listener which listens out for when an issue is updated, created or deleted.  I first have a check to make sure I only deal with a particular issue type of interest (a sub-task).

The script gets the parent of the sub-task which triggered the event and then gets all subtasks, loops through each one, extracts a property and adds that to a list field in the parent.

Problem: The issue I am facing is that when I call getSubTaskObjects() I seemingly get back all sub-tasks except for the sub-task that was just created.... even though this exists as it triggered the creation event.  I cannot for the life of me figure out what is going on.

Any help in shedding some light on my issue is greatly appreciated

 

This is the script I have;

import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.util.ImportUtils

// Custom field identifiers
final String TEAM = "customfield_14712";
final String DELIVERY_TEAMS = "customfield_14711";
final String OUTCOME = "customfield_14708";

MutableIssue triggerissue = (MutableIssue)event.getIssue()

// Apply logic only to the Deliverable issue type
if (triggerissue.getIssueType().name == "Deliverable") {

// Initialise variables / objects
MutableIssue parentIssue = (MutableIssue)triggerissue.parentObject
List<Issue> childIssues = (List)parentIssue.getSubTaskObjects()

log.warn(childIssues.size())
//ALWAYS IS ONE LESS THAN WHAT IT SHOULD BE

ApplicationUser user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
IssueManager issueManager = ComponentAccessor.getIssueManager()

CustomField cfTeam = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(TEAM)

log.warn("new Delivarble team: "+triggerissue.getCustomFieldValue(cfTeam))
//THIS IS TO TEST I CAN ACCESS THE TRIGGER ISSUE DIRECTLY, WHICH I CAN

CustomField cfOutcome = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(OUTCOME)
CustomField cfDeliveryTeams = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(DELIVERY_TEAMS)

List teamsToAdd = new ArrayList() //Where we will load the Delivery Team property into for each sub-task
def parentOutcome = parentIssue.getCustomFieldValue(cfOutcome)

// If the custom fields do not exist, exit
if (!cfTeam || !cfDeliveryTeams || !cfOutcome) {
log.warn("No custom field found in Scriptrunner Listener for Delivery Team changes")
return
}

// Cycle through each sub-task to get the Delivery Team property value and add to a List
childIssues.each {
def radioValue = it.getCustomFieldValue(cfTeam)
log.warn(radioValue)
if (radioValue != null && !teamsToAdd.contains(radioValue)) {
teamsToAdd.add(radioValue)
}
}

parentIssue.setCustomFieldValue(cfDeliveryTeams, teamsToAdd) // Update Delivery Team(s) custom field with values gathered from the sub-tasks
issueManager.updateIssue(user, parentIssue, EventDispatchOption.DO_NOT_DISPATCH, false)
log.warn("Updating Idea ($parentIssue.key Delivery Team(s) with $teamsToAdd")

triggerissue.setCustomFieldValue(cfOutcome, parentOutcome) //Update the Delverable with the Outcome from the parent Idea
issueManager.updateIssue(user, triggerissue, EventDispatchOption.DO_NOT_DISPATCH, false)
log.warn("Updating Deliverable ($triggerissue.key) Outcome with $parentOutcome")


//Re-index the sub-task after update
boolean wasIndexing = ImportUtils.isIndexIssues()
ImportUtils.setIndexIssues(true)
ComponentAccessor.getComponent(IssueIndexingService.class).reIndex(triggerissue)
ComponentAccessor.getComponent(IssueIndexingService.class).reIndex(parentIssue)
ImportUtils.setIndexIssues(wasIndexing)

} else {
// The event was not on a Deliverable ticket, so exit
log.warn("This issue type is not a Deliverable")
return
}

 

2 answers

1 accepted

0 votes
Answer accepted
Radek Dostál
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.
January 23, 2021

Might be that at the time the event is published, Jira has not yet finished creating the Parent->Subtask link, not really sure in that regard, not sure I really want to know for the sake of my sanity.

 

However one workaround you could do there would be something akin to the following:

import com.atlassian.jira.issue.Issue

Issue triggerissue_issue = event.getIssue()

List<Issue> childIssues = (List) parentIssue.getSubTaskObjects()

if (!childIssues.contains(triggerissue_issue)) childIssues.add(triggerissue_issue)

 

So since you know you're working with the right sub-task, and you are able to get the parent issue, then this should be a fairly reliable way.

Anthony B January 23, 2021

Hi @Radek Dostál, thank you for the suggestion - I will look at it. 

There was another option I considered, which was to get the field property value straight from the trigger issue itself and then add it in with the same information I collect when I cycle through the list of sub tasks.

I felt this 'workaround' wasn't clean as I didn't really understand why the trigger sub-task wouldn't be included in the list of sub-tasks extracted from the trigger task's parent.

Thanks again.

Anthony B January 24, 2021

A further update to my problem - I now also have the reverse issue where when I delete a sub-task, this issue is still included in the 

parentIssue.getSubTaskObjects()

method.  So does this mean the event is triggered before Jira has deleted the issue?

 

The strange thing is that I've had my script running for some time and never had a problem with either the create or delete scenario.  Only now the event Listener seems to be triggering before Jira has had a chance to carry out the relevant event actions.

It sort of doesn't feel right to keep adding in workarounds and that perhaps I am doing something wrong in how I have written / structured my code? 

 

Any further help is greatly appreciated :D 

0 votes
Der Lun _Adaptavist_ January 25, 2021

You mentioned that the issue has never occurred before prior to this, was there any update to ScriptRunner or upgrade of the Jira instance recently before the issue occur?

Possibly there is different behavior in the Jira API after you have upgraded your Jira version. 

Can you confirm that there aren't any code changes before the issue occur as well?

It does look like some odd indexing or listener issue. Maybe rebuilding the indexes of the Jira instance might help. Not sure if those could be corrupted.

Also, did Radek's workaround help in this case?

I can give a go at trying to replicate your issue if you are able to provide me with the ScriptRunner and Jira version. From there, I would probably be able to help further

Anthony B January 25, 2021

@Der Lun _Adaptavist_ Thanks for your reply.  I don't believe there was an update to Jira (though I don't manage the instance directly). 

The only recent code change I made was to add in the forced re-indexing of the parent issue as I noticed the code wasn't being reflected in Jira straight away.  When I tested at the time everything seemed fine.

I implemented @Radek Dostál 's suggestion (thanks!) which worked fine for the scenario where an issue was created - but have now spotted this issue around deleting sub-tasks

My Jira instance version is v8.11.1#811002 and Scriptunner version is 6.13.1.  

I tried tidying up the script, and included @Radek Dostál suggestion - the most recent code is:

// This script listens out for any changes to the Delivery Team field within a Deliverable ticket
// Any changes to this field will be reflected in the parent Idea ticket, in the Implementing Teams field

import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.event.type.EventDispatchOption
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.user.ApplicationUser
import com.atlassian.jira.issue.index.IssueIndexingService
import com.atlassian.jira.util.ImportUtils
import com.atlassian.jira.event.type.EventType
import com.atlassian.jira.event.type.EventTypeManager

final String scriptNo = "[001]";


// Custom field identifiers
final String TEAM = "customfield_14712";
final String DELIVERY_TEAMS = "customfield_14711";

boolean wasIndexing = ImportUtils.isIndexIssues()
MutableIssue triggerIssue = (MutableIssue)event.getIssue()
//IssueManager issueManager2 = ComponentAccessor.getIssueManager()
//MutableIssue issue = issueManager2.getIssueObject("PRD-278")

//Start the log with key info
log.warn("$scriptNo Script: Listen to sub-task (Deliverable) updates and re-build Delivery Team(s) in parent Idea")
log.warn("$scriptNo Event Id: ${event.getEventTypeId()}")
log.warn("$scriptNo Issue: ${triggerIssue.getKey()} (${triggerIssue.getIssueType().name})")

// Apply logic only to the Deliverable issue type
if (triggerIssue.getIssueType().name == "Deliverable") {

try {

def change = event?.getChangeLog()?.getRelated("ChildChangeItem")?.find {it.field == "Delivery Team"}
def eventId = event.getEventTypeId()

// Do something if the value of the Category field changed
if (change || eventId == 8 || eventId == 1 ) {

if(change) log.warn "$scriptNo Delivery Team changed from '${change.oldstring}' to '${change.newstring}' by ${event.user.getUsername()}"

// Initialise variables / objects
MutableIssue parentIssue = (MutableIssue)triggerIssue.parentObject
List<Issue> subTasks = (List)parentIssue.getSubTaskObjects()

// If the trigger issue hasn't yet been added to the Parent issue then do this (but only for Issue Create event)
if (!subTasks.contains(triggerIssue && eventId == 1)) subTasks.add(triggerIssue)

ApplicationUser user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()
IssueManager issueManager = ComponentAccessor.getIssueManager()

CustomField cfTeam = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(TEAM)
CustomField cfDeliveryTeams = ComponentAccessor.getCustomFieldManager().getCustomFieldObject(DELIVERY_TEAMS)

List teamsToAdd = new ArrayList() //Where we will load the Delivery Team property into for each sub-task

// If the custom field(s) do not exist, exit
if (!cfTeam || !cfDeliveryTeams) {
log.warn("$scriptNo No custom field found in Scriptrunner Listener for Delivery Team changes")
return
}

// Cycle through each sub-task to get the Delivery Team property value and add to a List
subTasks.each {
def radioValue = it.getCustomFieldValue(cfTeam)

if (radioValue != null && !teamsToAdd.contains(radioValue)) {
teamsToAdd.add(radioValue)
log.warn("inside subtask loop. issue=${it.key}, team=${radioValue}")
}
}

// Update Delivery Team(s) custom field with values gathered from the sub-tasks
parentIssue.setCustomFieldValue(cfDeliveryTeams, teamsToAdd)
issueManager.updateIssue(user, parentIssue, EventDispatchOption.DO_NOT_DISPATCH, false)
log.warn("$scriptNo Updating ${parentIssue.key} (Idea) 'Delivery Team(s)' field with $teamsToAdd")

if (eventId != 8) {
//Re-index the sub-task after update (except if the trigger issue was deleted)
ImportUtils.setIndexIssues(true)
ComponentAccessor.getComponent(IssueIndexingService.class).reIndex(parentIssue)
ImportUtils.setIndexIssues(wasIndexing)
}

} else {
// The Delivery Team field was not updated, so no need to make changes to the Idea parent issue
log.warn("$scriptNo Delivery Team field not updated. Exiting Listener")
return
}
} catch(Exception e) {
log.warn("$scriptNo ${triggerIssue.key}: There was a problem re-building the Delivery Team(s) field");
log.warn("$scriptNo ${triggerIssue.key}: Error --> \n${e}");
}

} else {
// The event was not on a Deliverable ticket, so exit
log.warn("$scriptNo ${triggerIssue.getKey()} is not a Deliverable. Exiting Listener")
return
}

Thank you for offering to take a look

Like Claudio Maia likes this
Anthony B January 25, 2021

@Der Lun _Adaptavist_ I have updated my code to include the following;

 // For Issue Create - If the trigger issue hasn't yet been added to the Parent issue then do this manually
if (!subTasks.contains(triggerIssue) && eventId==1) {
subTasks.add(triggerIssue)
log.warn ("$scriptNo Manually added trigger issue to parent")
}

// For Issue Delete - If the trigger issue hasn't yet been removed from the Parent issue then do this manually
if (subTasks.contains(triggerIssue) && eventId==8) {
subTasks.remove(triggerIssue)
log.warn ("$scriptNo Manually removed trigger issue from parent")
}

This now at least prevents my script from cycling through an out-of-date list of sub tasks.

I did test a much simpler bit of code on another project and got a similar result.  So either I've just missed this all along or something has changed with our instance that has impacted event timings

Like Claudio Maia likes this
Der Lun _Adaptavist_ January 26, 2021

Hi Anthony,

Great to hear that you found a solution to this!

Perhaps you might have missed that piece of code prior to this. 

Nevertheless, do let us know here if this is an issue again. We are here to help

Suggest an answer

Log in or Sign up to answer